Update Dark theme slice for new behavior

- Add an broadcast receiver to monitor the Home key.
- Add the sClickedInSlice flag to verify Dark theme is changed by slice or not.

Bug: 142476879
Fixes: 146149658
Fixes: 146652692
Test: make RunSettingsRoboTests -j ROBOTEST_FILTER=com.android.settings.homepage
Change-Id: I174d0c861ea3b3f793201e3b2e0ff65a1e690f4e
This commit is contained in:
Sunny Shao
2019-12-17 22:04:58 +08:00
parent 4873d0e894
commit 31c5f39d57
2 changed files with 113 additions and 11 deletions

View File

@@ -15,23 +15,28 @@
*/ */
package com.android.settings.homepage.contextualcards.slices; package com.android.settings.homepage.contextualcards.slices;
import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
import static android.provider.Settings.Global.LOW_POWER_MODE; import static android.provider.Settings.Global.LOW_POWER_MODE;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
import android.annotation.ColorInt; import android.annotation.ColorInt;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.UiModeManager; import android.app.UiModeManager;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.net.Uri; import android.net.Uri;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.PowerManager; import android.os.PowerManager;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
@@ -51,6 +56,7 @@ import java.io.IOException;
public class DarkThemeSlice implements CustomSliceable { public class DarkThemeSlice implements CustomSliceable {
private static final String TAG = "DarkThemeSlice"; private static final String TAG = "DarkThemeSlice";
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
private static final int BATTERY_LEVEL_THRESHOLD = 50; private static final int BATTERY_LEVEL_THRESHOLD = 50;
private static final int DELAY_TIME_EXECUTING_DARK_THEME = 200; private static final int DELAY_TIME_EXECUTING_DARK_THEME = 200;
@@ -59,6 +65,9 @@ public class DarkThemeSlice implements CustomSliceable {
static boolean sKeepSliceShow; static boolean sKeepSliceShow;
@VisibleForTesting @VisibleForTesting
static long sActiveUiSession = -1000; static long sActiveUiSession = -1000;
@VisibleForTesting
static boolean sSliceClicked = false;
static boolean sPreChecked = false;
private final Context mContext; private final Context mContext;
private final UiModeManager mUiModeManager; private final UiModeManager mUiModeManager;
@@ -78,8 +87,21 @@ public class DarkThemeSlice implements CustomSliceable {
sActiveUiSession = currentUiSession; sActiveUiSession = currentUiSession;
sKeepSliceShow = false; sKeepSliceShow = false;
} }
// Dark theme slice will disappear when battery saver is ON.
if (mPowerManager.isPowerSaveMode() || (!sKeepSliceShow && !isAvailable(mContext))) { // 1. Dark theme slice will disappear when battery saver is ON.
// 2. If the slice is shown and the user doesn't toggle it directly, but instead turns on
// Dark theme from Quick settings or display page, the card should no longer persist.
// This card will persist when user clicks its toggle directly.
// 3. If the slice is shown and the user toggles it on (switch to Dark theme) directly,
// then user returns to home (launcher), no matter by the Back key or Home gesture.
// Next time the Settings displays on screen again this card should no longer persist.
if (DEBUG) {
Log.d(TAG,
"!sKeepSliceShow = " + !sKeepSliceShow + " !sSliceClicked = "
+ !sSliceClicked + " !isAvailable = " + !isAvailable(mContext));
}
if (mPowerManager.isPowerSaveMode() || ((!sKeepSliceShow || !sSliceClicked)
&& !isAvailable(mContext))) {
return new ListBuilder(mContext, CustomSliceRegistry.DARK_THEME_SLICE_URI, return new ListBuilder(mContext, CustomSliceRegistry.DARK_THEME_SLICE_URI,
ListBuilder.INFINITY) ListBuilder.INFINITY)
.setIsError(true) .setIsError(true)
@@ -90,6 +112,12 @@ public class DarkThemeSlice implements CustomSliceable {
@ColorInt final int color = Utils.getColorAccentDefaultColor(mContext); @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
final IconCompat icon = final IconCompat icon =
IconCompat.createWithResource(mContext, R.drawable.dark_theme); IconCompat.createWithResource(mContext, R.drawable.dark_theme);
final boolean isChecked = isDarkThemeMode(mContext);
if (sPreChecked != isChecked) {
// Dark(Night) mode changed and reset the sSliceClicked.
resetValue(isChecked, false);
}
return new ListBuilder(mContext, CustomSliceRegistry.DARK_THEME_SLICE_URI, return new ListBuilder(mContext, CustomSliceRegistry.DARK_THEME_SLICE_URI,
ListBuilder.INFINITY) ListBuilder.INFINITY)
.setAccentColor(color) .setAccentColor(color)
@@ -99,7 +127,7 @@ public class DarkThemeSlice implements CustomSliceable {
.setSubtitle(mContext.getText(R.string.dark_theme_slice_subtitle)) .setSubtitle(mContext.getText(R.string.dark_theme_slice_subtitle))
.setPrimaryAction( .setPrimaryAction(
SliceAction.createToggle(toggleAction, null /* actionTitle */, SliceAction.createToggle(toggleAction, null /* actionTitle */,
isDarkThemeMode(mContext)))) isChecked)))
.build(); .build();
} }
@@ -112,6 +140,10 @@ public class DarkThemeSlice implements CustomSliceable {
public void onNotifyChange(Intent intent) { public void onNotifyChange(Intent intent) {
final boolean isChecked = intent.getBooleanExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, final boolean isChecked = intent.getBooleanExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE,
false); false);
// Dark(Night) mode changed by user clicked the toggle in the Dark theme slice.
if (isChecked) {
resetValue(isChecked, true);
}
// make toggle transition more smooth before dark theme takes effect // make toggle transition more smooth before dark theme takes effect
new Handler(Looper.getMainLooper()).postDelayed(() -> { new Handler(Looper.getMainLooper()).postDelayed(() -> {
mUiModeManager.setNightModeActivated(isChecked); mUiModeManager.setNightModeActivated(isChecked);
@@ -143,12 +175,17 @@ public class DarkThemeSlice implements CustomSliceable {
} }
@VisibleForTesting @VisibleForTesting
boolean isDarkThemeMode(Context context) { static boolean isDarkThemeMode(Context context) {
final int currentNightMode = final int currentNightMode =
context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
return currentNightMode == Configuration.UI_MODE_NIGHT_YES; return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
} }
private void resetValue(boolean preChecked, boolean clicked) {
sPreChecked = preChecked;
sSliceClicked = clicked;
}
public static class DarkThemeWorker extends SliceBackgroundWorker<Void> { public static class DarkThemeWorker extends SliceBackgroundWorker<Void> {
private final Context mContext; private final Context mContext;
private final ContentObserver mContentObserver = private final ContentObserver mContentObserver =
@@ -160,10 +197,12 @@ public class DarkThemeSlice implements CustomSliceable {
} }
} }
}; };
private final HomeKeyReceiver mHomeKeyReceiver;
public DarkThemeWorker(Context context, Uri uri) { public DarkThemeWorker(Context context, Uri uri) {
super(context, uri); super(context, uri);
mContext = context; mContext = context;
mHomeKeyReceiver = new HomeKeyReceiver();
} }
@Override @Override
@@ -171,15 +210,55 @@ public class DarkThemeSlice implements CustomSliceable {
mContext.getContentResolver().registerContentObserver( mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(LOW_POWER_MODE), false /* notifyForDescendants */, Settings.Global.getUriFor(LOW_POWER_MODE), false /* notifyForDescendants */,
mContentObserver); mContentObserver);
final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mContext.registerReceiver(mHomeKeyReceiver, intentFilter);
} }
@Override @Override
protected void onSliceUnpinned() { protected void onSliceUnpinned() {
mContext.getContentResolver().unregisterContentObserver(mContentObserver); mContext.getContentResolver().unregisterContentObserver(mContentObserver);
mContext.unregisterReceiver(mHomeKeyReceiver);
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
} }
} }
/**
* A receiver for Home key and recent app key.
*/
public static class HomeKeyReceiver extends BroadcastReceiver {
private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
@Override
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, intent.getAction())) {
if (TextUtils.equals(getTargetKey(context),
intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY))) {
if (DEBUG) {
Log.d(TAG, "HomeKeyReceiver : target key = " + getTargetKey(context));
}
if (DarkThemeSlice.isDarkThemeMode(context)) {
FeatureFactory.getFactory(
context).getSlicesFeatureProvider().newUiSession();
}
}
}
}
private String getTargetKey(Context context) {
if (isGestureNavigationEnabled(context)) {
return SYSTEM_DIALOG_REASON_RECENT_APPS;
}
return SYSTEM_DIALOG_REASON_HOME_KEY;
}
private boolean isGestureNavigationEnabled(Context context) {
return NAV_BAR_MODE_GESTURAL == context.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode);
}
}
} }

