Add 'Stay unlocked on fold' toogle to settings
Added 'Stay unlocked on fold' toogle to display settings. Enabling this
setting will allow user to keep their device awake after the fold the
device.
Test: atest StayAwakeOnFoldPreferenceControllerTest LogicalDisplayMapperTest
Test: manual
Built and flashed foldable device and manually tested by
1. unfolded device -> enable setting -> folded device
2. unfolded device -> disabled setting -> folded devices
Built and flashed non-foldable device and manually tested by
1. Went to Settings -> display -> the toogle setting is not visible
Bug: 274447767
Change-Id: I09915082b759c235811fc143184a19e2b84f1c8e
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d425bf8..df1552b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4699,6 +4699,15 @@
public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
/**
+ * Control whether to stay awake on fold
+ *
+ * If this isn't set, the system falls back to a device specific default.
+ * @hide
+ */
+ @Readable
+ public static final String STAY_AWAKE_ON_FOLD = "stay_awake_on_fold";
+
+ /**
* The amount of time in milliseconds before the device goes to sleep or begins
* to dream after a period of inactivity. This value is also known as the
* user activity timeout period since the screen isn't necessarily turned off
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 6a5535d..6b0a906 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -61,6 +61,7 @@
Settings.System.TTY_MODE,
Settings.System.MASTER_MONO,
Settings.System.MASTER_BALANCE,
+ Settings.System.STAY_AWAKE_ON_FOLD,
Settings.System.SOUND_EFFECTS_ENABLED,
Settings.System.HAPTIC_FEEDBACK_ENABLED,
Settings.System.POWER_SOUNDS_ENABLED, // moved to global
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 753c860..a08d07e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -218,6 +218,7 @@
VALIDATORS.put(System.WIFI_STATIC_DNS1, LENIENT_IP_ADDRESS_VALIDATOR);
VALIDATORS.put(System.WIFI_STATIC_DNS2, LENIENT_IP_ADDRESS_VALIDATOR);
VALIDATORS.put(System.SHOW_BATTERY_PERCENT, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.STAY_AWAKE_ON_FOLD, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF, BOOLEAN_VALIDATOR);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 858800a..4ba0db5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -156,6 +156,7 @@
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.utils.SensorUtils;
import com.android.server.input.InputManagerInternal;
+import com.android.server.utils.FoldSettingWrapper;
import com.android.server.wm.SurfaceAnimationThread;
import com.android.server.wm.WindowManagerInternal;
@@ -539,7 +540,8 @@
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
- new LogicalDisplayListener(), mSyncRoot, mHandler);
+ new LogicalDisplayListener(), mSyncRoot, mHandler,
+ new FoldSettingWrapper(mContext.getContentResolver()));
mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
Resources resources = mContext.getResources();
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index d01b03f..26f8029 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -42,6 +42,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -142,6 +143,7 @@
private final Listener mListener;
private final DisplayManagerService.SyncRoot mSyncRoot;
private final LogicalDisplayMapperHandler mHandler;
+ private final FoldSettingWrapper mFoldSettingWrapper;
private final PowerManager mPowerManager;
/**
@@ -189,21 +191,23 @@
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler) {
+ @NonNull Handler handler, FoldSettingWrapper foldSettingWrapper) {
this(context, repo, listener, syncRoot, handler,
new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
- : sNextNonDefaultDisplayId++));
+ : sNextNonDefaultDisplayId++), foldSettingWrapper);
}
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) {
+ @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap,
+ FoldSettingWrapper foldSettingWrapper) {
mSyncRoot = syncRoot;
mPowerManager = context.getSystemService(PowerManager.class);
mInteractive = mPowerManager.isInteractive();
mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
mDisplayDeviceRepo = repo;
mListener = listener;
+ mFoldSettingWrapper = foldSettingWrapper;
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(
com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);
@@ -531,9 +535,10 @@
* Returns if the device should be put to sleep or not.
*
* Includes a check to verify that the device state that we are moving to, {@code pendingState},
- * is the same as the physical state of the device, {@code baseState}. Different values for
- * these parameters indicate a device state override is active, and we shouldn't put the device
- * to sleep to provide a better user experience.
+ * is the same as the physical state of the device, {@code baseState}. Also if the
+ * 'Stay Awake On Fold' is not enabled. Different values for these parameters indicate a device
+ * state override is active, and we shouldn't put the device to sleep to provide a better user
+ * experience.
*
* @param pendingState device state we are moving to
* @param currentState device state we are currently in
@@ -551,7 +556,7 @@
&& mDeviceStatesOnWhichToSleep.get(pendingState)
&& !mDeviceStatesOnWhichToSleep.get(currentState)
&& !isOverrideActive
- && isInteractive && isBootCompleted;
+ && isInteractive && isBootCompleted && !mFoldSettingWrapper.shouldStayAwakeOnFold();
}
private boolean areAllTransitioningDisplaysOffLocked() {
diff --git a/services/core/java/com/android/server/utils/FoldSettingWrapper.java b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
new file mode 100644
index 0000000..97a1ac0
--- /dev/null
+++ b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.utils;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+/**
+ * A wrapper class for the {@link Settings.System#STAY_AWAKE_ON_FOLD} setting.
+ *
+ * This class provides a convenient way to access the {@link Settings.System#STAY_AWAKE_ON_FOLD}
+ * setting for testing.
+ */
+public class FoldSettingWrapper {
+ private final ContentResolver mContentResolver;
+
+ public FoldSettingWrapper(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ /**
+ * Returns whether the device should remain awake after folding.
+ */
+ public boolean shouldStayAwakeOnFold() {
+ try {
+ return (Settings.System.getIntForUser(
+ mContentResolver,
+ Settings.System.STAY_AWAKE_ON_FOLD,
+ 0) == 1);
+ } catch (Settings.SettingNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 1eec70d..dc7f680 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -65,6 +65,7 @@
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
import org.junit.Before;
import org.junit.Test;
@@ -100,6 +101,7 @@
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
+ @Mock FoldSettingWrapper mFoldSettingWrapperMock;
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
@@ -139,6 +141,7 @@
when(mContextMock.getSystemServiceName(PowerManager.class))
.thenReturn(Context.POWER_SERVICE);
+ when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(false);
when(mContextMock.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
when(mContextMock.getResources()).thenReturn(mResourcesMock);
when(mResourcesMock.getBoolean(
@@ -155,7 +158,7 @@
mHandler = new Handler(mLooper.getLooper());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
- mDeviceStateToLayoutMapSpy);
+ mDeviceStateToLayoutMapSpy, mFoldSettingWrapperMock);
}
@@ -571,6 +574,17 @@
}
@Test
+ public void testDeviceShouldNotSleepWhenFoldSettingTrue() {
+ when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(true);
+
+ assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+ DEVICE_STATE_OPEN,
+ /* isOverrideActive= */false,
+ /* isInteractive= */true,
+ /* isBootCompleted= */true));
+ }
+
+ @Test
public void testDeviceShouldNotBePutToSleep() {
assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
DEVICE_STATE_CLOSED,
@@ -978,4 +992,3 @@
}
}
}
-