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.phone.UnlockedScreenOffAnimationController; 45 import com.android.systemui.statusbar.policy.DevicePostureController; 46 import com.android.systemui.util.sensors.AsyncSensorManager; 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 int[] mSensorToBrightness; 83 private final int[] mSensorToScrimOpacity; 84 private final int mScreenBrightnessDim; 85 86 @DevicePostureController.DevicePostureInt 87 private int mDevicePosture; 88 private boolean mRegistered; 89 private int mDefaultDozeBrightness; 90 private boolean mPaused = false; 91 private boolean mScreenOff = false; 92 private int mLastSensorValue = -1; 93 94 /** 95 * Debug value used for emulating various display brightness buckets: 96 * 97 * {@code am broadcast -p com.android.systemui -a com.android.systemui.doze.AOD_BRIGHTNESS 98 * --ei brightness_bucket 1} 99 */ 100 private int mDebugBrightnessBucket = -1; 101 102 private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; 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, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController)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 UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { 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 mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; 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 switch (newState) { 144 case INITIALIZED: 145 resetBrightnessToDefault(); 146 break; 147 case DOZE_AOD: 148 case DOZE_REQUEST_PULSE: 149 case DOZE_AOD_DOCKED: 150 setLightSensorEnabled(true); 151 break; 152 case DOZE: 153 setLightSensorEnabled(false); 154 resetBrightnessToDefault(); 155 break; 156 case FINISH: 157 onDestroy(); 158 break; 159 } 160 if (newState != DozeMachine.State.FINISH) { 161 setScreenOff(newState == DozeMachine.State.DOZE); 162 setPaused(newState == DozeMachine.State.DOZE_AOD_PAUSED); 163 } 164 } 165 onDestroy()166 private void onDestroy() { 167 setLightSensorEnabled(false); 168 mDevicePostureController.removeCallback(mDevicePostureCallback); 169 } 170 171 @Override onSensorChanged(SensorEvent event)172 public void onSensorChanged(SensorEvent event) { 173 Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]); 174 try { 175 if (mRegistered) { 176 mLastSensorValue = (int) event.values[0]; 177 updateBrightnessAndReady(false /* force */); 178 } 179 } finally { 180 Trace.endSection(); 181 } 182 } 183 updateBrightnessAndReady(boolean force)184 public void updateBrightnessAndReady(boolean force) { 185 if (force || mRegistered || mDebugBrightnessBucket != -1) { 186 int sensorValue = mDebugBrightnessBucket == -1 187 ? mLastSensorValue : mDebugBrightnessBucket; 188 int brightness = computeBrightness(sensorValue); 189 boolean brightnessReady = brightness > 0; 190 if (brightnessReady) { 191 mDozeService.setDozeScreenBrightness( 192 clampToDimBrightnessForScreenOff(clampToUserSetting(brightness))); 193 } 194 195 int scrimOpacity = -1; 196 if (!isLightSensorPresent()) { 197 // No light sensor, scrims are always transparent. 198 scrimOpacity = 0; 199 } else if (brightnessReady) { 200 // Only unblank scrim once brightness is ready. 201 scrimOpacity = computeScrimOpacity(sensorValue); 202 } 203 if (scrimOpacity >= 0) { 204 mDozeHost.setAodDimmingScrim(scrimOpacity / 255f); 205 } 206 } 207 } 208 lightSensorSupportsCurrentPosture()209 private boolean lightSensorSupportsCurrentPosture() { 210 return mLightSensorOptional != null 211 && mDevicePosture < mLightSensorOptional.length; 212 } 213 isLightSensorPresent()214 private boolean isLightSensorPresent() { 215 if (!lightSensorSupportsCurrentPosture()) { 216 return mLightSensorOptional != null && mLightSensorOptional[0].isPresent(); 217 } 218 219 return mLightSensorOptional[mDevicePosture].isPresent(); 220 } 221 getLightSensor()222 private Sensor getLightSensor() { 223 if (!lightSensorSupportsCurrentPosture()) { 224 return null; 225 } 226 227 return mLightSensorOptional[mDevicePosture].get(); 228 } 229 computeScrimOpacity(int sensorValue)230 private int computeScrimOpacity(int sensorValue) { 231 if (sensorValue < 0 || sensorValue >= mSensorToScrimOpacity.length) { 232 return -1; 233 } 234 return mSensorToScrimOpacity[sensorValue]; 235 } 236 computeBrightness(int sensorValue)237 private int computeBrightness(int sensorValue) { 238 if (sensorValue < 0 || sensorValue >= mSensorToBrightness.length) { 239 return -1; 240 } 241 return mSensorToBrightness[sensorValue]; 242 } 243 244 @Override onAccuracyChanged(Sensor sensor, int accuracy)245 public void onAccuracyChanged(Sensor sensor, int accuracy) { 246 } 247 resetBrightnessToDefault()248 private void resetBrightnessToDefault() { 249 mDozeService.setDozeScreenBrightness( 250 clampToDimBrightnessForScreenOff( 251 clampToUserSetting(mDefaultDozeBrightness))); 252 mDozeHost.setAodDimmingScrim(0f); 253 } 254 //TODO: brightnessfloat change usages to float. clampToUserSetting(int brightness)255 private int clampToUserSetting(int brightness) { 256 int userSetting = Settings.System.getIntForUser(mContext.getContentResolver(), 257 Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE, 258 UserHandle.USER_CURRENT); 259 return Math.min(brightness, userSetting); 260 } 261 262 /** 263 * Clamp the brightness to the dim brightness value used by PowerManagerService just before the 264 * device times out and goes to sleep, if we are sleeping from a timeout. This ensures that we 265 * don't raise the brightness back to the user setting before or during the screen off 266 * animation. 267 */ clampToDimBrightnessForScreenOff(int brightness)268 private int clampToDimBrightnessForScreenOff(int brightness) { 269 final boolean screenTurningOff = 270 mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() 271 || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP; 272 if (screenTurningOff 273 && mWakefulnessLifecycle.getLastSleepReason() == GO_TO_SLEEP_REASON_TIMEOUT) { 274 return Math.max( 275 PowerManager.BRIGHTNESS_OFF, 276 // Use the lower of either the dim brightness, or the current brightness reduced 277 // by the minimum dim amount. This is the same logic used in 278 // DisplayPowerController#updatePowerState to apply a minimum dim amount. 279 Math.min( 280 brightness - (int) Math.floor( 281 mScreenBrightnessMinimumDimAmountFloat * 255), 282 mScreenBrightnessDim)); 283 } else { 284 return brightness; 285 } 286 } 287 setLightSensorEnabled(boolean enabled)288 private void setLightSensorEnabled(boolean enabled) { 289 if (enabled && !mRegistered && isLightSensorPresent()) { 290 // Wait until we get an event from the sensor until indicating ready. 291 mRegistered = mSensorManager.registerListener(this, getLightSensor(), 292 SensorManager.SENSOR_DELAY_NORMAL, mHandler); 293 mLastSensorValue = -1; 294 } else if (!enabled && mRegistered) { 295 mSensorManager.unregisterListener(this); 296 mRegistered = false; 297 mLastSensorValue = -1; 298 // Sensor is not enabled, hence we use the default brightness and are always ready. 299 } 300 } 301 setPaused(boolean paused)302 private void setPaused(boolean paused) { 303 if (mPaused != paused) { 304 mPaused = paused; 305 updateBrightnessAndReady(false /* force */); 306 } 307 } 308 setScreenOff(boolean screenOff)309 private void setScreenOff(boolean screenOff) { 310 if (mScreenOff != screenOff) { 311 mScreenOff = screenOff; 312 updateBrightnessAndReady(true /* force */); 313 } 314 } 315 316 @Override onReceive(Context context, Intent intent)317 public void onReceive(Context context, Intent intent) { 318 mDebugBrightnessBucket = intent.getIntExtra(BRIGHTNESS_BUCKET, -1); 319 updateBrightnessAndReady(false /* force */); 320 } 321 322 /** Dump current state */ dump(PrintWriter pw)323 public void dump(PrintWriter pw) { 324 pw.println("DozeScreenBrightness:"); 325 IndentingPrintWriter idpw = new IndentingPrintWriter(pw); 326 idpw.increaseIndent(); 327 idpw.println("registered=" + mRegistered); 328 idpw.println("posture=" + DevicePostureController.devicePostureToString(mDevicePosture)); 329 } 330 331 private final DevicePostureController.Callback mDevicePostureCallback = 332 new DevicePostureController.Callback() { 333 @Override 334 public void onPostureChanged(int posture) { 335 if (mDevicePosture == posture 336 || mLightSensorOptional.length < 2 337 || posture >= mLightSensorOptional.length) { 338 return; 339 } 340 341 final Sensor oldSensor = mLightSensorOptional[mDevicePosture].get(); 342 final Sensor newSensor = mLightSensorOptional[posture].get(); 343 if (Objects.equals(oldSensor, newSensor)) { 344 mDevicePosture = posture; 345 // uses the same sensor for the new posture 346 return; 347 } 348 349 // cancel the previous sensor: 350 if (mRegistered) { 351 setLightSensorEnabled(false); 352 mDevicePosture = posture; 353 setLightSensorEnabled(true); 354 } else { 355 mDevicePosture = posture; 356 } 357 mDozeLog.tracePostureChanged(mDevicePosture, "DozeScreenBrightness swap " 358 + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered); 359 } 360 }; 361 } 362