Dark theme twilight mode backend and tile

am: 381e3300e8

Change-Id: I0d5b96795d5ad818a9b7a21758721e35059d18ed
This commit is contained in:
Jay Aliomer
2019-11-21 12:23:42 -08:00
committed by android-build-merger
6 changed files with 313 additions and 12 deletions

View File

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

View File

@@ -473,4 +473,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

@@ -56,8 +56,8 @@ import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.util.ArraySet;
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;
@@ -65,6 +65,7 @@ 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;
@@ -72,6 +73,8 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
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;
@@ -85,6 +88,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 Map<Integer, String> mCarModePackagePriority = new HashMap<>();
private boolean mCarModeEnabled = false;
@@ -119,6 +126,7 @@ final class UiModeManagerService extends SystemService {
private TwilightManager mTwilightManager;
private NotificationManager mNotificationManager;
private StatusBarManager mStatusBarManager;
private WindowManagerInternal mWindowManager;
private PowerManager.WakeLock mWakeLock;
@@ -128,6 +136,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);
@@ -189,8 +208,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.
}
}
}
@@ -227,8 +261,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));
}
};
@@ -247,6 +283,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
@@ -313,6 +350,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();
@@ -347,8 +394,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;
@@ -474,14 +524,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 {
@@ -521,6 +587,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) {
@@ -1022,6 +1116,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;
}
}