Dark theme twilight mode backend and tile
am: 381e3300e8
Change-Id: I0d5b96795d5ad818a9b7a21758721e35059d18ed
This commit is contained in:
@@ -68,4 +68,9 @@ interface IUiModeManager {
|
||||
* Tells if Night mode is locked or not.
|
||||
*/
|
||||
boolean isNightModeLocked();
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
boolean setNightModeActivated(boolean active);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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