1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.doze; 18 19 import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT; 20 21 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP; 22 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.hardware.Sensor; 27 import android.hardware.SensorEvent; 28 import android.hardware.SensorEventListener; 29 import android.hardware.SensorManager; 30 import android.os.Handler; 31 import android.os.PowerManager; 32 import android.os.SystemProperties; 33 import android.os.Trace; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.util.IndentingPrintWriter; 37 38 import com.android.internal.R; 39 import com.android.systemui.doze.dagger.BrightnessSensor; 40 import com.android.systemui.doze.dagger.DozeScope; 41 import com.android.systemui.doze.dagger.WrappedService; 42 import com.android.systemui.keyguard.WakefulnessLifecycle; 43 import com.android.systemui.statusbar.phone.DozeParameters; 44 import com.android.systemui.statusbar.policy.DevicePostureController; 45 import com.android.systemui.util.sensors.AsyncSensorManager; 46 import com.android.systemui.util.settings.SystemSettings; 47 48 import java.io.PrintWriter; 49 import java.util.Objects; 50 import java.util.Optional; 51 52 import javax.inject.Inject; 53 54 /** 55 * Controls the screen brightness when dozing. 56 */ 57 @DozeScope 58 public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachine.Part, 59 SensorEventListener { 60 private static final boolean DEBUG_AOD_BRIGHTNESS = SystemProperties 61 .getBoolean("debug.aod_brightness", false); 62 protected static final String ACTION_AOD_BRIGHTNESS = 63 "com.android.systemui.doze.AOD_BRIGHTNESS"; 64 protected static final String BRIGHTNESS_BUCKET = "brightness_bucket"; 65 66 /** 67 * Just before the screen times out from user inactivity, DisplayPowerController dims the screen 68 * brightness to the lower of {@link #mScreenBrightnessDim}, or the current brightness minus 69 * this amount. 70 */ 71 private final float mScreenBrightnessMinimumDimAmountFloat; 72 private final Context mContext; 73 private final DozeMachine.Service mDozeService; 74 private final DozeHost mDozeHost; 75 private final Handler mHandler; 76 private final SensorManager mSensorManager; 77 private final Optional<Sensor>[] mLightSensorOptional; // light sensors to use per posture 78 private final WakefulnessLifecycle mWakefulnessLifecycle; 79 private final DozeParameters mDozeParameters; 80 private final DevicePostureController mDevicePostureController; 81 private final DozeLog mDozeLog; 82 private final SystemSettings mSystemSettings; 83 private final int[] mSensorToBrightness; 84 private final int[] mSensorToScrimOpacity; 85 private final int mScreenBrightnessDim; 86 87 @DevicePostureController.DevicePostureInt 88 private int mDevicePosture; 89 private boolean mRegistered; 90 private int mDefaultDozeBrightness; 91 private boolean mPaused = false; 92 private boolean mScreenOff = false; 93 private int mLastSensorValue = -1; 94 private DozeMachine.State mState = DozeMachine.State.UNINITIALIZED; 95 96 /** 97 * Debug value used for emulating various display brightness buckets: 98 * 99 * {@code am broadcast -p com.android.systemui -a com.android.systemui.doze.AOD_BRIGHTNESS 100 * --ei brightness_bucket 1} 101 */ 102 private int mDebugBrightnessBucket = -1; 103 104 @Inject DozeScreenBrightness( Context context, @WrappedService DozeMachine.Service service, AsyncSensorManager sensorManager, @BrightnessSensor Optional<Sensor>[] lightSensorOptional, DozeHost host, Handler handler, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, WakefulnessLifecycle wakefulnessLifecycle, DozeParameters dozeParameters, DevicePostureController devicePostureController, DozeLog dozeLog, SystemSettings systemSettings)105 public DozeScreenBrightness( 106 Context context, 107 @WrappedService DozeMachine.Service service, 108 AsyncSensorManager sensorManager, 109 @BrightnessSensor Optional<Sensor>[] lightSensorOptional, 110 DozeHost host, Handler handler, 111 AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, 112 WakefulnessLifecycle wakefulnessLifecycle, 113 DozeParameters dozeParameters, 114 DevicePostureController devicePostureController, 115 DozeLog dozeLog, 116 SystemSettings systemSettings) { 117 mContext = context; 118 mDozeService = service; 119 mSensorManager = sensorManager; 120 mLightSensorOptional = lightSensorOptional; 121 mDevicePostureController = devicePostureController; 122 mDevicePosture = mDevicePostureController.getDevicePosture(); 123 mWakefulnessLifecycle = wakefulnessLifecycle; 124 mDozeParameters = dozeParameters; 125 mDozeHost = host; 126 mHandler = handler; 127 mDozeLog = dozeLog; 128 mSystemSettings = systemSettings; 129 130 mScreenBrightnessMinimumDimAmountFloat = context.getResources().getFloat( 131 R.dimen.config_screenBrightnessMinimumDimAmountFloat); 132 133 mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness; 134 mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness; 135 mSensorToBrightness = alwaysOnDisplayPolicy.screenBrightnessArray; 136 mSensorToScrimOpacity = alwaysOnDisplayPolicy.dimmingScrimArray; 137 138 mDevicePostureController.addCallback(mDevicePostureCallback); 139 } 140 141 @Override transitionTo(DozeMachine.State oldState, DozeMachine.State newState)142 public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { 143 mState = newState; 144 switch (newState) { 145 case INITIALIZED: 146 resetBrightnessToDefault(); 147 break; 148 case DOZE_AOD: 149 case DOZE_REQUEST_PULSE: 150 case DOZE_AOD_DOCKED: 151 setLightSensorEnabled(true); 152 break; 153 case DOZE: 154 case DOZE_SUSPEND_TRIGGERS: 155 setLightSensorEnabled(false); 156 resetBrightnessToDefault(); 157 break; 158 case DOZE_AOD_PAUSED: 159 setLightSensorEnabled(false); 160 break; 161 case FINISH: 162 onDestroy(); 163 break; 164 } 165 if (newState != DozeMachine.State.FINISH) { 166 setScreenOff(newState == DozeMachine.State.DOZE); 167 setPaused(newState == DozeMachine.State.DOZE_AOD_PAUSED); 168 } 169 } 170 onDestroy()171 private void onDestroy() { 172 setLightSensorEnabled(false); 173 mDevicePostureController.removeCallback(mDevicePostureCallback); 174 } 175 176 @Override onSensorChanged(SensorEvent event)177 public void onSensorChanged(SensorEvent event) { 178 if (Trace.isEnabled()) { 179 Trace.traceBegin( 180 Trace.TRACE_TAG_APP, "DozeScreenBrightness.onSensorChanged" + event.values[0]); 181 } 182 try { 183 if (mRegistered) { 184 mLastSensorValue = (int) event.values[0]; 185 updateBrightnessAndReady(false /* force */); 186 } 187 } finally { 188 Trace.endSection(); 189 } 190 } 191 updateBrightnessAndReady(boolean force)192 public void updateBrightnessAndReady(boolean force) { 193 if (force || mRegistered || mDebugBrightnessBucket != -1) { 194 int sensorValue = mDebugBrightnessBucket == -1 195 ? mLastSensorValue : mDebugBrightnessBucket; 196 int brightness = computeBrightness(sensorValue); 197 boolean brightnessReady = brightness > 0; 198 if (brightnessReady) { 199 mDozeService.setDozeScreenBrightness( 200 clampToDimBrightnessForScreenOff(clampToUserSetting(brightness))); 201 } 202 203 int scrimOpacity = -1; 204 if (!isLightSensorPresent()) { 205 // No light sensor, scrims are always transparent. 206 scrimOpacity = 0; 207 } else if (brightnessReady) { 208 // Only unblank scrim once brightness is ready. 209 scrimOpacity = computeScrimOpacity(sensorValue); 210 } 211 if (scrimOpacity >= 0) { 212 mDozeHost.setAodDimmingScrim(scrimOpacity / 255f); 213 } 214 } 215 } 216 lightSensorSupportsCurrentPosture()217 private boolean lightSensorSupportsCurrentPosture() { 218 return mLightSensorOptional != null 219 && mDevicePosture < mLightSensorOptional.length; 220 } 221 isLightSensorPresent()222 private boolean isLightSensorPresent() { 223 if (!lightSensorSupportsCurrentPosture()) { 224 return mLightSensorOptional != null && mLightSensorOptional[0].isPresent(); 225 } 226 227 return mLightSensorOptional[mDevicePosture].isPresent(); 228 } 229 getLightSensor()230 private Sensor getLightSensor() { 231 if (!lightSensorSupportsCurrentPosture()) { 232 return null; 233 } 234 235 return mLightSensorOptional[mDevicePosture].get(); 236 } 237 computeScrimOpacity(int sensorValue)238 private int computeScrimOpacity(int sensorValue) { 239 if (sensorValue < 0 || sensorValue >= mSensorToScrimOpacity.length) { 240 return -1; 241 } 242 return mSensorToScrimOpacity[sensorValue]; 243 } 244 computeBrightness(int sensorValue)245 private int computeBrightness(int sensorValue) { 246 if (sensorValue < 0 || sensorValue >= mSensorToBrightness.length) { 247 return -1; 248 } 249 return mSensorToBrightness[sensorValue]; 250 } 251 252 @Override onAccuracyChanged(Sensor sensor, int accuracy)253 public void onAccuracyChanged(Sensor sensor, int accuracy) { 254 } 255 resetBrightnessToDefault()256 private void resetBrightnessToDefault() { 257 mDozeService.setDozeScreenBrightness( 258 clampToDimBrightnessForScreenOff( 259 clampToUserSetting(mDefaultDozeBrightness))); 260 mDozeHost.setAodDimmingScrim(0f); 261 } 262 //TODO: brightnessfloat change usages to float. clampToUserSetting(int brightness)263 private int clampToUserSetting(int brightness) { 264 int userSetting = mSystemSettings.getIntForUser( 265 Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE, 266 UserHandle.USER_CURRENT); 267 return Math.min(brightness, userSetting); 268 } 269 270 /** 271 * Clamp the brightness to the dim brightness value used by PowerManagerService just before the 272 * device times out and goes to sleep, if we are sleeping from a timeout. This ensures that we 273 * don't raise the brightness back to the user setting before or during the screen off 274 * animation. 275 */ clampToDimBrightnessForScreenOff(int brightness)276 private int clampToDimBrightnessForScreenOff(int brightness) { 277 final boolean screenTurningOff = 278 (mDozeParameters.shouldClampToDimBrightness() 279 || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP) 280 && mState == DozeMachine.State.INITIALIZED; 281 if (screenTurningOff 282 && mWakefulnessLifecycle.getLastSleepReason() == GO_TO_SLEEP_REASON_TIMEOUT) { 283 return Math.max( 284 PowerManager.BRIGHTNESS_OFF, 285 // Use the lower of either the dim brightness, or the current brightness reduced 286 // by the minimum dim amount. This is the same logic used in 287 // DisplayPowerController#updatePowerState to apply a minimum dim amount. 288 Math.min( 289 brightness - (int) Math.floor( 290 mScreenBrightnessMinimumDimAmountFloat * 255), 291 mScreenBrightnessDim)); 292 } else { 293 return brightness; 294 } 295 } 296 setLightSensorEnabled(boolean enabled)297 private void setLightSensorEnabled(boolean enabled) { 298 if (enabled && !mRegistered && isLightSensorPresent()) { 299 // Wait until we get an event from the sensor until indicating ready. 300 mRegistered = mSensorManager.registerListener(this, getLightSensor(), 301 SensorManager.SENSOR_DELAY_NORMAL, mHandler); 302 mLastSensorValue = -1; 303 } else if (!enabled && mRegistered) { 304 mSensorManager.unregisterListener(this); 305 mRegistered = false; 306 mLastSensorValue = -1; 307 // Sensor is not enabled, hence we use the default brightness and are always ready. 308 } 309 } 310 setPaused(boolean paused)311 private void setPaused(boolean paused) { 312 if (mPaused != paused) { 313 mPaused = paused; 314 updateBrightnessAndReady(false /* force */); 315 } 316 } 317 setScreenOff(boolean screenOff)318 private void setScreenOff(boolean screenOff) { 319 if (mScreenOff != screenOff) { 320 mScreenOff = screenOff; 321 updateBrightnessAndReady(true /* force */); 322 } 323 } 324 325 @Override onReceive(Context context, Intent intent)326 public void onReceive(Context context, Intent intent) { 327 mDebugBrightnessBucket = intent.getIntExtra(BRIGHTNESS_BUCKET, -1); 328 updateBrightnessAndReady(false /* force */); 329 } 330 331 /** Dump current state */ dump(PrintWriter pw)332 public void dump(PrintWriter pw) { 333 pw.println("DozeScreenBrightness:"); 334 IndentingPrintWriter idpw = new IndentingPrintWriter(pw); 335 idpw.increaseIndent(); 336 idpw.println("registered=" + mRegistered); 337 idpw.println("posture=" + DevicePostureController.devicePostureToString(mDevicePosture)); 338 } 339 340 private final DevicePostureController.Callback mDevicePostureCallback = 341 new DevicePostureController.Callback() { 342 @Override 343 public void onPostureChanged(int posture) { 344 if (mDevicePosture == posture 345 || mLightSensorOptional.length < 2 346 || posture >= mLightSensorOptional.length) { 347 return; 348 } 349 350 final Sensor oldSensor = mLightSensorOptional[mDevicePosture].get(); 351 final Sensor newSensor = mLightSensorOptional[posture].get(); 352 if (Objects.equals(oldSensor, newSensor)) { 353 mDevicePosture = posture; 354 // uses the same sensor for the new posture 355 return; 356 } 357 358 // cancel the previous sensor: 359 if (mRegistered) { 360 setLightSensorEnabled(false); 361 mDevicePosture = posture; 362 setLightSensorEnabled(true); 363 } else { 364 mDevicePosture = posture; 365 } 366 mDozeLog.tracePostureChanged(mDevicePosture, "DozeScreenBrightness swap " 367 + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered); 368 } 369 }; 370 } 371