diff --git a/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java b/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java index 1b7f003ac8f..c4765c3cf15 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java +++ b/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java @@ -15,23 +15,28 @@ */ 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; @@ -51,6 +56,7 @@ import java.io.IOException; 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; @@ -59,6 +65,9 @@ public class DarkThemeSlice implements CustomSliceable { 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; @@ -78,8 +87,21 @@ public class DarkThemeSlice implements CustomSliceable { 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) @@ -90,6 +112,12 @@ public class DarkThemeSlice implements CustomSliceable { @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) @@ -99,7 +127,7 @@ public class DarkThemeSlice implements CustomSliceable { .setSubtitle(mContext.getText(R.string.dark_theme_slice_subtitle)) .setPrimaryAction( SliceAction.createToggle(toggleAction, null /* actionTitle */, - isDarkThemeMode(mContext)))) + isChecked))) .build(); } @@ -112,6 +140,10 @@ public class DarkThemeSlice implements CustomSliceable { 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); @@ -143,12 +175,17 @@ public class DarkThemeSlice implements CustomSliceable { } @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 { private final Context mContext; private final ContentObserver mContentObserver = @@ -160,10 +197,12 @@ public class DarkThemeSlice implements CustomSliceable { } } }; + private final HomeKeyReceiver mHomeKeyReceiver; public DarkThemeWorker(Context context, Uri uri) { super(context, uri); mContext = context; + mHomeKeyReceiver = new HomeKeyReceiver(); } @Override @@ -171,15 +210,55 @@ public class DarkThemeSlice implements CustomSliceable { 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); + } + } } diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSliceTest.java index 1af7b2bfacd..7170bdda9cb 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSliceTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSliceTest.java @@ -16,6 +16,9 @@ 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; @@ -68,7 +71,7 @@ public class DarkThemeSliceTest { // Set-up specs for SliceMetadata. SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); - mDarkThemeSlice = spy(new DarkThemeSlice(mContext)); + mDarkThemeSlice = new DarkThemeSlice(mContext); mDarkThemeSlice.sKeepSliceShow = false; } @@ -81,7 +84,7 @@ public class DarkThemeSliceTest { @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(); } @@ -111,7 +114,7 @@ public class DarkThemeSliceTest { @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); @@ -126,7 +129,7 @@ public class DarkThemeSliceTest { 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); @@ -143,6 +146,26 @@ public class DarkThemeSliceTest { 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 public void getSlice_isAvailable_returnSlice() { setBatteryCapacityLevel(40); @@ -163,7 +186,7 @@ public class DarkThemeSliceTest { } 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);