Dark theme twilight mode backend and tile

When the user has Dark theme scheduling turned on (b/141567787), the quick settings tile subtext should reflect the following:
If Dark theme is on: "Until sunrise"
If Dark theme is off: "On at sunset"
If the user does not have Dark theme scheduled, the text should not appear.
This matches the appearance of the Night Light QS tile.
All starting windows snapshots will be deleted and splash screens
will be used instead.

Test: atest UiModeManagerServiceTest
Fix: 143874807
Merged-In: Ie2ce64b5c5544fffe76be7ec3f971eb7461f8c4d

Change-Id: Ie2ce64b5c5544fffe76be7ec3f971eb7461f8c4d
(cherry picked from commit 8b2671b029)
This commit is contained in:
Jay Aliomer
2019-10-24 13:18:06 -04:00
parent 893a2b05a5
commit 381e3300e8
6 changed files with 313 additions and 12 deletions

View File

@@ -61,4 +61,9 @@ interface IUiModeManager {
* Tells if Night mode is locked or not.
*/
boolean isNightModeLocked();
/**
* @hide
*/
boolean setNightModeActivated(boolean active);
}

View File

@@ -317,4 +317,18 @@ public class UiModeManager {
}
return true;
}
/**
* @hide*
*/
public boolean setNightModeActivated(boolean active) {
if (mService != null) {
try {
return mService.setNightModeActivated(active);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return false;
}
}

View File

@@ -894,6 +894,10 @@
<string name="quick_settings_ui_mode_night_label">Dark theme</string>
<!-- QuickSettings: Label for the dark theme tile when enabled by battery saver. [CHAR LIMIT=40] -->
<string name="quick_settings_ui_mode_night_label_battery_saver">Dark theme\nBattery saver</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be enabled at sunset. [CHAR LIMIT=20] -->
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset">On at sunset</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be on until sunrise. [CHAR LIMIT=20] -->
<string name="quick_settings_dark_mode_secondary_label_until_sunrise">Until sunrise</string>
<!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_label">NFC</string>

View File

@@ -21,7 +21,7 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.widget.Switch;
import android.text.TextUtils;
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.R;
@@ -79,24 +79,33 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
return;
}
boolean newState = !mState.value;
mUiModeManager.setNightMode(newState ? UiModeManager.MODE_NIGHT_YES
: UiModeManager.MODE_NIGHT_NO);
mUiModeManager.setNightModeActivated(newState);
refreshState(newState);
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
int uiMode = mUiModeManager.getNightMode();
boolean powerSave = mBatteryController.isPowerSave();
boolean isAuto = uiMode == UiModeManager.MODE_NIGHT_AUTO;
boolean nightMode = (mContext.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
if (isAuto) {
state.secondaryLabel = mContext.getResources().getString(nightMode
? R.string.quick_settings_dark_mode_secondary_label_until_sunrise
: R.string.quick_settings_dark_mode_secondary_label_on_at_sunset);
} else {
state.secondaryLabel = null;
}
state.value = nightMode;
state.label = mContext.getString(powerSave
? R.string.quick_settings_ui_mode_night_label_battery_saver
: R.string.quick_settings_ui_mode_night_label);
state.contentDescription = state.label;
state.icon = mIcon;
state.expandedAccessibilityClassName = Switch.class.getName();
state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
? state.label
: TextUtils.concat(state.label, ", ", state.secondaryLabel);
if (powerSave) {
state.state = Tile.STATE_UNAVAILABLE;
} else {

View File

@@ -53,8 +53,8 @@ import android.service.dreams.Sandman;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.DisableCarModeActivity;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
@@ -62,10 +62,13 @@ import com.android.internal.util.DumpUtils;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import static android.content.Intent.ACTION_SCREEN_OFF;
final class UiModeManagerService extends SystemService {
private static final String TAG = UiModeManager.class.getSimpleName();
private static final boolean LOG = false;
@@ -79,6 +82,10 @@ final class UiModeManagerService extends SystemService {
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mNightMode = UiModeManager.MODE_NIGHT_NO;
// we use the override auto mode
// for example: force night mode off in the night time while in auto mode
private int mNightModeOverride = mNightMode;
protected static final String OVERRIDE_NIGHT_MODE = Secure.UI_NIGHT_MODE + "_override";
private boolean mCarModeEnabled = false;
private boolean mCharging = false;
@@ -112,6 +119,7 @@ final class UiModeManagerService extends SystemService {
private TwilightManager mTwilightManager;
private NotificationManager mNotificationManager;
private StatusBarManager mStatusBarManager;
private WindowManagerInternal mWindowManager;
private PowerManager.WakeLock mWakeLock;
@@ -121,6 +129,17 @@ final class UiModeManagerService extends SystemService {
super(context);
}
@VisibleForTesting
protected UiModeManagerService(Context context, WindowManagerInternal wm,
PowerManager.WakeLock wl, TwilightManager tm,
boolean setupWizardComplete) {
super(context);
mWindowManager = wm;
mWakeLock = wl;
mTwilightManager = tm;
mSetupWizardComplete = setupWizardComplete;
}
private static Intent buildHomeIntent(String category) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(category);
@@ -182,8 +201,23 @@ final class UiModeManagerService extends SystemService {
public void onTwilightStateChanged(@Nullable TwilightState state) {
synchronized (mLock) {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
updateComputedNightModeLocked();
updateLocked(0, 0);
final IntentFilter intentFilter =
new IntentFilter(ACTION_SCREEN_OFF);
getContext().registerReceiver(mOnScreenOffHandler, intentFilter);
}
}
}
};
private final BroadcastReceiver mOnScreenOffHandler = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mLock) {
updateLocked(0, 0);
try {
getContext().unregisterReceiver(mOnScreenOffHandler);
} catch (IllegalArgumentException e) {
// we ignore this exception if the receiver is unregistered already.
}
}
}
@@ -220,8 +254,10 @@ final class UiModeManagerService extends SystemService {
private final ContentObserver mDarkThemeObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
final int mode = Secure.getIntForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE, mNightMode, 0);
int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
mNightMode, 0);
mode = mode == UiModeManager.MODE_NIGHT_AUTO
? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO;
SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
}
};
@@ -240,6 +276,7 @@ final class UiModeManagerService extends SystemService {
final PowerManager powerManager =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
// If setup isn't complete for this user listen for completion so we can unblock
// being able to send a night mode configuration change event
@@ -306,6 +343,16 @@ final class UiModeManagerService extends SystemService {
false, mDarkThemeObserver, 0);
}
@VisibleForTesting
protected IUiModeManager getService() {
return mService;
}
@VisibleForTesting
protected Configuration getConfiguration() {
return mConfiguration;
}
// Records whether setup wizard has happened or not and adds an observer for this user if not.
private void verifySetupWizardCompleted() {
final Context context = getContext();
@@ -340,8 +387,11 @@ final class UiModeManagerService extends SystemService {
if (mSetupWizardComplete) {
mNightMode = Secure.getIntForUser(context.getContentResolver(),
Secure.UI_NIGHT_MODE, defaultNightMode, userId);
mNightModeOverride = Secure.getIntForUser(context.getContentResolver(),
OVERRIDE_NIGHT_MODE, defaultNightMode, userId);
} else {
mNightMode = defaultNightMode;
mNightModeOverride = defaultNightMode;
}
return oldNightMode != mNightMode;
@@ -424,14 +474,30 @@ final class UiModeManagerService extends SystemService {
try {
synchronized (mLock) {
if (mNightMode != mode) {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
try {
getContext().unregisterReceiver(mOnScreenOffHandler);
} catch (IllegalArgumentException e) {
// we ignore this exception if the receiver is unregistered already.
}
}
// Only persist setting if not in car mode
if (!mCarModeEnabled) {
Secure.putIntForUser(getContext().getContentResolver(),
Secure.UI_NIGHT_MODE, mode, user);
Secure.putIntForUser(getContext().getContentResolver(),
OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
}
mNightMode = mode;
updateLocked(0, 0);
mNightModeOverride = mode;
//on screen off will update configuration instead
if (mNightMode != UiModeManager.MODE_NIGHT_AUTO) {
updateLocked(0, 0);
} else {
getContext().registerReceiver(
mOnScreenOffHandler, new IntentFilter(ACTION_SCREEN_OFF));
}
}
}
} finally {
@@ -471,6 +537,34 @@ final class UiModeManagerService extends SystemService {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
dumpImpl(pw);
}
@Override
public boolean setNightModeActivated(boolean active) {
synchronized (mLock) {
final long ident = Binder.clearCallingIdentity();
try {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
try {
getContext().unregisterReceiver(mOnScreenOffHandler);
} catch (IllegalArgumentException e) {
}
mNightModeOverride = active
? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO;
} else if (mNightMode == UiModeManager.MODE_NIGHT_NO
&& active) {
mNightMode = UiModeManager.MODE_NIGHT_YES;
} else if (mNightMode == UiModeManager.MODE_NIGHT_YES
&& !active) {
mNightMode = UiModeManager.MODE_NIGHT_NO;
}
updateConfigurationLocked();
sendConfigurationLocked();
return true;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
};
void dumpImpl(PrintWriter pw) {
@@ -848,6 +942,20 @@ final class UiModeManagerService extends SystemService {
if (state != null) {
mComputedNightMode = state.isNight();
}
if (mNightModeOverride == UiModeManager.MODE_NIGHT_YES && !mComputedNightMode) {
mComputedNightMode = true;
return;
}
if (mNightModeOverride == UiModeManager.MODE_NIGHT_NO && mComputedNightMode) {
mComputedNightMode = false;
return;
}
mNightModeOverride = mNightMode;
final int user = UserHandle.getCallingUserId();
Secure.putIntForUser(getContext().getContentResolver(),
OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 20019 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.server;
import android.app.IUiModeManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.PowerManager;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import com.android.server.twilight.TwilightManager;
import com.android.server.wm.WindowManagerInternal;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import java.util.HashSet;
import java.util.Set;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_NO;
import static android.app.UiModeManager.MODE_NIGHT_YES;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class UiModeManagerServiceTest extends UiServiceTestCase {
private UiModeManagerService mUiManagerService;
private IUiModeManager mService;
@Mock
private ContentResolver mContentResolver;
@Mock
private WindowManagerInternal mWindowManager;
@Mock
private Context mContext;
@Mock
private Resources mResources;
@Mock
TwilightManager mTwilightManager;
@Mock
PowerManager.WakeLock mWakeLock;
private Set<BroadcastReceiver> mScreenOffRecievers;
@Before
public void setUp() {
mUiManagerService = new UiModeManagerService(mContext, mWindowManager, mWakeLock,
mTwilightManager, true);
mScreenOffRecievers = new HashSet<>();
mService = mUiManagerService.getService();
when(mContext.checkCallingOrSelfPermission(anyString()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.registerReceiver(any(), any())).then(inv -> {
mScreenOffRecievers.add(inv.getArgument(0));
return null;
});
}
@Test
public void setAutoMode_screenOffRegistered() throws RemoteException {
try {
mService.setNightMode(MODE_NIGHT_NO);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
mService.setNightMode(MODE_NIGHT_AUTO);
verify(mContext).registerReceiver(any(BroadcastReceiver.class), any());
}
@Test
public void setAutoMode_screenOffUnRegistered() throws RemoteException {
try {
mService.setNightMode(MODE_NIGHT_AUTO);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
try {
mService.setNightMode(MODE_NIGHT_NO);
} catch (SecurityException e) { /*we should ignore this update config exception*/ }
given(mContext.registerReceiver(any(), any())).willThrow(SecurityException.class);
verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
}
@Test
public void setNightModeActive_fromNightModeYesToNoWhenFalse() throws RemoteException {
try {
mService.setNightMode(MODE_NIGHT_YES);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
try {
mService.setNightModeActivated(false);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
assertEquals(MODE_NIGHT_NO, mService.getNightMode());
}
@Test
public void setNightModeActive_fromNightModeNoToYesWhenTrue() throws RemoteException {
try {
mService.setNightMode(MODE_NIGHT_NO);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
try {
mService.setNightModeActivated(true);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
assertEquals(MODE_NIGHT_YES, mService.getNightMode());
}
@Test
public void setNightModeActive_autoNightModeNoChanges() throws RemoteException {
try {
mService.setNightMode(MODE_NIGHT_AUTO);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
try {
mService.setNightModeActivated(true);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
assertEquals(MODE_NIGHT_AUTO, mService.getNightMode());
}
@Test
public void isNightModeActive_nightModeYes() throws RemoteException {
try {
mService.setNightMode(MODE_NIGHT_YES);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
assertTrue(isNightModeActivated());
}
@Test
public void isNightModeActive_nightModeNo() throws RemoteException {
try {
mService.setNightMode(MODE_NIGHT_NO);
} catch (SecurityException e) { /* we should ignore this update config exception*/ }
assertFalse(isNightModeActivated());
}
private boolean isNightModeActivated() {
return (mUiManagerService.getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_YES) != 0;
}
}