Custom dark theme scheduling

allows the use to set the start and end automatic dark theme
activation within a day.
Fixes: 147649309
Test: atest UiModeManagerServiceTest UiModeManagerTest
Change-Id: Iaa3593d4e8863412e3703ce9f089b88dd4df1225
This commit is contained in:
Jay Aliomer
2019-12-07 12:14:26 -05:00
parent 310f576b7f
commit 15d855465b
11 changed files with 696 additions and 84 deletions

View File

@@ -6470,7 +6470,11 @@ package android.app {
method public void disableCarMode(int);
method public void enableCarMode(int);
method public int getCurrentModeType();
method @NonNull public java.time.LocalTime getCustomNightModeEnd();
method @NonNull public java.time.LocalTime getCustomNightModeStart();
method public int getNightMode();
method public void setCustomNightModeEnd(@NonNull java.time.LocalTime);
method public void setCustomNightModeStart(@NonNull java.time.LocalTime);
method public void setNightMode(int);
field public static String ACTION_ENTER_CAR_MODE;
field public static String ACTION_ENTER_DESK_MODE;
@@ -6480,6 +6484,7 @@ package android.app {
field public static final int ENABLE_CAR_MODE_ALLOW_SLEEP = 2; // 0x2
field public static final int ENABLE_CAR_MODE_GO_CAR_HOME = 1; // 0x1
field public static final int MODE_NIGHT_AUTO = 0; // 0x0
field public static final int MODE_NIGHT_CUSTOM = 3; // 0x3
field public static final int MODE_NIGHT_NO = 1; // 0x1
field public static final int MODE_NIGHT_YES = 2; // 0x2
}
@@ -50559,6 +50564,7 @@ package android.util {
method public static java.util.TimeZone getTimeZone(int, boolean, long, String);
method public static String getTimeZoneDatabaseVersion();
method @Nullable public static java.util.List<java.lang.String> getTimeZoneIdsForCountryCode(@NonNull String);
method public static boolean isTimeBetween(@NonNull java.time.LocalTime, @NonNull java.time.LocalTime, @NonNull java.time.LocalTime);
}
@Deprecated public class TimingLogger {

View File

@@ -1,33 +1,48 @@
// Baseline format: 1.0
AcronymName: android.net.NetworkCapabilities#setSSID(String):
Acronyms should not be capitalized in method names: was `setSSID`, should this be `setSsid`?
ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION:
ActionValue: android.net.wifi.WifiManager#ACTION_LINK_CONFIGURATION_CHANGED:
Inconsistent action value; expected `android.net.wifi.action.LINK_CONFIGURATION_CHANGED`, was `android.net.wifi.LINK_CONFIGURATION_CHANGED`
ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #1:
Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
ArrayReturn: android.bluetooth.BluetoothCodecStatus#BluetoothCodecStatus(android.bluetooth.BluetoothCodecConfig, android.bluetooth.BluetoothCodecConfig[], android.bluetooth.BluetoothCodecConfig[]) parameter #2:
Method parameter should be Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsLocalCapabilities():
Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
ArrayReturn: android.bluetooth.BluetoothCodecStatus#getCodecsSelectableCapabilities():
Method should return Collection<BluetoothCodecConfig> (or subclass) instead of raw array; was `android.bluetooth.BluetoothCodecConfig[]`
ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #0:
Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]`
ArrayReturn: android.bluetooth.BluetoothUuid#containsAnyUuid(android.os.ParcelUuid[], android.os.ParcelUuid[]) parameter #1:
Method parameter should be Collection<ParcelUuid> (or subclass) instead of raw array; was `android.os.ParcelUuid[]`
ArrayReturn: android.media.tv.tuner.Tuner.FilterCallback#onFilterEvent(android.media.tv.tuner.Tuner.Filter, android.media.tv.tuner.filter.FilterEvent[]) parameter #1:
Method parameter should be Collection<FilterEvent> (or subclass) instead of raw array; was `android.media.tv.tuner.filter.FilterEvent[]`
ArrayReturn: android.net.NetworkScoreManager#requestScores(android.net.NetworkKey[]) parameter #0:
Method parameter should be Collection<NetworkKey> (or subclass) instead of raw array; was `android.net.NetworkKey[]`
ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#deletePersistentGroup(android.net.wifi.p2p.WifiP2pManager.Channel, int, android.net.wifi.p2p.WifiP2pManager.ActionListener):
Registration methods should have overload that accepts delivery Executor: `deletePersistentGroup`
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#factoryReset(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener):
Registration methods should have overload that accepts delivery Executor: `factoryReset`
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#listen(android.net.wifi.p2p.WifiP2pManager.Channel, boolean, android.net.wifi.p2p.WifiP2pManager.ActionListener):
Registration methods should have overload that accepts delivery Executor: `listen`
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#requestPersistentGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PersistentGroupInfoListener):
Registration methods should have overload that accepts delivery Executor: `requestPersistentGroupInfo`
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setDeviceName(android.net.wifi.p2p.WifiP2pManager.Channel, String, android.net.wifi.p2p.WifiP2pManager.ActionListener):
Registration methods should have overload that accepts delivery Executor: `setDeviceName`
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setWfdInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pWfdInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener):
Registration methods should have overload that accepts delivery Executor: `setWfdInfo`
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setWifiP2pChannels(android.net.wifi.p2p.WifiP2pManager.Channel, int, int, android.net.wifi.p2p.WifiP2pManager.ActionListener):
Registration methods should have overload that accepts delivery Executor: `setWifiP2pChannels`
HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities():
Type must not be heavy BitSet (method android.net.wifi.wificond.NativeScanResult.getCapabilities())
PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback):
Found registerApCallback but not unregisterApCallback in android.net.wifi.wificond.WifiCondManager
GenericException: android.app.prediction.AppPredictor#finalize():
@@ -40,13 +55,22 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize():
HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities():
IntentBuilderName: android.content.Context#registerReceiverForAllUsers(android.content.BroadcastReceiver, android.content.IntentFilter, String, android.os.Handler):
Methods creating an Intent should be named `create<Foo>Intent()`, was `registerReceiverForAllUsers`
KotlinKeyword: android.app.Notification#when:
KotlinOperator: android.telephony.CbGeoUtils.Geometry#contains(android.telephony.CbGeoUtils.LatLng):
Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#toString():
MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#writeToParcel(android.os.Parcel, int) parameter #0:
@@ -70,7 +94,7 @@ MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent
MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1:
MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2:
MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0:
@@ -157,43 +181,60 @@ MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(
MutableBareField: android.net.IpConfiguration#httpProxy:
Bare field httpProxy must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.IpConfiguration#ipAssignment:
Bare field ipAssignment must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.IpConfiguration#proxySettings:
Bare field proxySettings must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.IpConfiguration#staticIpConfiguration:
Bare field staticIpConfiguration must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.wifi.WifiConfiguration#allowAutojoin:
MutableBareField: android.net.wifi.WifiConfiguration#apBand:
Bare field apBand must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.wifi.WifiConfiguration#carrierId:
MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSpecifier:
Bare field fromWifiNetworkSpecifier must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSuggestion:
Bare field fromWifiNetworkSuggestion must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.wifi.WifiConfiguration#macRandomizationSetting:
Bare field macRandomizationSetting must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.wifi.WifiConfiguration#meteredOverride:
Bare field meteredOverride must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.wifi.WifiConfiguration#requirePMF:
Bare field requirePMF must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.wifi.WifiConfiguration#saePasswordId:
Bare field saePasswordId must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.wifi.WifiConfiguration#shared:
Bare field shared must be marked final, or moved behind accessors if mutable
MutableBareField: android.net.wifi.WifiScanner.ScanSettings#type:
Bare field type must be marked final, or moved behind accessors if mutable
NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
NotCloseable: android.bluetooth.BluetoothA2dpSink:
Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothA2dpSink
NotCloseable: android.bluetooth.BluetoothMap:
Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothMap
NotCloseable: android.bluetooth.BluetoothPan:
Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPan
NotCloseable: android.bluetooth.BluetoothPbap:
Classes that release resources (finalize()) should implement AutoClosable and CloseGuard: class android.bluetooth.BluetoothPbap
OnNameExpected: android.content.ContentProvider#checkUriPermission(android.net.Uri, int, int):
If implemented by developer, should follow the on<Something> style; otherwise consider marking final
PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback):
ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
@@ -201,6 +242,7 @@ ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.
ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context):
SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
@@ -246,9 +288,11 @@ SamShouldBeLast: android.app.AlarmManager#setExact(int, long, String, android.ap
SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String):
SamShouldBeLast: android.app.WallpaperManager#addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler):
SAM-compatible parameters (such as parameter 1, "listener", in android.app.WallpaperManager.addOnColorsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback):
SamShouldBeLast: android.content.Context#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
SamShouldBeLast: android.content.Context#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection):
@@ -279,12 +323,20 @@ SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageC
SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper):
SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper):
SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper):
SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper):
SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler):
SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
@@ -380,7 +432,7 @@ SamShouldBeLast: android.telephony.TelephonyManager#requestNetworkScan(android.t
SamShouldBeLast: android.telephony.TelephonyManager#setPreferredOpportunisticDataSubscription(int, boolean, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
SamShouldBeLast: android.telephony.TelephonyManager#updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
SamShouldBeLast: android.view.View#postDelayed(Runnable, long):
SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long):
@@ -445,3 +497,11 @@ ServiceName: android.Manifest.permission#REQUEST_NOTIFICATION_ASSISTANT_SERVICE:
ServiceName: android.provider.DeviceConfig#NAMESPACE_PACKAGE_MANAGER_SERVICE:
UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
UserHandleName: android.telephony.CellBroadcastIntents#sendOrderedBroadcastForBackgroundReceivers(android.content.Context, android.os.UserHandle, android.content.Intent, String, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `sendOrderedBroadcastForBackgroundReceivers`

View File

@@ -70,7 +70,27 @@ interface IUiModeManager {
boolean isNightModeLocked();
/**
* @hide
* [De]Activates night mode
*/
boolean setNightModeActivated(boolean active);
/**
* Returns custom start clock time
*/
long getCustomNightModeStart();
/**
* Sets custom start clock time
*/
void setCustomNightModeStart(long time);
/**
* Returns custom end clock time
*/
long getCustomNightModeEnd();
/**
* Sets custom end clock time
*/
void setCustomNightModeEnd(long time);
}

View File

@@ -18,6 +18,7 @@ package android.app;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -32,6 +33,7 @@ import android.os.ServiceManager.ServiceNotFoundException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.LocalTime;
/**
* This class provides access to the system uimode services. These services
@@ -163,6 +165,7 @@ public class UiModeManager {
/** @hide */
@IntDef(prefix = { "MODE_" }, value = {
MODE_NIGHT_AUTO,
MODE_NIGHT_CUSTOM,
MODE_NIGHT_NO,
MODE_NIGHT_YES
})
@@ -173,19 +176,25 @@ public class UiModeManager {
* Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
* automatically switch night mode on and off based on the time.
*/
public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_UNDEFINED >> 4;
public static final int MODE_NIGHT_AUTO = 0;
/**
* Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
* automatically switch night mode on and off based on the time.
*/
public static final int MODE_NIGHT_CUSTOM = 3;
/**
* Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
* never run in night mode.
*/
public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
public static final int MODE_NIGHT_NO = 1;
/**
* Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
* always run in night mode.
*/
public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
public static final int MODE_NIGHT_YES = 2;
private IUiModeManager mService;
@@ -377,6 +386,8 @@ public class UiModeManager {
* {@code notnight} mode</li>
* <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into
* {@code night} mode</li>
* <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between
* {@code night} and {@code notnight} based on the custom time set (or default)</li>
* <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between
* {@code night} and {@code notnight} based on the device's current
* location and certain other sensors</li>
@@ -418,6 +429,7 @@ public class UiModeManager {
* <li>{@link #MODE_NIGHT_NO}</li>
* <li>{@link #MODE_NIGHT_YES}</li>
* <li>{@link #MODE_NIGHT_AUTO}</li>
* <li>{@link #MODE_NIGHT_CUSTOM}</li>
* <li>{@code -1} on error</li>
* </ul>
*
@@ -475,7 +487,7 @@ public class UiModeManager {
}
/**
* @hide*
* @hide
*/
public boolean setNightModeActivated(boolean active) {
if (mService != null) {
@@ -487,4 +499,75 @@ public class UiModeManager {
}
return false;
}
/**
* Returns the time of the day Dark theme activates
* <p>
* When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses
* this time set to activate it automatically.
*/
@NonNull
public LocalTime getCustomNightModeStart() {
if (mService != null) {
try {
return LocalTime.ofNanoOfDay(mService.getCustomNightModeStart() * 1000);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return LocalTime.MIDNIGHT;
}
/**
* Sets the time of the day Dark theme activates
* <p>
* When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses
* this time set to activate it automatically
* @param time The time of the day Dark theme should activate
*/
public void setCustomNightModeStart(@NonNull LocalTime time) {
if (mService != null) {
try {
mService.setCustomNightModeStart(time.toNanoOfDay() / 1000);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* Returns the time of the day Dark theme deactivates
* <p>
* When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses
* this time set to deactivate it automatically.
*/
@NonNull
public LocalTime getCustomNightModeEnd() {
if (mService != null) {
try {
return LocalTime.ofNanoOfDay(mService.getCustomNightModeEnd() * 1000);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return LocalTime.MIDNIGHT;
}
/**
* Sets the time of the day Dark theme deactivates
* <p>
* When night mode is {@link #MODE_NIGHT_CUSTOM}, the system uses
* this time set to deactivate it automatically.
* @param time The time of the day Dark theme should deactivate
*/
public void setCustomNightModeEnd(@NonNull LocalTime time) {
if (mService != null) {
try {
mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
}

View File

@@ -5882,6 +5882,22 @@ public final class Settings {
public static final String DARK_MODE_DIALOG_SEEN =
"dark_mode_dialog_seen";
/**
* Custom time when Dark theme is scheduled to activate.
* Represented as milliseconds from midnight (e.g. 79200000 == 10pm).
* @hide
*/
public static final String DARK_THEME_CUSTOM_START_TIME =
"dark_theme_custom_start_time";
/**
* Custom time when Dark theme is scheduled to deactivate.
* Represented as milliseconds from midnight (e.g. 79200000 == 10pm).
* @hide
*/
public static final String DARK_THEME_CUSTOM_END_TIME =
"dark_theme_custom_end_time";
/**
* Defines value returned by {@link android.service.autofill.UserData#getMaxUserDataSize()}.
*
@@ -7705,6 +7721,14 @@ public final class Settings {
*/
public static final String UI_NIGHT_MODE = "ui_night_mode";
/**
* The current night mode that has been overrided by the system. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
* @hide
*/
public static final String UI_NIGHT_MODE_OVERRIDE = "ui_night_mode_override";
/**
* Whether screensavers are enabled.
* @hide

View File

@@ -30,6 +30,7 @@ import libcore.timezone.ZoneInfoDB;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
@@ -381,6 +382,28 @@ public class TimeUtils {
pw.print(sDumpDateFormat.format(new Date(time)));
}
/**
* This method is used to find if a clock time is inclusively between two other clock times
* @param reference The time of the day we want check if it is between start and end
* @param start The start time reference
* @param end The end time
* @return true if the reference time is between the two clock times, and false otherwise.
*/
public static boolean isTimeBetween(@NonNull LocalTime reference,
@NonNull LocalTime start,
@NonNull LocalTime end) {
// ////////E----+-----S////////
if ((reference.isBefore(start) && reference.isAfter(end)
// -----+----S//////////E------
|| (reference.isBefore(end) && reference.isBefore(start) && start.isBefore(end))
// ---------S//////////E---+---
|| (reference.isAfter(end) && reference.isAfter(start)) && start.isBefore(end))) {
return false;
} else {
return true;
}
}
/**
* Dump a currentTimeMillis style timestamp for dumpsys, with the delta time from now.
*

View File

@@ -2562,4 +2562,10 @@ enum PageId {
// CATEGORY: SETTINGS
// OS: R
OPEN_SUPPORTED_LINKS = 1824;
// OPEN: Settings > Display > Dark theme > Set start time dialog
DIALOG_DARK_THEME_SET_START_TIME = 1825;
// OPEN: Settings > Display > Dark theme > Set end time dialog
DIALOG_DARK_THEME_SET_END_TIME = 1826;
}

View File

@@ -920,6 +920,10 @@
<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: Secondary text for when the Dark theme will be enabled at some user-selected time. [CHAR LIMIT=20] -->
<string name="quick_settings_dark_mode_secondary_label_on_at">On at <xliff:g id="time" example="10 pm">%s</xliff:g></string>
<!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until some user-selected time. [CHAR LIMIT=20] -->
<string name="quick_settings_dark_mode_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
<!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_label">NFC</string>

View File

@@ -33,6 +33,8 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import javax.inject.Inject;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* Quick Settings tile for: Night Mode / Dark Theme / Dark Mode.
@@ -43,7 +45,7 @@ import javax.inject.Inject;
public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
ConfigurationController.ConfigurationListener,
BatteryController.BatteryStateChangeCallback {
public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a");
private final Icon mIcon = ResourceIcon.get(
com.android.internal.R.drawable.ic_qs_ui_mode_night);
private final UiModeManager mUiModeManager;
@@ -88,17 +90,28 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
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;
if (powerSave) {
state.secondaryLabel = mContext.getResources().getString(
R.string.quick_settings_dark_mode_secondary_label_battery_saver);
} else if (isAuto) {
} else if (uiMode == UiModeManager.MODE_NIGHT_AUTO) {
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 if (uiMode == UiModeManager.MODE_NIGHT_CUSTOM) {
final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat(mContext);
final LocalTime time;
if (nightMode) {
time = mUiModeManager.getCustomNightModeEnd();
} else {
time = mUiModeManager.getCustomNightModeStart();
}
state.secondaryLabel = mContext.getResources().getString(nightMode
? R.string.quick_settings_dark_mode_secondary_label_until
: R.string.quick_settings_dark_mode_secondary_label_on_at,
use24HourFormat ? time.toString() : formatter.format(time));
} else {
state.secondaryLabel = null;
}

View File

@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.IUiModeManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -70,10 +71,19 @@ import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
import static android.app.UiModeManager.MODE_NIGHT_YES;
import static android.util.TimeUtils.isTimeBetween;
final class UiModeManagerService extends SystemService {
private static final String TAG = UiModeManager.class.getSimpleName();
private static final boolean LOG = false;
@@ -90,7 +100,12 @@ final class UiModeManagerService extends SystemService {
// 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 final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0);
private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
private LocalTime mCustomAutoNightModeEndMilliseconds = DEFAULT_CUSTOM_NIGHT_END_TIME;
protected static final String OVERRIDE_NIGHT_MODE = Secure.UI_NIGHT_MODE_OVERRIDE;
private Map<Integer, String> mCarModePackagePriority = new HashMap<>();
private boolean mCarModeEnabled = false;
@@ -131,6 +146,8 @@ final class UiModeManagerService extends SystemService {
private NotificationManager mNotificationManager;
private StatusBarManager mStatusBarManager;
private WindowManagerInternal mWindowManager;
private AlarmManager mAlarmManager;
private PowerManager mPowerManager;
private PowerManager.WakeLock mWakeLock;
@@ -141,14 +158,16 @@ final class UiModeManagerService extends SystemService {
}
@VisibleForTesting
protected UiModeManagerService(Context context, WindowManagerInternal wm,
PowerManager.WakeLock wl, TwilightManager tm,
protected UiModeManagerService(Context context, WindowManagerInternal wm, AlarmManager am,
PowerManager pm, PowerManager.WakeLock wl, TwilightManager tm,
boolean setupWizardComplete) {
super(context);
mWindowManager = wm;
mWakeLock = wl;
mTwilightManager = tm;
mSetupWizardComplete = setupWizardComplete;
mAlarmManager = am;
mPowerManager = pm;
}
private static Intent buildHomeIntent(String category) {
@@ -237,6 +256,21 @@ final class UiModeManagerService extends SystemService {
}
};
private final BroadcastReceiver mOnTimeChangedHandler = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mLock) {
updateCustomTimeLocked();
}
}
};
private final AlarmManager.OnAlarmListener mCustomTimeListener = () -> {
synchronized (mLock) {
updateCustomTimeLocked();
}
};
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
@@ -270,8 +304,9 @@ final class UiModeManagerService extends SystemService {
public void onChange(boolean selfChange, Uri uri) {
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;
if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
mode = MODE_NIGHT_YES;
}
SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
}
};
@@ -287,10 +322,11 @@ final class UiModeManagerService extends SystemService {
public void onStart() {
final Context context = getContext();
final PowerManager powerManager =
mPowerManager =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
// 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
@@ -387,6 +423,16 @@ final class UiModeManagerService extends SystemService {
Secure.USER_SETUP_COMPLETE, 0, UserHandle.getCallingUserId()) == 1;
}
private void updateCustomTimeLocked() {
if (mNightMode != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
updateLocked(0, 0);
} else {
registerScreenOffEvent();
}
scheduleNextCustomTimeListener();
}
/**
* Updates the night mode setting in Settings.Global and returns if the value was successfully
* changed.
@@ -404,9 +450,19 @@ final class UiModeManagerService extends SystemService {
Secure.UI_NIGHT_MODE, defaultNightMode, userId);
mNightModeOverride = Secure.getIntForUser(context.getContentResolver(),
OVERRIDE_NIGHT_MODE, defaultNightMode, userId);
mCustomAutoNightModeStartMilliseconds = LocalTime.ofNanoOfDay(
Secure.getLongForUser(context.getContentResolver(),
Secure.DARK_THEME_CUSTOM_START_TIME,
DEFAULT_CUSTOM_NIGHT_START_TIME.toNanoOfDay() / 1000L, userId) * 1000);
mCustomAutoNightModeEndMilliseconds = LocalTime.ofNanoOfDay(
Secure.getLongForUser(context.getContentResolver(),
Secure.DARK_THEME_CUSTOM_END_TIME,
DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
} else {
mNightMode = defaultNightMode;
mNightModeOverride = defaultNightMode;
mCustomAutoNightModeEndMilliseconds = DEFAULT_CUSTOM_NIGHT_END_TIME;
mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
}
return oldNightMode != mNightMode;
@@ -419,6 +475,10 @@ final class UiModeManagerService extends SystemService {
getContext().registerReceiver(mOnScreenOffHandler, intentFilter);
}
private void cancelCustomAlarm() {
mAlarmManager.cancel(mCustomTimeListener);
}
private void unregisterScreenOffEvent() {
mWaitForScreenOff = false;
try {
@@ -428,6 +488,21 @@ final class UiModeManagerService extends SystemService {
}
}
private void registerTimeChangeEvent() {
final IntentFilter intentFilter =
new IntentFilter(Intent.ACTION_TIME_CHANGED);
intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
getContext().registerReceiver(mOnTimeChangedHandler, intentFilter);
}
private void unregisterTimeChangeEvent() {
try {
getContext().unregisterReceiver(mOnTimeChangedHandler);
} catch (IllegalArgumentException e) {
// we ignore this exception if the receiver is unregistered already.
}
}
private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
@Override
public void enableCarMode(@UiModeManager.EnableCarMode int flags,
@@ -537,7 +612,8 @@ final class UiModeManagerService extends SystemService {
switch (mode) {
case UiModeManager.MODE_NIGHT_NO:
case UiModeManager.MODE_NIGHT_YES:
case UiModeManager.MODE_NIGHT_AUTO:
case MODE_NIGHT_AUTO:
case MODE_NIGHT_CUSTOM:
break;
default:
throw new IllegalArgumentException("Unknown mode: " + mode);
@@ -548,8 +624,9 @@ final class UiModeManagerService extends SystemService {
try {
synchronized (mLock) {
if (mNightMode != mode) {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
unregisterScreenOffEvent();
cancelCustomAlarm();
}
mNightMode = mode;
@@ -559,7 +636,9 @@ final class UiModeManagerService extends SystemService {
persistNightMode(user);
}
// on screen off will update configuration instead
if (mNightMode != UiModeManager.MODE_NIGHT_AUTO || mCar) {
if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
|| shouldApplyAutomaticChangesImmediately()) {
unregisterScreenOffEvent();
updateLocked(0, 0);
} else {
registerScreenOffEvent();
@@ -610,7 +689,7 @@ final class UiModeManagerService extends SystemService {
final int user = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
try {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
unregisterScreenOffEvent();
mNightModeOverride = active
? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO;
@@ -630,8 +709,74 @@ final class UiModeManagerService extends SystemService {
}
}
}
@Override
public long getCustomNightModeStart() {
return mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000;
}
@Override
public void setCustomNightModeStart(long time) {
if (isNightModeLocked() && getContext().checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
!= PackageManager.PERMISSION_GRANTED) {
Slog.e(TAG, "Set custom time start, requires MODIFY_DAY_NIGHT_MODE permission");
return;
}
final int user = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
try {
LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000);
if (newTime == null) return;
mCustomAutoNightModeStartMilliseconds = newTime;
persistNightMode(user);
onCustomTimeUpdated(user);
} catch (DateTimeException e) {
unregisterScreenOffEvent();
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public long getCustomNightModeEnd() {
return mCustomAutoNightModeEndMilliseconds.toNanoOfDay() / 1000;
}
@Override
public void setCustomNightModeEnd(long time) {
if (isNightModeLocked() && getContext().checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
!= PackageManager.PERMISSION_GRANTED) {
Slog.e(TAG, "Set custom time end, requires MODIFY_DAY_NIGHT_MODE permission");
return;
}
final int user = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
try {
LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000);
if (newTime == null) return;
mCustomAutoNightModeEndMilliseconds = newTime;
onCustomTimeUpdated(user);
} catch (DateTimeException e) {
unregisterScreenOffEvent();
} finally {
Binder.restoreCallingIdentity(ident);
}
}
};
private void onCustomTimeUpdated(int user) {
persistNightMode(user);
if (mNightMode != MODE_NIGHT_CUSTOM) return;
if (shouldApplyAutomaticChangesImmediately()) {
unregisterScreenOffEvent();
updateLocked(0, 0);
} else {
registerScreenOffEvent();
}
}
void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
pw.println("Current UI Mode Service state:");
@@ -677,7 +822,6 @@ final class UiModeManagerService extends SystemService {
mTwilightManager = getLocalService(TwilightManager.class);
mSystemReady = true;
mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
updateComputedNightModeLocked();
registerVrStateListener();
updateLocked(0, 0);
}
@@ -838,6 +982,12 @@ final class UiModeManagerService extends SystemService {
Secure.UI_NIGHT_MODE, mNightMode, user);
Secure.putIntForUser(getContext().getContentResolver(),
OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
Secure.putLongForUser(getContext().getContentResolver(),
Secure.DARK_THEME_CUSTOM_START_TIME,
mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000, user);
Secure.putLongForUser(getContext().getContentResolver(),
Secure.DARK_THEME_CUSTOM_END_TIME,
mCustomAutoNightModeEndMilliseconds.toNanoOfDay() / 1000, user);
}
private void updateConfigurationLocked() {
@@ -856,13 +1006,16 @@ final class UiModeManagerService extends SystemService {
uiMode = Configuration.UI_MODE_TYPE_VR_HEADSET;
}
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
if (mNightMode == MODE_NIGHT_AUTO) {
boolean activateNightMode = mComputedNightMode;
if (mTwilightManager != null) {
mTwilightManager.registerListener(mTwilightListener, mHandler);
final TwilightState lastState = mTwilightManager.getLastTwilightState();
activateNightMode = lastState == null ? mComputedNightMode : lastState.isNight();
}
updateComputedNightModeLocked();
uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
: Configuration.UI_MODE_NIGHT_NO;
updateComputedNightModeLocked(activateNightMode);
uiMode = getComputedUiModeConfiguration(uiMode);
} else {
if (mTwilightManager != null) {
mTwilightManager.unregisterListener(mTwilightListener);
@@ -870,6 +1023,16 @@ final class UiModeManagerService extends SystemService {
uiMode |= mNightMode << 4;
}
if (mNightMode == MODE_NIGHT_CUSTOM) {
registerTimeChangeEvent();
final boolean activate = computeCustomNightMode();
updateComputedNightModeLocked(activate);
scheduleNextCustomTimeListener();
uiMode = getComputedUiModeConfiguration(uiMode);
} else {
unregisterTimeChangeEvent();
}
// Override night mode in power save mode if not in car mode
if (mPowerSave && !mCarModeEnabled) {
uiMode &= ~Configuration.UI_MODE_NIGHT_NO;
@@ -885,11 +1048,26 @@ final class UiModeManagerService extends SystemService {
}
mCurUiMode = uiMode;
if (!mHoldingConfiguration || !mWaitForScreenOff) {
if (!mHoldingConfiguration && !mWaitForScreenOff) {
mConfiguration.uiMode = uiMode;
}
}
@UiModeManager.NightMode
private int getComputedUiModeConfiguration(@UiModeManager.NightMode int uiMode) {
uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
: Configuration.UI_MODE_NIGHT_NO;
uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO
: ~Configuration.UI_MODE_NIGHT_YES;
return uiMode;
}
private boolean computeCustomNightMode() {
return isTimeBetween(LocalTime.now(),
mCustomAutoNightModeStartMilliseconds,
mCustomAutoNightModeEndMilliseconds);
}
private void applyConfigurationExternallyLocked() {
if (mSetUiMode != mConfiguration.uiMode) {
mSetUiMode = mConfiguration.uiMode;
@@ -899,10 +1077,34 @@ final class UiModeManagerService extends SystemService {
ActivityTaskManager.getService().updateConfiguration(mConfiguration);
} catch (RemoteException e) {
Slog.w(TAG, "Failure communicating with activity manager", e);
} catch (SecurityException e) {
Slog.e(TAG, "Activity does not have the ", e);
}
}
}
private boolean shouldApplyAutomaticChangesImmediately() {
return mCar || !mPowerManager.isInteractive();
}
private void scheduleNextCustomTimeListener() {
cancelCustomAlarm();
LocalDateTime now = LocalDateTime.now();
final boolean active = computeCustomNightMode();
final LocalDateTime next = active
? getDateTimeAfter(mCustomAutoNightModeEndMilliseconds, now)
: getDateTimeAfter(mCustomAutoNightModeStartMilliseconds, now);
final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, mCustomTimeListener, null);
}
private LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
final LocalDateTime ldt = LocalDateTime.of(compareTime.toLocalDate(), localTime);
// Check if the local time has passed, if so return the same time tomorrow.
return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
}
void updateLocked(int enableFlags, int disableFlags) {
String action = null;
String oldAction = null;
@@ -1133,26 +1335,21 @@ final class UiModeManagerService extends SystemService {
}
}
private void updateComputedNightModeLocked() {
if (mTwilightManager != null) {
TwilightState state = mTwilightManager.getLastTwilightState();
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);
private void updateComputedNightModeLocked(boolean activate) {
mComputedNightMode = activate;
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);
}
private void registerVrStateListener() {
@@ -1174,6 +1371,7 @@ final class UiModeManagerService extends SystemService {
public static final String NIGHT_MODE_STR_YES = "yes";
public static final String NIGHT_MODE_STR_NO = "no";
public static final String NIGHT_MODE_STR_AUTO = "auto";
public static final String NIGHT_MODE_STR_CUSTOM = "custom";
public static final String NIGHT_MODE_STR_UNKNOWN = "unknown";
private final IUiModeManager mInterface;
@@ -1246,6 +1444,8 @@ final class UiModeManagerService extends SystemService {
return NIGHT_MODE_STR_NO;
case UiModeManager.MODE_NIGHT_AUTO:
return NIGHT_MODE_STR_AUTO;
case MODE_NIGHT_CUSTOM:
return NIGHT_MODE_STR_CUSTOM;
default:
return NIGHT_MODE_STR_UNKNOWN;
}
@@ -1259,6 +1459,8 @@ final class UiModeManagerService extends SystemService {
return UiModeManager.MODE_NIGHT_NO;
case NIGHT_MODE_STR_AUTO:
return UiModeManager.MODE_NIGHT_AUTO;
case NIGHT_MODE_STR_CUSTOM:
return UiModeManager.MODE_NIGHT_CUSTOM;
default:
return -1;
}

View File

@@ -16,38 +16,54 @@
package com.android.server;
import android.app.AlarmManager;
import android.app.IUiModeManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Handler;
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.twilight.TwilightState;
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 java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
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.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -66,22 +82,51 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
TwilightManager mTwilightManager;
@Mock
PowerManager.WakeLock mWakeLock;
private Set<BroadcastReceiver> mScreenOffRecievers;
@Mock
AlarmManager mAlarmManager;
@Mock
PowerManager mPowerManager;
@Mock
TwilightState mTwilightState;
private BroadcastReceiver mScreenOffCallback;
private BroadcastReceiver mTimeChangedCallback;
private AlarmManager.OnAlarmListener mCustomListener;
@Before
public void setUp() {
mUiManagerService = new UiModeManagerService(mContext, mWindowManager, mWakeLock,
mTwilightManager, true);
mScreenOffRecievers = new HashSet<>();
initMocks(this);
mUiManagerService = new UiModeManagerService(mContext,
mWindowManager, mAlarmManager, mPowerManager,
mWakeLock, mTwilightManager, true);
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));
when(mPowerManager.isInteractive()).thenReturn(true);
when(mTwilightManager.getLastTwilightState()).thenReturn(mTwilightState);
when(mTwilightState.isNight()).thenReturn(true);
when(mContext.registerReceiver(notNull(), notNull())).then(inv -> {
IntentFilter filter = inv.getArgument(1);
if (filter.hasAction(Intent.ACTION_TIMEZONE_CHANGED)) {
mTimeChangedCallback = inv.getArgument(0);
}
if (filter.hasAction(Intent.ACTION_SCREEN_OFF)) {
mScreenOffCallback = inv.getArgument(0);
}
return null;
});
doAnswer(inv -> {
mCustomListener = inv.getArgument(3);
return null;
}).when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(),
any(AlarmManager.OnAlarmListener.class), any(Handler.class));
doAnswer(inv -> {
mCustomListener = () -> {};
return null;
}).when(mAlarmManager).cancel(eq(mCustomListener));
}
@Test
@@ -102,7 +147,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
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));
verify(mContext, atLeastOnce()).unregisterReceiver(any(BroadcastReceiver.class));
}
@Test
@@ -165,6 +210,132 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
assertFalse(isNightModeActivated());
}
@Test
public void customTime_darkThemeOn() throws RemoteException {
LocalTime now = LocalTime.now();
mService.setNightMode(MODE_NIGHT_NO);
mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
mService.setNightMode(MODE_NIGHT_CUSTOM);
mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
assertTrue(isNightModeActivated());
}
@Test
public void customTime_darkThemeOff() throws RemoteException {
LocalTime now = LocalTime.now();
mService.setNightMode(MODE_NIGHT_YES);
mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000);
mService.setNightMode(MODE_NIGHT_CUSTOM);
mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
assertFalse(isNightModeActivated());
}
@Test
public void customTime_darkThemeOff_afterStartEnd() throws RemoteException {
LocalTime now = LocalTime.now();
mService.setNightMode(MODE_NIGHT_YES);
mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000);
mService.setNightMode(MODE_NIGHT_CUSTOM);
mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
assertFalse(isNightModeActivated());
}
@Test
public void customTime_darkThemeOn_afterStartEnd() throws RemoteException {
LocalTime now = LocalTime.now();
mService.setNightMode(MODE_NIGHT_YES);
mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000);
mService.setNightMode(MODE_NIGHT_CUSTOM);
mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
assertFalse(isNightModeActivated());
}
@Test
public void customTime_darkThemeOn_beforeStartEnd() throws RemoteException {
LocalTime now = LocalTime.now();
mService.setNightMode(MODE_NIGHT_YES);
mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
mService.setCustomNightModeEnd(now.minusHours(2L).toNanoOfDay() / 1000);
mService.setNightMode(MODE_NIGHT_CUSTOM);
mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
assertTrue(isNightModeActivated());
}
@Test
public void customTime_darkThemeOff_beforeStartEnd() throws RemoteException {
LocalTime now = LocalTime.now();
mService.setNightMode(MODE_NIGHT_YES);
mService.setCustomNightModeStart(now.minusHours(2L).toNanoOfDay() / 1000);
mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000);
mService.setNightMode(MODE_NIGHT_CUSTOM);
mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
assertFalse(isNightModeActivated());
}
@Test
public void customTIme_customAlarmSetWhenScreenTimeChanges() throws RemoteException {
when(mPowerManager.isInteractive()).thenReturn(false);
mService.setNightMode(MODE_NIGHT_CUSTOM);
verify(mAlarmManager, times(1))
.setExact(anyInt(), anyLong(), anyString(), any(), any());
mTimeChangedCallback.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
verify(mAlarmManager, atLeast(2))
.setExact(anyInt(), anyLong(), anyString(), any(), any());
}
@Test
public void customTime_alarmSetInTheFutureWhenOn() throws RemoteException {
LocalDateTime now = LocalDateTime.now();
when(mPowerManager.isInteractive()).thenReturn(false);
mService.setNightMode(MODE_NIGHT_YES);
mService.setCustomNightModeStart(now.toLocalTime().minusHours(1L).toNanoOfDay() / 1000);
mService.setCustomNightModeEnd(now.toLocalTime().plusHours(1L).toNanoOfDay() / 1000);
LocalDateTime next = now.plusHours(1L);
final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
mService.setNightMode(MODE_NIGHT_CUSTOM);
verify(mAlarmManager)
.setExact(anyInt(), eq(millis), anyString(), any(), any());
}
@Test
public void customTime_appliesImmediatelyWhenScreenOff() throws RemoteException {
when(mPowerManager.isInteractive()).thenReturn(false);
LocalTime now = LocalTime.now();
mService.setNightMode(MODE_NIGHT_NO);
mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
mService.setNightMode(MODE_NIGHT_CUSTOM);
assertTrue(isNightModeActivated());
}
@Test
public void customTime_appliesOnlyWhenScreenOff() throws RemoteException {
LocalTime now = LocalTime.now();
mService.setNightMode(MODE_NIGHT_NO);
mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
mService.setNightMode(MODE_NIGHT_CUSTOM);
assertFalse(isNightModeActivated());
mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
assertTrue(isNightModeActivated());
}
@Test
public void nightAuto_appliesOnlyWhenScreenOff() throws RemoteException {
when(mTwilightState.isNight()).thenReturn(true);
mService.setNightMode(MODE_NIGHT_NO);
mService.setNightMode(MODE_NIGHT_AUTO);
assertFalse(isNightModeActivated());
mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
assertTrue(isNightModeActivated());
}
private boolean isNightModeActivated() {
return (mUiManagerService.getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_YES) != 0;