1 /* 2 * Copyright (C) 2012 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.server.display; 18 19 import android.content.Context; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.os.PowerManager; 23 import android.util.FloatProperty; 24 import android.util.IntProperty; 25 import android.util.Slog; 26 import android.view.Choreographer; 27 import android.view.Display; 28 29 import java.io.PrintWriter; 30 31 /** 32 * Controls the display power state. 33 * <p> 34 * This component is similar in nature to a {@link android.view.View} except that it 35 * describes the properties of a display. When properties are changed, the component 36 * invalidates itself and posts a callback to apply the changes in a consistent order. 37 * This mechanism enables multiple properties of the display power state to be animated 38 * together smoothly by the animation framework. Some of the work to blank or unblank 39 * the display is done on a separate thread to avoid blocking the looper. 40 * </p><p> 41 * This component must only be created or accessed by the {@link Looper} thread 42 * that belongs to the {@link DisplayPowerController}. 43 * </p><p> 44 * We don't need to worry about holding a suspend blocker here because the 45 * power manager does that for us whenever there is a change in progress. 46 * </p> 47 */ 48 final class DisplayPowerState { 49 private static final String TAG = "DisplayPowerState"; 50 51 private static boolean DEBUG = false; 52 53 private final Handler mHandler; 54 private final Choreographer mChoreographer; 55 private final DisplayBlanker mBlanker; 56 private final ColorFade mColorFade; 57 private final PhotonicModulator mPhotonicModulator; 58 59 private int mScreenState; 60 private int mScreenBrightness; 61 private boolean mScreenReady; 62 private boolean mScreenUpdatePending; 63 64 private boolean mColorFadePrepared; 65 private float mColorFadeLevel; 66 private boolean mColorFadeReady; 67 private boolean mColorFadeDrawPending; 68 69 private Runnable mCleanListener; 70 71 public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade) { 72 mHandler = new Handler(true /*async*/); 73 mChoreographer = Choreographer.getInstance(); 74 mBlanker = blanker; 75 mColorFade = colorFade; 76 mPhotonicModulator = new PhotonicModulator(); 77 mPhotonicModulator.start(); 78 79 // At boot time, we know that the screen is on and the electron beam 80 // animation is not playing. We don't know the screen's brightness though, 81 // so prepare to set it to a known state when the state is next applied. 82 // Although we set the brightness to full on here, the display power controller 83 // will reset the brightness to a new level immediately before the changes 84 // actually have a chance to be applied. 85 mScreenState = Display.STATE_ON; 86 mScreenBrightness = PowerManager.BRIGHTNESS_ON; 87 scheduleScreenUpdate(); 88 89 mColorFadePrepared = false; 90 mColorFadeLevel = 1.0f; 91 mColorFadeReady = true; 92 } 93 94 public static final FloatProperty<DisplayPowerState> COLOR_FADE_LEVEL = 95 new FloatProperty<DisplayPowerState>("electronBeamLevel") { 96 @Override 97 public void setValue(DisplayPowerState object, float value) { 98 object.setColorFadeLevel(value); 99 } 100 101 @Override 102 public Float get(DisplayPowerState object) { 103 return object.getColorFadeLevel(); 104 } 105 }; 106 107 public static final IntProperty<DisplayPowerState> SCREEN_BRIGHTNESS = 108 new IntProperty<DisplayPowerState>("screenBrightness") { 109 @Override 110 public void setValue(DisplayPowerState object, int value) { 111 object.setScreenBrightness(value); 112 } 113 114 @Override 115 public Integer get(DisplayPowerState object) { 116 return object.getScreenBrightness(); 117 } 118 }; 119 120 /** 121 * Sets whether the screen is on, off, or dozing. 122 */ 123 public void setScreenState(int state) { 124 if (mScreenState != state) { 125 if (DEBUG) { 126 Slog.d(TAG, "setScreenState: state=" + state); 127 } 128 129 mScreenState = state; 130 mScreenReady = false; 131 scheduleScreenUpdate(); 132 } 133 } 134 135 /** 136 * Gets the desired screen state. 137 */ 138 public int getScreenState() { 139 return mScreenState; 140 } 141 142 /** 143 * Sets the display brightness. 144 * 145 * @param brightness The brightness, ranges from 0 (minimum / off) to 255 (brightest). 146 */ 147 public void setScreenBrightness(int brightness) { 148 if (mScreenBrightness != brightness) { 149 if (DEBUG) { 150 Slog.d(TAG, "setScreenBrightness: brightness=" + brightness); 151 } 152 153 mScreenBrightness = brightness; 154 if (mScreenState != Display.STATE_OFF) { 155 mScreenReady = false; 156 scheduleScreenUpdate(); 157 } 158 } 159 } 160 161 /** 162 * Gets the screen brightness. 163 */ 164 public int getScreenBrightness() { 165 return mScreenBrightness; 166 } 167 168 /** 169 * Prepares the electron beam to turn on or off. 170 * This method should be called before starting an animation because it 171 * can take a fair amount of time to prepare the electron beam surface. 172 * 173 * @param mode The electron beam animation mode to prepare. 174 * @return True if the electron beam was prepared. 175 */ 176 public boolean prepareColorFade(Context context, int mode) { 177 if (!mColorFade.prepare(context, mode)) { 178 mColorFadePrepared = false; 179 mColorFadeReady = true; 180 return false; 181 } 182 183 mColorFadePrepared = true; 184 mColorFadeReady = false; 185 scheduleColorFadeDraw(); 186 return true; 187 } 188 189 /** 190 * Dismisses the color fade surface. 191 */ 192 public void dismissColorFade() { 193 mColorFade.dismiss(); 194 mColorFadePrepared = false; 195 mColorFadeReady = true; 196 } 197 198 /** 199 * Dismisses the color fade resources. 200 */ 201 public void dismissColorFadeResources() { 202 mColorFade.dismissResources(); 203 } 204 205 /** 206 * Sets the level of the electron beam steering current. 207 * 208 * The display is blanked when the level is 0.0. In normal use, the electron 209 * beam should have a value of 1.0. The electron beam is unstable in between 210 * these states and the picture quality may be compromised. For best effect, 211 * the electron beam should be warmed up or cooled off slowly. 212 * 213 * Warning: Electron beam emits harmful radiation. Avoid direct exposure to 214 * skin or eyes. 215 * 216 * @param level The level, ranges from 0.0 (full off) to 1.0 (full on). 217 */ 218 public void setColorFadeLevel(float level) { 219 if (mColorFadeLevel != level) { 220 if (DEBUG) { 221 Slog.d(TAG, "setColorFadeLevel: level=" + level); 222 } 223 224 mColorFadeLevel = level; 225 if (mScreenState != Display.STATE_OFF) { 226 mScreenReady = false; 227 scheduleScreenUpdate(); // update backlight brightness 228 } 229 if (mColorFadePrepared) { 230 mColorFadeReady = false; 231 scheduleColorFadeDraw(); 232 } 233 } 234 } 235 236 /** 237 * Gets the level of the electron beam steering current. 238 */ 239 public float getColorFadeLevel() { 240 return mColorFadeLevel; 241 } 242 243 /** 244 * Returns true if no properties have been invalidated. 245 * Otherwise, returns false and promises to invoke the specified listener 246 * when the properties have all been applied. 247 * The listener always overrides any previously set listener. 248 */ 249 public boolean waitUntilClean(Runnable listener) { 250 if (!mScreenReady || !mColorFadeReady) { 251 mCleanListener = listener; 252 return false; 253 } else { 254 mCleanListener = null; 255 return true; 256 } 257 } 258 259 public void dump(PrintWriter pw) { 260 pw.println(); 261 pw.println("Display Power State:"); 262 pw.println(" mScreenState=" + Display.stateToString(mScreenState)); 263 pw.println(" mScreenBrightness=" + mScreenBrightness); 264 pw.println(" mScreenReady=" + mScreenReady); 265 pw.println(" mScreenUpdatePending=" + mScreenUpdatePending); 266 pw.println(" mColorFadePrepared=" + mColorFadePrepared); 267 pw.println(" mColorFadeLevel=" + mColorFadeLevel); 268 pw.println(" mColorFadeReady=" + mColorFadeReady); 269 pw.println(" mColorFadeDrawPending=" + mColorFadeDrawPending); 270 271 mPhotonicModulator.dump(pw); 272 mColorFade.dump(pw); 273 } 274 275 private void scheduleScreenUpdate() { 276 if (!mScreenUpdatePending) { 277 mScreenUpdatePending = true; 278 postScreenUpdateThreadSafe(); 279 } 280 } 281 282 private void postScreenUpdateThreadSafe() { 283 mHandler.removeCallbacks(mScreenUpdateRunnable); 284 mHandler.post(mScreenUpdateRunnable); 285 } 286 287 private void scheduleColorFadeDraw() { 288 if (!mColorFadeDrawPending) { 289 mColorFadeDrawPending = true; 290 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, 291 mColorFadeDrawRunnable, null); 292 } 293 } 294 295 private void invokeCleanListenerIfNeeded() { 296 final Runnable listener = mCleanListener; 297 if (listener != null && mScreenReady && mColorFadeReady) { 298 mCleanListener = null; 299 listener.run(); 300 } 301 } 302 303 private final Runnable mScreenUpdateRunnable = new Runnable() { 304 @Override 305 public void run() { 306 mScreenUpdatePending = false; 307 308 int brightness = mScreenState != Display.STATE_OFF 309 && mColorFadeLevel > 0f ? mScreenBrightness : 0; 310 if (mPhotonicModulator.setState(mScreenState, brightness)) { 311 if (DEBUG) { 312 Slog.d(TAG, "Screen ready"); 313 } 314 mScreenReady = true; 315 invokeCleanListenerIfNeeded(); 316 } else { 317 if (DEBUG) { 318 Slog.d(TAG, "Screen not ready"); 319 } 320 } 321 } 322 }; 323 324 private final Runnable mColorFadeDrawRunnable = new Runnable() { 325 @Override 326 public void run() { 327 mColorFadeDrawPending = false; 328 329 if (mColorFadePrepared) { 330 mColorFade.draw(mColorFadeLevel); 331 } 332 333 mColorFadeReady = true; 334 invokeCleanListenerIfNeeded(); 335 } 336 }; 337 338 /** 339 * Updates the state of the screen and backlight asynchronously on a separate thread. 340 */ 341 private final class PhotonicModulator extends Thread { 342 private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off 343 private static final int INITIAL_BACKLIGHT = -1; // unknown 344 345 private final Object mLock = new Object(); 346 347 private int mPendingState = INITIAL_SCREEN_STATE; 348 private int mPendingBacklight = INITIAL_BACKLIGHT; 349 private int mActualState = INITIAL_SCREEN_STATE; 350 private int mActualBacklight = INITIAL_BACKLIGHT; 351 private boolean mStateChangeInProgress; 352 private boolean mBacklightChangeInProgress; 353 354 public PhotonicModulator() { 355 super("PhotonicModulator"); 356 } 357 358 public boolean setState(int state, int backlight) { 359 synchronized (mLock) { 360 boolean stateChanged = state != mPendingState; 361 boolean backlightChanged = backlight != mPendingBacklight; 362 if (stateChanged || backlightChanged) { 363 if (DEBUG) { 364 Slog.d(TAG, "Requesting new screen state: state=" 365 + Display.stateToString(state) + ", backlight=" + backlight); 366 } 367 368 mPendingState = state; 369 mPendingBacklight = backlight; 370 371 boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress; 372 mStateChangeInProgress = stateChanged || mStateChangeInProgress; 373 mBacklightChangeInProgress = backlightChanged || mBacklightChangeInProgress; 374 375 if (!changeInProgress) { 376 mLock.notifyAll(); 377 } 378 } 379 return !mStateChangeInProgress; 380 } 381 } 382 383 public void dump(PrintWriter pw) { 384 synchronized (mLock) { 385 pw.println(); 386 pw.println("Photonic Modulator State:"); 387 pw.println(" mPendingState=" + Display.stateToString(mPendingState)); 388 pw.println(" mPendingBacklight=" + mPendingBacklight); 389 pw.println(" mActualState=" + Display.stateToString(mActualState)); 390 pw.println(" mActualBacklight=" + mActualBacklight); 391 pw.println(" mStateChangeInProgress=" + mStateChangeInProgress); 392 pw.println(" mBacklightChangeInProgress=" + mBacklightChangeInProgress); 393 } 394 } 395 396 @Override 397 public void run() { 398 for (;;) { 399 // Get pending change. 400 final int state; 401 final boolean stateChanged; 402 final int backlight; 403 final boolean backlightChanged; 404 synchronized (mLock) { 405 state = mPendingState; 406 stateChanged = (state != mActualState); 407 backlight = mPendingBacklight; 408 backlightChanged = (backlight != mActualBacklight); 409 if (!stateChanged) { 410 // State changed applied, notify outer class. 411 postScreenUpdateThreadSafe(); 412 mStateChangeInProgress = false; 413 } 414 if (!backlightChanged) { 415 mBacklightChangeInProgress = false; 416 } 417 if (!stateChanged && !backlightChanged) { 418 try { 419 mLock.wait(); 420 } catch (InterruptedException ex) { } 421 continue; 422 } 423 mActualState = state; 424 mActualBacklight = backlight; 425 } 426 427 // Apply pending change. 428 if (DEBUG) { 429 Slog.d(TAG, "Updating screen state: state=" 430 + Display.stateToString(state) + ", backlight=" + backlight); 431 } 432 mBlanker.requestDisplayState(state, backlight); 433 } 434 } 435 } 436 } 437