Snap for 9451646 from b638b557b8 to tm-qpr2-release
Change-Id: I7317b046b00a491745400be9aae4a12f3858da7d
This commit is contained in:
@@ -752,6 +752,19 @@ public class WallpaperManager {
|
||||
return sGlobals.mService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary method for project b/197814683
|
||||
* Starting from U, this will return true if the new wallpaper logic is enabled,
|
||||
* i.e. if the lockscreen wallpaper always uses a wallpaperService and not a static image.
|
||||
* In T, this is just a stub method that always return false.
|
||||
*
|
||||
* @return false
|
||||
* @hide
|
||||
*/
|
||||
public boolean isLockscreenLiveWallpaperEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether wcg (Wide Color Gamut) should be enabled.
|
||||
* <p>
|
||||
|
||||
BIN
packages/SystemUI/docs/modern-architecture.png
Normal file
BIN
packages/SystemUI/docs/modern-architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
261
packages/SystemUI/docs/status-bar-data-pipeline.md
Normal file
261
packages/SystemUI/docs/status-bar-data-pipeline.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# Status Bar Data Pipeline
|
||||
|
||||
## Background
|
||||
|
||||
The status bar is the UI shown at the top of the user's screen that gives them
|
||||
information about the time, notifications, and system status like mobile
|
||||
conectivity and battery level. This document is about the implementation of the
|
||||
wifi and mobile system icons on the right side:
|
||||
|
||||

|
||||
|
||||
In Android U, the data pipeline that determines what mobile and wifi icons to
|
||||
show in the status bar has been re-written with a new architecture. This format
|
||||
generally follows Android best practices to
|
||||
[app architecture](https://developer.android.com/topic/architecture#recommended-app-arch).
|
||||
This document serves as a guide for the new architecture, and as a guide for how
|
||||
OEMs can add customizations to the new architecture.
|
||||
|
||||
## Architecture
|
||||
|
||||
In the new architecture, there is a separate pipeline for each type of icon. For
|
||||
Android U, **only the wifi icon and mobile icons have been implemented in the
|
||||
new architecture**.
|
||||
|
||||
As shown in the Android best practices guide, each new pipeline has a data
|
||||
layer, a domain layer, and a UI layer:
|
||||
|
||||

|
||||
|
||||
The classes in the data layer are `repository` instances. The classes in the
|
||||
domain layer are `interactor` instances. The classes in the UI layer are
|
||||
`viewmodel` instances and `viewbinder` instances. In this document, "repository"
|
||||
and "data layer" will be used interchangably (and the same goes for the other
|
||||
layers).
|
||||
|
||||
The wifi logic is in `statusbar/pipeline/wifi` and the mobile logic is in
|
||||
`statusbar/pipeline/mobile`.
|
||||
|
||||
#### Repository (data layer)
|
||||
|
||||
System callbacks, broadcast receivers, configuration values are all defined
|
||||
here, and exposed through the appropriate interface. Where appropriate, we
|
||||
define `Model` objects at this layer so that clients do not have to rely on
|
||||
system-defined interfaces.
|
||||
|
||||
#### Interactor (domain layer)
|
||||
|
||||
Here is where we define the business logic and transform the data layer objects
|
||||
into something consumable by the ViewModel classes. For example,
|
||||
`MobileIconsInteractor` defines the CBRS filtering logic by exposing a
|
||||
`filteredSubscriptions` list.
|
||||
|
||||
#### ViewModel (UI layer)
|
||||
|
||||
View models should define the final piece of business logic mapping to UI logic.
|
||||
For example, the mobile view model checks the `IconInteractor.isRoaming` flow to
|
||||
decide whether or not to show the roaming indicator.
|
||||
|
||||
#### ViewBinder
|
||||
|
||||
These have already been implemented and configured. ViewBinders replace the old
|
||||
`applyMobileState` mechanism that existed in the `IconManager` classes of the
|
||||
old pipeline. A view binder associates a ViewModel with a View, and keeps the
|
||||
view up-to-date with the most recent information from the model.
|
||||
|
||||
Any new fields added to the ViewModel classes need to be equivalently bound to
|
||||
the view here.
|
||||
|
||||
### Putting it all together
|
||||
|
||||
Putting that altogether, we have this overall architecture diagram for the
|
||||
icons:
|
||||
|
||||

|
||||
|
||||
### Mobile icons architecture
|
||||
|
||||
Because there can be multiple mobile connections at the same time, the mobile
|
||||
pipeline is split up hierarchically. At each level (data, domain, and UI), there
|
||||
is a singleton parent class that manages information relevant to **all** mobile
|
||||
connections, and multiple instances of child classes that manage information for
|
||||
a **single** mobile connection.
|
||||
|
||||
For example, `MobileConnectionsRepository` is a singleton at the data layer that
|
||||
stores information relevant to **all** mobile connections, and it also manages a
|
||||
list of child `MobileConnectionRepository` classes. `MobileConnectionRepository`
|
||||
is **not** a singleton, and each individual `MobileConnectionRepository`
|
||||
instance fully qualifies the state of a **single** connection. This pattern is
|
||||
repeated at the `Interactor` and `ViewModel` layers for mobile.
|
||||
|
||||

|
||||
|
||||
Note: Since there is at most one wifi connection, the wifi pipeline is not split
|
||||
up in the same way.
|
||||
|
||||
## Customizations
|
||||
|
||||
The new pipeline completely replaces these classes:
|
||||
|
||||
* `WifiStatusTracker`
|
||||
* `MobileStatusTracker`
|
||||
* `NetworkSignalController` and `NetworkSignalControllerImpl`
|
||||
* `MobileSignalController`
|
||||
* `WifiSignalController`
|
||||
* `StatusBarSignalPolicy` (including `SignalIconState`, `MobileIconState`, and
|
||||
`WifiIconState`)
|
||||
|
||||
Any customizations in any of these classes will need to be migrated to the new
|
||||
pipeline. As a general rule, any change that would have gone into
|
||||
`NetworkControllerImpl` would be done in `MobileConnectionsRepository`, and any
|
||||
change for `MobileSignalController` can be done in `MobileConnectionRepository`
|
||||
(see above on the relationship between those repositories).
|
||||
|
||||
### Sample customization: New service
|
||||
|
||||
Some customizations require listening to additional services to get additional
|
||||
data. This new architecture makes it easy to add additional services to the
|
||||
status bar data pipeline to get icon customizations.
|
||||
|
||||
Below is a general guide to how a new service should be added. However, there
|
||||
may be exceptions to this guide for specific use cases.
|
||||
|
||||
1. In the data layer (`repository` classes), add a new `StateFlow` that listens
|
||||
to the service:
|
||||
|
||||
```kotlin
|
||||
class MobileConnectionsRepositoryImpl {
|
||||
...
|
||||
val fooVal: StateFlow<Int> =
|
||||
conflatedCallbackFlow {
|
||||
val callback = object : FooServiceCallback(), FooListener {
|
||||
override fun onFooChanged(foo: Int) {
|
||||
trySend(foo)
|
||||
}
|
||||
}
|
||||
|
||||
fooService.registerCallback(callback)
|
||||
|
||||
awaitClose { fooService.unregisterCallback(callback) }
|
||||
}
|
||||
.stateIn(scope, started = SharingStarted.WhileSubscribed(), FOO_DEFAULT_VAL)
|
||||
}
|
||||
```
|
||||
|
||||
1. In the domain layer (`interactor` classes), either use this new flow to
|
||||
process values, or just expose the flow as-is for the UI layer.
|
||||
|
||||
For example, if `bar` should only be true when `foo` is positive:
|
||||
|
||||
```kotlin
|
||||
class MobileIconsInteractor {
|
||||
...
|
||||
val bar: StateFlow<Boolean> =
|
||||
mobileConnectionsRepo
|
||||
.mapLatest { foo -> foo > 0 }
|
||||
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = false)
|
||||
}
|
||||
```
|
||||
|
||||
1. In the UI layer (`viewmodel` classes), update the existing flows to process
|
||||
the new value from the interactor.
|
||||
|
||||
For example, if the icon should be hidden when `bar` is true:
|
||||
|
||||
```kotlin
|
||||
class MobileIconViewModel {
|
||||
...
|
||||
iconId: Flow<Int> = combine(
|
||||
iconInteractor.level,
|
||||
iconInteractor.numberOfLevels,
|
||||
iconInteractor.bar,
|
||||
) { level, numberOfLevels, bar ->
|
||||
if (bar) {
|
||||
null
|
||||
} else {
|
||||
calcIcon(level, numberOfLevels)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Demo mode
|
||||
|
||||
SystemUI demo mode is a first-class citizen in the new pipeline. It is
|
||||
implemented as an entirely separate repository,
|
||||
`DemoMobileConnectionsRepository`. When the system moves into demo mode, the
|
||||
implementation of the data layer is switched to the demo repository via the
|
||||
`MobileRepositorySwitcher` class.
|
||||
|
||||
Because the demo mode repositories implement the same interfaces as the
|
||||
production classes, any changes made above will have to be implemented for demo
|
||||
mode as well.
|
||||
|
||||
1. Following from above, if `fooVal` is added to the
|
||||
`MobileConnectionsRepository` interface:
|
||||
|
||||
```kotlin
|
||||
class DemoMobileConnectionsRepository {
|
||||
private val _fooVal = MutableStateFlow(FOO_DEFAULT_VALUE)
|
||||
override val fooVal: StateFlow<Int> = _fooVal.asStateFlow()
|
||||
|
||||
// Process the state. **See below on how to add the command to the CLI**
|
||||
fun processEnabledMobileState(state: Mobile) {
|
||||
...
|
||||
_fooVal.value = state.fooVal
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. (Optional) If you want to enable the command line interface for setting and
|
||||
testing this value in demo mode, you can add parsing logic to
|
||||
`DemoModeMobileConnectionDataSource` and `FakeNetworkEventModel`:
|
||||
|
||||
```kotlin
|
||||
sealed interface FakeNetworkEventModel {
|
||||
data class Mobile(
|
||||
...
|
||||
// Add new fields here
|
||||
val fooVal: Int?
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```kotlin
|
||||
class DemoModeMobileConnectionDataSource {
|
||||
// Currently, the demo commands are implemented as an extension function on Bundle
|
||||
private fun Bundle.activeMobileEvent(): Mobile {
|
||||
...
|
||||
val fooVal = getString("fooVal")?.toInt()
|
||||
return Mobile(
|
||||
...
|
||||
fooVal = fooVal,
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If step 2 is implemented, then you will be able to pass demo commands via the
|
||||
command line:
|
||||
|
||||
```
|
||||
adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e fooVal <test value>
|
||||
```
|
||||
|
||||
## Migration plan
|
||||
|
||||
For Android U, the new pipeline will be enabled and default. However, the old
|
||||
pipeline code will still be around just in case the new pipeline doesn’t do well
|
||||
in the testing phase.
|
||||
|
||||
For Android V, the old pipeline will be completely removed and the new pipeline
|
||||
will be the one source of truth.
|
||||
|
||||
Our ask for OEMs is to default to using the new pipeline in Android U. If there
|
||||
are customizations that seem difficult to migrate over to the new pipeline,
|
||||
please file a bug with us and we’d be more than happy to consult on the best
|
||||
solution. The new pipeline was designed with customizability in mind, so our
|
||||
hope is that working the new pipeline will be easier and faster.
|
||||
|
||||
Note: The new pipeline currently only supports the wifi and mobile icons. The
|
||||
other system status bar icons may be migrated to a similar architecture in the
|
||||
future.
|
||||
BIN
packages/SystemUI/docs/status-bar-mobile-pipeline.png
Normal file
BIN
packages/SystemUI/docs/status-bar-mobile-pipeline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
packages/SystemUI/docs/status-bar-pipeline.png
Normal file
BIN
packages/SystemUI/docs/status-bar-pipeline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
BIN
packages/SystemUI/docs/status-bar.png
Normal file
BIN
packages/SystemUI/docs/status-bar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -636,6 +636,7 @@ public class FrameworkServicesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Nullable
|
||||
static BluetoothAdapter provideBluetoothAdapter(BluetoothManager bluetoothManager) {
|
||||
return bluetoothManager.getAdapter();
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class StylusManager
|
||||
@Inject
|
||||
constructor(
|
||||
private val inputManager: InputManager,
|
||||
private val bluetoothAdapter: BluetoothAdapter,
|
||||
private val bluetoothAdapter: BluetoothAdapter?,
|
||||
@Background private val handler: Handler,
|
||||
@Background private val executor: Executor,
|
||||
) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener {
|
||||
@@ -141,7 +141,7 @@ constructor(
|
||||
}
|
||||
|
||||
private fun onStylusBluetoothConnected(btAddress: String) {
|
||||
val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
|
||||
val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
|
||||
try {
|
||||
bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
@@ -150,7 +150,7 @@ constructor(
|
||||
}
|
||||
|
||||
private fun onStylusBluetoothDisconnected(btAddress: String) {
|
||||
val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
|
||||
val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
|
||||
try {
|
||||
bluetoothAdapter.removeOnMetadataChangedListener(device, this)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
||||
@@ -2644,6 +2644,9 @@ public class DisplayDeviceConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniquely identifies a Sensor, with the combination of Type and Name.
|
||||
*/
|
||||
static class SensorData {
|
||||
public String type;
|
||||
public String name;
|
||||
|
||||
@@ -50,7 +50,6 @@ import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.DeviceConfigInterface;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
@@ -68,6 +67,7 @@ import com.android.internal.os.BackgroundThread;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.display.utils.AmbientFilter;
|
||||
import com.android.server.display.utils.AmbientFilterFactory;
|
||||
import com.android.server.display.utils.SensorUtils;
|
||||
import com.android.server.sensors.SensorManagerInternal;
|
||||
import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
|
||||
import com.android.server.statusbar.StatusBarManagerInternal;
|
||||
@@ -520,14 +520,20 @@ public class DisplayModeDirector {
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility to make this class aware of the new display configs whenever the default display is
|
||||
* changed
|
||||
* Called when the underlying display device of the default display is changed.
|
||||
* Some data in this class relates to the physical display of the device, and so we need to
|
||||
* reload the configurations based on this.
|
||||
* E.g. the brightness sensors and refresh rate capabilities depend on the physical display
|
||||
* device that is being used, so will be reloaded.
|
||||
*
|
||||
* @param displayDeviceConfig configurations relating to the underlying display device.
|
||||
*/
|
||||
public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
|
||||
mSettingsObserver.setRefreshRates(displayDeviceConfig,
|
||||
/* attemptLoadingFromDeviceConfig= */ true);
|
||||
mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
|
||||
/* attemptLoadingFromDeviceConfig= */ true);
|
||||
mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1541,6 +1547,9 @@ public class DisplayModeDirector {
|
||||
|
||||
private SensorManager mSensorManager;
|
||||
private Sensor mLightSensor;
|
||||
private Sensor mRegisteredLightSensor;
|
||||
private String mLightSensorType;
|
||||
private String mLightSensorName;
|
||||
private final LightSensorEventListener mLightSensorListener =
|
||||
new LightSensorEventListener();
|
||||
// Take it as low brightness before valid sensor data comes
|
||||
@@ -1701,17 +1710,8 @@ public class DisplayModeDirector {
|
||||
return mLowAmbientBrightnessThresholds;
|
||||
}
|
||||
|
||||
public void registerLightSensor(SensorManager sensorManager, Sensor lightSensor) {
|
||||
mSensorManager = sensorManager;
|
||||
mLightSensor = lightSensor;
|
||||
|
||||
mSensorManager.registerListener(mLightSensorListener,
|
||||
mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
|
||||
}
|
||||
|
||||
public void observe(SensorManager sensorManager) {
|
||||
mSensorManager = sensorManager;
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
|
||||
|
||||
// DeviceConfig is accessible after system ready.
|
||||
@@ -1855,6 +1855,10 @@ public class DisplayModeDirector {
|
||||
pw.println(" mAmbientHighBrightnessThresholds: " + d);
|
||||
}
|
||||
|
||||
pw.println(" mLightSensor: " + mLightSensor);
|
||||
pw.println(" mRegisteredLightSensor: " + mRegisteredLightSensor);
|
||||
pw.println(" mLightSensorName: " + mLightSensorName);
|
||||
pw.println(" mLightSensorType: " + mLightSensorType);
|
||||
mLightSensorListener.dumpLocked(pw);
|
||||
|
||||
if (mAmbientFilter != null) {
|
||||
@@ -1908,27 +1912,9 @@ public class DisplayModeDirector {
|
||||
}
|
||||
|
||||
if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
|
||||
Resources resources = mContext.getResources();
|
||||
String lightSensorType = resources.getString(
|
||||
com.android.internal.R.string.config_displayLightSensorType);
|
||||
Sensor lightSensor = getLightSensor();
|
||||
|
||||
Sensor lightSensor = null;
|
||||
if (!TextUtils.isEmpty(lightSensorType)) {
|
||||
List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
|
||||
for (int i = 0; i < sensors.size(); i++) {
|
||||
Sensor sensor = sensors.get(i);
|
||||
if (lightSensorType.equals(sensor.getStringType())) {
|
||||
lightSensor = sensor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lightSensor == null) {
|
||||
lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
|
||||
}
|
||||
|
||||
if (lightSensor != null) {
|
||||
if (lightSensor != null && lightSensor != mLightSensor) {
|
||||
final Resources res = mContext.getResources();
|
||||
|
||||
mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res);
|
||||
@@ -1938,15 +1924,40 @@ public class DisplayModeDirector {
|
||||
mAmbientFilter = null;
|
||||
mLightSensor = null;
|
||||
}
|
||||
|
||||
updateSensorStatus();
|
||||
if (mRefreshRateChangeable) {
|
||||
updateSensorStatus();
|
||||
synchronized (mLock) {
|
||||
onBrightnessChangedLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadLightSensor(DisplayDeviceConfig displayDeviceConfig) {
|
||||
reloadLightSensorData(displayDeviceConfig);
|
||||
restartObserver();
|
||||
}
|
||||
|
||||
private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) {
|
||||
// The displayDeviceConfig (ddc) contains display specific preferences. When loaded,
|
||||
// it naturally falls back to the global config.xml.
|
||||
if (displayDeviceConfig != null
|
||||
&& displayDeviceConfig.getAmbientLightSensor() != null) {
|
||||
// This covers both the ddc and the config.xml fallback
|
||||
mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type;
|
||||
mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name;
|
||||
} else if (mLightSensorName == null && mLightSensorType == null) {
|
||||
Resources resources = mContext.getResources();
|
||||
mLightSensorType = resources.getString(
|
||||
com.android.internal.R.string.config_displayLightSensorType);
|
||||
mLightSensorName = "";
|
||||
}
|
||||
}
|
||||
|
||||
private Sensor getLightSensor() {
|
||||
return SensorUtils.findSensor(mSensorManager, mLightSensorType,
|
||||
mLightSensorName, Sensor.TYPE_LIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if at least one value is positive, in which case it is necessary to listen
|
||||
* to value changes.
|
||||
@@ -2088,17 +2099,36 @@ public class DisplayModeDirector {
|
||||
|
||||
if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
|
||||
&& isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
|
||||
mSensorManager.registerListener(mLightSensorListener,
|
||||
mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
|
||||
if (mLoggingEnabled) {
|
||||
Slog.d(TAG, "updateSensorStatus: registerListener");
|
||||
}
|
||||
registerLightSensor();
|
||||
|
||||
} else {
|
||||
mLightSensorListener.removeCallbacks();
|
||||
mSensorManager.unregisterListener(mLightSensorListener);
|
||||
if (mLoggingEnabled) {
|
||||
Slog.d(TAG, "updateSensorStatus: unregisterListener");
|
||||
}
|
||||
unregisterSensorListener();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerLightSensor() {
|
||||
if (mRegisteredLightSensor == mLightSensor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRegisteredLightSensor != null) {
|
||||
unregisterSensorListener();
|
||||
}
|
||||
|
||||
mSensorManager.registerListener(mLightSensorListener,
|
||||
mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
|
||||
mRegisteredLightSensor = mLightSensor;
|
||||
if (mLoggingEnabled) {
|
||||
Slog.d(TAG, "updateSensorStatus: registerListener");
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterSensorListener() {
|
||||
mLightSensorListener.removeCallbacks();
|
||||
mSensorManager.unregisterListener(mLightSensorListener);
|
||||
mRegisteredLightSensor = null;
|
||||
if (mLoggingEnabled) {
|
||||
Slog.d(TAG, "updateSensorStatus: unregisterListener");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ public class SensorUtils {
|
||||
*/
|
||||
public static Sensor findSensor(SensorManager sensorManager, String sensorType,
|
||||
String sensorName, int fallbackType) {
|
||||
if (sensorManager == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ("".equals(sensorName) && "".equals(sensorType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@@ -74,6 +75,7 @@ import android.provider.Settings;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Display;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -102,6 +104,7 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -1939,6 +1942,61 @@ public class DisplayModeDirectorTest {
|
||||
new int[]{20});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSensorReloadOnDeviceSwitch() throws Exception {
|
||||
// First, configure brightness zones or DMD won't register for sensor data.
|
||||
final FakeDeviceConfig config = mInjector.getDeviceConfig();
|
||||
config.setRefreshRateInHighZone(60);
|
||||
config.setHighDisplayBrightnessThresholds(new int[] { 255 });
|
||||
config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
|
||||
|
||||
DisplayModeDirector director =
|
||||
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
|
||||
setPeakRefreshRate(90 /*fps*/);
|
||||
director.getSettingsObserver().setDefaultRefreshRate(90);
|
||||
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
|
||||
|
||||
Sensor lightSensorOne = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
|
||||
Sensor lightSensorTwo = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
|
||||
SensorManager sensorManager = createMockSensorManager(lightSensorOne, lightSensorTwo);
|
||||
when(sensorManager.getDefaultSensor(5)).thenReturn(lightSensorOne, lightSensorTwo);
|
||||
director.start(sensorManager);
|
||||
ArgumentCaptor<SensorEventListener> listenerCaptor =
|
||||
ArgumentCaptor.forClass(SensorEventListener.class);
|
||||
verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
|
||||
.registerListener(
|
||||
listenerCaptor.capture(),
|
||||
eq(lightSensorOne),
|
||||
anyInt(),
|
||||
any(Handler.class));
|
||||
|
||||
DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
|
||||
when(ddcMock.getDefaultLowRefreshRate()).thenReturn(50);
|
||||
when(ddcMock.getDefaultHighRefreshRate()).thenReturn(55);
|
||||
when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
|
||||
when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
|
||||
when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
|
||||
when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
|
||||
|
||||
Resources resMock = mock(Resources.class);
|
||||
when(resMock.getInteger(
|
||||
com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
|
||||
.thenReturn(3);
|
||||
ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
|
||||
doAnswer((Answer<Void>) invocation -> {
|
||||
valueArgumentCaptor.getValue().type = 4;
|
||||
valueArgumentCaptor.getValue().data = 13;
|
||||
return null;
|
||||
}).when(resMock).getValue(anyInt(), valueArgumentCaptor.capture(), eq(true));
|
||||
when(mContext.getResources()).thenReturn(resMock);
|
||||
|
||||
director.defaultDisplayDeviceUpdated(ddcMock);
|
||||
|
||||
verify(sensorManager).unregisterListener(any(SensorEventListener.class));
|
||||
verify(sensorManager).registerListener(any(SensorEventListener.class),
|
||||
eq(lightSensorTwo), anyInt(), any(Handler.class));
|
||||
}
|
||||
|
||||
private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
|
||||
return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user