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:
@@ -61,4 +61,9 @@ interface IUiModeManager {
|
||||
* Tells if Night mode is locked or not.
|
||||
*/
|
||||
boolean isNightModeLocked();
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
boolean setNightModeActivated(boolean active);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user