diff options
| author | 2023-01-04 00:20:54 +0000 | |
|---|---|---|
| committer | 2023-01-04 00:20:54 +0000 | |
| commit | f4c70d67ca52f714956dc3c167d383d1db2b16d8 (patch) | |
| tree | 640f7c58d227aad51825fc24f26911fef206e23e | |
| parent | 060e7b6f9656a03b79d44c6f2bda03b3b9ff4b0a (diff) | |
| parent | b638b557b80bc71ef6d1d9272d3fdc675795eca9 (diff) | |
Snap for 9451646 from b638b557b80bc71ef6d1d9272d3fdc675795eca9 to tm-qpr2-release
Change-Id: I7317b046b00a491745400be9aae4a12f3858da7d
| -rw-r--r-- | core/java/android/app/WallpaperManager.java | 13 | ||||
| -rw-r--r-- | packages/SystemUI/docs/modern-architecture.png | bin | 0 -> 23573 bytes | |||
| -rw-r--r-- | packages/SystemUI/docs/status-bar-data-pipeline.md | 261 | ||||
| -rw-r--r-- | packages/SystemUI/docs/status-bar-mobile-pipeline.png | bin | 0 -> 50983 bytes | |||
| -rw-r--r-- | packages/SystemUI/docs/status-bar-pipeline.png | bin | 0 -> 89119 bytes | |||
| -rw-r--r-- | packages/SystemUI/docs/status-bar.png | bin | 0 -> 19725 bytes | |||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java | 1 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt | 6 | ||||
| -rw-r--r-- | services/core/java/com/android/server/display/DisplayDeviceConfig.java | 3 | ||||
| -rw-r--r-- | services/core/java/com/android/server/display/DisplayModeDirector.java | 118 | ||||
| -rw-r--r-- | services/core/java/com/android/server/display/utils/SensorUtils.java | 4 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java | 58 |
12 files changed, 417 insertions, 47 deletions
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 7eacc3c3bdf1..dea1a050a27a 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -753,6 +753,19 @@ public class WallpaperManager { } /** + * 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> * Some devices lack of capability of mixed color spaces composition, diff --git a/packages/SystemUI/docs/modern-architecture.png b/packages/SystemUI/docs/modern-architecture.png Binary files differnew file mode 100644 index 000000000000..2636362dd2ec --- /dev/null +++ b/packages/SystemUI/docs/modern-architecture.png diff --git a/packages/SystemUI/docs/status-bar-data-pipeline.md b/packages/SystemUI/docs/status-bar-data-pipeline.md new file mode 100644 index 000000000000..9fcdce1ef717 --- /dev/null +++ b/packages/SystemUI/docs/status-bar-data-pipeline.md @@ -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. diff --git a/packages/SystemUI/docs/status-bar-mobile-pipeline.png b/packages/SystemUI/docs/status-bar-mobile-pipeline.png Binary files differnew file mode 100644 index 000000000000..620563de3daa --- /dev/null +++ b/packages/SystemUI/docs/status-bar-mobile-pipeline.png diff --git a/packages/SystemUI/docs/status-bar-pipeline.png b/packages/SystemUI/docs/status-bar-pipeline.png Binary files differnew file mode 100644 index 000000000000..1c568c9bcda9 --- /dev/null +++ b/packages/SystemUI/docs/status-bar-pipeline.png diff --git a/packages/SystemUI/docs/status-bar.png b/packages/SystemUI/docs/status-bar.png Binary files differnew file mode 100644 index 000000000000..3a5af0e2c3e0 --- /dev/null +++ b/packages/SystemUI/docs/status-bar.png diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 2260e35fd6bc..4bb5d04e7fc2 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -636,6 +636,7 @@ public class FrameworkServicesModule { @Provides @Singleton + @Nullable static BluetoothAdapter provideBluetoothAdapter(BluetoothManager bluetoothManager) { return bluetoothManager.getAdapter(); } diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt index 3e111e6de785..302d6a9ca1b7 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt @@ -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) { diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 5bdfa70096c0..927874373df8 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -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; diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 6331a5dd07b4..aafba5a2a1b4 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -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 = 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); - } + Sensor lightSensor = getLightSensor(); - 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"); } } diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java index 4924ad525fcc..48bc46c88df1 100644 --- a/services/core/java/com/android/server/display/utils/SensorUtils.java +++ b/services/core/java/com/android/server/display/utils/SensorUtils.java @@ -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; } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index cfea63babac3..b133a2a44fcf 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -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); } |