*/
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.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManager;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
public class DarkThemeSlice implements CustomSliceable {
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 DELAY_TIME_EXECUTING_DARK_THEME = 200;
static boolean sKeepSliceShow;
@VisibleForTesting
static long sActiveUiSession = -1000;
+ @VisibleForTesting
+ static boolean sSliceClicked = false;
+ static boolean sPreChecked = false;
private final Context mContext;
private final UiModeManager mUiModeManager;
sActiveUiSession = currentUiSession;
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,
ListBuilder.INFINITY)
.setIsError(true)
@ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
final IconCompat icon =
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,
ListBuilder.INFINITY)
.setAccentColor(color)
.setSubtitle(mContext.getText(R.string.dark_theme_slice_subtitle))
.setPrimaryAction(
SliceAction.createToggle(toggleAction, null /* actionTitle */,
- isDarkThemeMode(mContext))))
+ isChecked)))
.build();
}
public void onNotifyChange(Intent intent) {
final boolean isChecked = intent.getBooleanExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE,
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
new Handler(Looper.getMainLooper()).postDelayed(() -> {
mUiModeManager.setNightModeActivated(isChecked);
}
@VisibleForTesting
- boolean isDarkThemeMode(Context context) {
+ static boolean isDarkThemeMode(Context context) {
final int currentNightMode =
context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
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> {
private final Context mContext;
private final ContentObserver mContentObserver =
}
}
};
+ private final HomeKeyReceiver mHomeKeyReceiver;
public DarkThemeWorker(Context context, Uri uri) {
super(context, uri);
mContext = context;
+ mHomeKeyReceiver = new HomeKeyReceiver();
}
@Override
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(LOW_POWER_MODE), false /* notifyForDescendants */,
mContentObserver);
+ final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ mContext.registerReceiver(mHomeKeyReceiver, intentFilter);
}
@Override
protected void onSliceUnpinned() {
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ mContext.unregisterReceiver(mHomeKeyReceiver);
}
@Override
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);
+ }
+ }
}
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 org.mockito.Mockito.doReturn;
// Set-up specs for SliceMetadata.
SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
- mDarkThemeSlice = spy(new DarkThemeSlice(mContext));
+ mDarkThemeSlice = new DarkThemeSlice(mContext);
mDarkThemeSlice.sKeepSliceShow = false;
}
@Test
public void isAvailable_inDarkThemeMode_returnFalse() {
- doReturn(true).when(mDarkThemeSlice).isDarkThemeMode(mContext);
+ mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_YES;
assertThat(mDarkThemeSlice.isAvailable(mContext)).isFalse();
}
@Test
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 SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
mDarkThemeSlice.sActiveUiSession =
mFeatureFactory.slicesFeatureProvider.getUiSessionToken() + 1;
- doReturn(true).when(mDarkThemeSlice).isDarkThemeMode(mContext);
+ mContext.getResources().getConfiguration().uiMode = UI_MODE_NIGHT_YES;
final Slice mediaSlice = mDarkThemeSlice.getSlice();
final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
}
@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
public void getSlice_isAvailable_returnSlice() {
setBatteryCapacityLevel(40);
}
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);
when(mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY))
.thenReturn(power_level);