summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/IUiModeManager.aidl5
-rw-r--r--core/java/android/app/UiModeManager.java14
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java21
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java120
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java161
6 files changed, 313 insertions, 12 deletions
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index cae54b6c0611..1380f0cdc341 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -61,4 +61,9 @@ interface IUiModeManager {
* Tells if Night mode is locked or not.
*/
boolean isNightModeLocked();
+
+ /**
+ * @hide
+ */
+ boolean setNightModeActivated(boolean active);
}
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 46316e1a254b..d8cfb7e3cb76 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -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;
+ }
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6ffea1be32ef..f28c3196daba 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -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>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index dd0ea5ef17f4..dc9a2ce1beef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -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 {
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 6b038979a98d..56db8898f7fc 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -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);
+
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
new file mode 100644
index 000000000000..338f837b9b44
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -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;
+ }
+}