View File

@@ -16,6 +16,9 @@
package com.android.settings.homepage.contextualcards.slices; package com.android.settings.homepage.contextualcards.slices;
import static android.content.res.Configuration.UI_MODE_NIGHT_NO;
import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
@@ -68,7 +71,7 @@ public class DarkThemeSliceTest {
// Set-up specs for SliceMetadata. // Set-up specs for SliceMetadata.
SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
mDarkThemeSlice = spy(new DarkThemeSlice(mContext)); mDarkThemeSlice = new DarkThemeSlice(mContext);
mDarkThemeSlice.sKeepSliceShow = false; mDarkThemeSlice.sKeepSliceShow = false;
} }
@@ -81,7 +84,7 @@ public class DarkThemeSliceTest {
@Test @Test
public void isAvailable_inDarkThemeMode_returnFalse() { public void isAvailable_inDarkThemeMode_returnFalse() {
doReturn(true).when(mDarkThemeSlice).isDarkThemeMode(mContext); mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_YES;
assertThat(mDarkThemeSlice.isAvailable(mContext)).isFalse(); assertThat(mDarkThemeSlice.isAvailable(mContext)).isFalse();
} }
@@ -111,7 +114,7 @@ public class DarkThemeSliceTest {
@Test @Test
public void getSlice_notAvailable_returnErrorSlice() { public void getSlice_notAvailable_returnErrorSlice() {
doReturn(true).when(mDarkThemeSlice).isDarkThemeMode(mContext); mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_YES;
final Slice mediaSlice = mDarkThemeSlice.getSlice(); final Slice mediaSlice = mDarkThemeSlice.getSlice();
final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice); final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
@@ -126,7 +129,7 @@ public class DarkThemeSliceTest {
mDarkThemeSlice.sActiveUiSession = mDarkThemeSlice.sActiveUiSession =
mFeatureFactory.slicesFeatureProvider.getUiSessionToken() + 1; mFeatureFactory.slicesFeatureProvider.getUiSessionToken() + 1;
doReturn(true).when(mDarkThemeSlice).isDarkThemeMode(mContext); mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_YES;
final Slice mediaSlice = mDarkThemeSlice.getSlice(); final Slice mediaSlice = mDarkThemeSlice.getSlice();
final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice); final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
@@ -143,6 +146,26 @@ public class DarkThemeSliceTest {
assertThat(mDarkThemeSlice.getSlice()).isNotNull(); assertThat(mDarkThemeSlice.getSlice()).isNotNull();
} }
@Test
public void getSlice_sliceNotClicked_notAvailable_returnErrorSlice() {
mDarkThemeSlice.sSliceClicked = false;
mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_YES;
final Slice mediaSlice = mDarkThemeSlice.getSlice();
final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
assertThat(metadata.isErrorSlice()).isTrue();
}
@Test
public void getSlice_sliceClicked_isAvailable_returnSlice() {
mDarkThemeSlice.sSliceClicked = true;
setBatteryCapacityLevel(40);
assertThat(mDarkThemeSlice.getSlice()).isNotNull();
}
@Test @Test
public void getSlice_isAvailable_returnSlice() { public void getSlice_isAvailable_returnSlice() {
setBatteryCapacityLevel(40); setBatteryCapacityLevel(40);
@@ -163,7 +186,7 @@ public class DarkThemeSliceTest {
} }
private void setBatteryCapacityLevel(int power_level) { private void setBatteryCapacityLevel(int power_level) {
doReturn(false).when(mDarkThemeSlice).isDarkThemeMode(mContext); mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_NO;
doReturn(mBatteryManager).when(mContext).getSystemService(BatteryManager.class); doReturn(mBatteryManager).when(mContext).getSystemService(BatteryManager.class);
when(mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)) when(mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY))
.thenReturn(power_level); .thenReturn(power_level);