blob: e2fd0acd3887d85911337f71d2a9b965d633ca40 [file] [log] [blame]
Jeff Brown96307042012-07-27 15:51:34 -07001/*
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
Jeff Brownad9ef192014-04-08 17:26:30 -070017package com.android.server.display;
Jeff Brown96307042012-07-27 15:51:34 -070018
Michael Lentine0839adb2014-07-29 18:47:56 -070019import android.content.Context;
Jeff Brown32dafe22012-10-19 17:04:30 -070020import android.os.Handler;
Jeff Brown96307042012-07-27 15:51:34 -070021import android.os.Looper;
22import android.os.PowerManager;
23import android.util.FloatProperty;
24import android.util.IntProperty;
25import android.util.Slog;
26import android.view.Choreographer;
Jeff Brown037c33e2014-04-09 00:31:55 -070027import android.view.Display;
Jeff Brown96307042012-07-27 15:51:34 -070028
29import java.io.PrintWriter;
30
31/**
Jeff Brown32dafe22012-10-19 17:04:30 -070032 * Controls the display power state.
33 * <p>
Jeff Brownad9ef192014-04-08 17:26:30 -070034 * 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
Jeff Brown32dafe22012-10-19 17:04:30 -070036 * 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>
Jeff Brown96307042012-07-27 15:51:34 -070041 * This component must only be created or accessed by the {@link Looper} thread
42 * that belongs to the {@link DisplayPowerController}.
Jeff Brown32dafe22012-10-19 17:04:30 -070043 * </p><p>
Jeff Brown96307042012-07-27 15:51:34 -070044 * We don't need to worry about holding a suspend blocker here because the
Jeff Brownad9ef192014-04-08 17:26:30 -070045 * power manager does that for us whenever there is a change in progress.
Jeff Brown32dafe22012-10-19 17:04:30 -070046 * </p>
Jeff Brown96307042012-07-27 15:51:34 -070047 */
48final class DisplayPowerState {
49 private static final String TAG = "DisplayPowerState";
50
51 private static boolean DEBUG = false;
52
Jeff Brown32dafe22012-10-19 17:04:30 -070053 private final Handler mHandler;
Jeff Brown96307042012-07-27 15:51:34 -070054 private final Choreographer mChoreographer;
Jeff Brown037c33e2014-04-09 00:31:55 -070055 private final DisplayBlanker mBlanker;
Michael Lentine0839adb2014-07-29 18:47:56 -070056 private final ColorFade mColorFade;
Jeff Brown32dafe22012-10-19 17:04:30 -070057 private final PhotonicModulator mPhotonicModulator;
Jeff Brown96307042012-07-27 15:51:34 -070058
Jeff Brown037c33e2014-04-09 00:31:55 -070059 private int mScreenState;
Jeff Brown96307042012-07-27 15:51:34 -070060 private int mScreenBrightness;
Jeff Brown32dafe22012-10-19 17:04:30 -070061 private boolean mScreenReady;
62 private boolean mScreenUpdatePending;
63
Michael Lentine0839adb2014-07-29 18:47:56 -070064 private boolean mColorFadePrepared;
65 private float mColorFadeLevel;
66 private boolean mColorFadeReady;
67 private boolean mColorFadeDrawPending;
Jeff Brown96307042012-07-27 15:51:34 -070068
69 private Runnable mCleanListener;
70
Jeff Brown5d6443b2015-04-10 20:15:01 -070071 public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade) {
Jeff Brown32dafe22012-10-19 17:04:30 -070072 mHandler = new Handler(true /*async*/);
Jeff Brown96307042012-07-27 15:51:34 -070073 mChoreographer = Choreographer.getInstance();
Jeff Brown037c33e2014-04-09 00:31:55 -070074 mBlanker = blanker;
Jeff Brown5d6443b2015-04-10 20:15:01 -070075 mColorFade = colorFade;
Jeff Brown32dafe22012-10-19 17:04:30 -070076 mPhotonicModulator = new PhotonicModulator();
Jeff Brown0a434772014-09-30 14:42:27 -070077 mPhotonicModulator.start();
Jeff Brown96307042012-07-27 15:51:34 -070078
Jeff Brownf75724b2012-08-25 13:34:32 -070079 // 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.
Jeff Brown037c33e2014-04-09 00:31:55 -070085 mScreenState = Display.STATE_ON;
Jeff Brown96307042012-07-27 15:51:34 -070086 mScreenBrightness = PowerManager.BRIGHTNESS_ON;
Jeff Brown32dafe22012-10-19 17:04:30 -070087 scheduleScreenUpdate();
88
Michael Lentine0839adb2014-07-29 18:47:56 -070089 mColorFadePrepared = false;
90 mColorFadeLevel = 1.0f;
91 mColorFadeReady = true;
Jeff Brown96307042012-07-27 15:51:34 -070092 }
93
Michael Lentine0839adb2014-07-29 18:47:56 -070094 public static final FloatProperty<DisplayPowerState> COLOR_FADE_LEVEL =
Jeff Brown96307042012-07-27 15:51:34 -070095 new FloatProperty<DisplayPowerState>("electronBeamLevel") {
96 @Override
97 public void setValue(DisplayPowerState object, float value) {
Michael Lentine0839adb2014-07-29 18:47:56 -070098 object.setColorFadeLevel(value);
Jeff Brown96307042012-07-27 15:51:34 -070099 }
100
101 @Override
102 public Float get(DisplayPowerState object) {
Michael Lentine0839adb2014-07-29 18:47:56 -0700103 return object.getColorFadeLevel();
Jeff Brown96307042012-07-27 15:51:34 -0700104 }
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 /**
Jeff Brown037c33e2014-04-09 00:31:55 -0700121 * Sets whether the screen is on, off, or dozing.
Jeff Brown96307042012-07-27 15:51:34 -0700122 */
Jeff Brown037c33e2014-04-09 00:31:55 -0700123 public void setScreenState(int state) {
124 if (mScreenState != state) {
Jeff Brown96307042012-07-27 15:51:34 -0700125 if (DEBUG) {
Jeff Brown037c33e2014-04-09 00:31:55 -0700126 Slog.d(TAG, "setScreenState: state=" + state);
Jeff Brown96307042012-07-27 15:51:34 -0700127 }
128
Jeff Brown037c33e2014-04-09 00:31:55 -0700129 mScreenState = state;
Jeff Brown32dafe22012-10-19 17:04:30 -0700130 mScreenReady = false;
131 scheduleScreenUpdate();
Jeff Brown96307042012-07-27 15:51:34 -0700132 }
133 }
134
135 /**
Jeff Brown037c33e2014-04-09 00:31:55 -0700136 * Gets the desired screen state.
Jeff Brown96307042012-07-27 15:51:34 -0700137 */
Jeff Brown037c33e2014-04-09 00:31:55 -0700138 public int getScreenState() {
139 return mScreenState;
Jeff Brown96307042012-07-27 15:51:34 -0700140 }
141
142 /**
Jeff Brown32dafe22012-10-19 17:04:30 -0700143 * 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;
Jeff Brown037c33e2014-04-09 00:31:55 -0700154 if (mScreenState != Display.STATE_OFF) {
Jeff Brown32dafe22012-10-19 17:04:30 -0700155 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 /**
Jeff Brown96307042012-07-27 15:51:34 -0700169 * 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 *
Jeff Brown8b9cf1c2012-10-07 14:54:17 -0700173 * @param mode The electron beam animation mode to prepare.
Jeff Brown96307042012-07-27 15:51:34 -0700174 * @return True if the electron beam was prepared.
175 */
Michael Lentine0839adb2014-07-29 18:47:56 -0700176 public boolean prepareColorFade(Context context, int mode) {
177 if (!mColorFade.prepare(context, mode)) {
178 mColorFadePrepared = false;
179 mColorFadeReady = true;
Jeff Brown32dafe22012-10-19 17:04:30 -0700180 return false;
181 }
182
Michael Lentine0839adb2014-07-29 18:47:56 -0700183 mColorFadePrepared = true;
184 mColorFadeReady = false;
185 scheduleColorFadeDraw();
Jeff Brown32dafe22012-10-19 17:04:30 -0700186 return true;
Jeff Brown96307042012-07-27 15:51:34 -0700187 }
188
189 /**
Michael Lentined73854d2015-09-25 10:55:26 -0700190 * Dismisses the color fade surface.
Jeff Brown96307042012-07-27 15:51:34 -0700191 */
Michael Lentine0839adb2014-07-29 18:47:56 -0700192 public void dismissColorFade() {
193 mColorFade.dismiss();
194 mColorFadePrepared = false;
195 mColorFadeReady = true;
Jeff Brown96307042012-07-27 15:51:34 -0700196 }
197
Michael Lentined73854d2015-09-25 10:55:26 -0700198 /**
199 * Dismisses the color fade resources.
200 */
201 public void dismissColorFadeResources() {
202 mColorFade.dismissResources();
203 }
204
Jeff Brown96307042012-07-27 15:51:34 -0700205 /**
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 */
Michael Lentine0839adb2014-07-29 18:47:56 -0700218 public void setColorFadeLevel(float level) {
219 if (mColorFadeLevel != level) {
Jeff Brown96307042012-07-27 15:51:34 -0700220 if (DEBUG) {
Michael Lentine0839adb2014-07-29 18:47:56 -0700221 Slog.d(TAG, "setColorFadeLevel: level=" + level);
Jeff Brown96307042012-07-27 15:51:34 -0700222 }
223
Michael Lentine0839adb2014-07-29 18:47:56 -0700224 mColorFadeLevel = level;
Jeff Brown037c33e2014-04-09 00:31:55 -0700225 if (mScreenState != Display.STATE_OFF) {
Jeff Brown32dafe22012-10-19 17:04:30 -0700226 mScreenReady = false;
227 scheduleScreenUpdate(); // update backlight brightness
228 }
Michael Lentine0839adb2014-07-29 18:47:56 -0700229 if (mColorFadePrepared) {
230 mColorFadeReady = false;
231 scheduleColorFadeDraw();
Jeff Brown32dafe22012-10-19 17:04:30 -0700232 }
Jeff Brown96307042012-07-27 15:51:34 -0700233 }
234 }
235
236 /**
237 * Gets the level of the electron beam steering current.
238 */
Michael Lentine0839adb2014-07-29 18:47:56 -0700239 public float getColorFadeLevel() {
240 return mColorFadeLevel;
Jeff Brown96307042012-07-27 15:51:34 -0700241 }
242
243 /**
Jeff Brown96307042012-07-27 15:51:34 -0700244 * 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) {
Michael Lentine0839adb2014-07-29 18:47:56 -0700250 if (!mScreenReady || !mColorFadeReady) {
Jeff Brown96307042012-07-27 15:51:34 -0700251 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:");
Jeff Brown037c33e2014-04-09 00:31:55 -0700262 pw.println(" mScreenState=" + Display.stateToString(mScreenState));
Jeff Brown96307042012-07-27 15:51:34 -0700263 pw.println(" mScreenBrightness=" + mScreenBrightness);
Jeff Brown32dafe22012-10-19 17:04:30 -0700264 pw.println(" mScreenReady=" + mScreenReady);
265 pw.println(" mScreenUpdatePending=" + mScreenUpdatePending);
Michael Lentine0839adb2014-07-29 18:47:56 -0700266 pw.println(" mColorFadePrepared=" + mColorFadePrepared);
267 pw.println(" mColorFadeLevel=" + mColorFadeLevel);
268 pw.println(" mColorFadeReady=" + mColorFadeReady);
269 pw.println(" mColorFadeDrawPending=" + mColorFadeDrawPending);
Jeff Brown96307042012-07-27 15:51:34 -0700270
Jeff Brown32dafe22012-10-19 17:04:30 -0700271 mPhotonicModulator.dump(pw);
Michael Lentine0839adb2014-07-29 18:47:56 -0700272 mColorFade.dump(pw);
Jeff Brown96307042012-07-27 15:51:34 -0700273 }
274
Jeff Brown32dafe22012-10-19 17:04:30 -0700275 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
Michael Lentine0839adb2014-07-29 18:47:56 -0700287 private void scheduleColorFadeDraw() {
288 if (!mColorFadeDrawPending) {
289 mColorFadeDrawPending = true;
Jeff Brown96307042012-07-27 15:51:34 -0700290 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
Michael Lentine0839adb2014-07-29 18:47:56 -0700291 mColorFadeDrawRunnable, null);
Jeff Brown96307042012-07-27 15:51:34 -0700292 }
293 }
294
Jeff Brown32dafe22012-10-19 17:04:30 -0700295 private void invokeCleanListenerIfNeeded() {
296 final Runnable listener = mCleanListener;
Michael Lentine0839adb2014-07-29 18:47:56 -0700297 if (listener != null && mScreenReady && mColorFadeReady) {
Jeff Brown32dafe22012-10-19 17:04:30 -0700298 mCleanListener = null;
299 listener.run();
300 }
301 }
302
303 private final Runnable mScreenUpdateRunnable = new Runnable() {
Jeff Brown96307042012-07-27 15:51:34 -0700304 @Override
305 public void run() {
Jeff Brown32dafe22012-10-19 17:04:30 -0700306 mScreenUpdatePending = false;
307
Jeff Brown037c33e2014-04-09 00:31:55 -0700308 int brightness = mScreenState != Display.STATE_OFF
Michael Lentine0839adb2014-07-29 18:47:56 -0700309 && mColorFadeLevel > 0f ? mScreenBrightness : 0;
Jeff Brown037c33e2014-04-09 00:31:55 -0700310 if (mPhotonicModulator.setState(mScreenState, brightness)) {
Jeff Brown2d8a3902014-03-11 23:02:35 -0700311 if (DEBUG) {
312 Slog.d(TAG, "Screen ready");
313 }
Jeff Brown32dafe22012-10-19 17:04:30 -0700314 mScreenReady = true;
315 invokeCleanListenerIfNeeded();
Jeff Brown2d8a3902014-03-11 23:02:35 -0700316 } else {
317 if (DEBUG) {
318 Slog.d(TAG, "Screen not ready");
319 }
Jeff Brown96307042012-07-27 15:51:34 -0700320 }
321 }
322 };
Jeff Brown32dafe22012-10-19 17:04:30 -0700323
Michael Lentine0839adb2014-07-29 18:47:56 -0700324 private final Runnable mColorFadeDrawRunnable = new Runnable() {
Jeff Brown32dafe22012-10-19 17:04:30 -0700325 @Override
326 public void run() {
Michael Lentine0839adb2014-07-29 18:47:56 -0700327 mColorFadeDrawPending = false;
Jeff Brown32dafe22012-10-19 17:04:30 -0700328
Michael Lentine0839adb2014-07-29 18:47:56 -0700329 if (mColorFadePrepared) {
330 mColorFade.draw(mColorFadeLevel);
Jeff Brown32dafe22012-10-19 17:04:30 -0700331 }
332
Michael Lentine0839adb2014-07-29 18:47:56 -0700333 mColorFadeReady = true;
Jeff Brown32dafe22012-10-19 17:04:30 -0700334 invokeCleanListenerIfNeeded();
335 }
336 };
337
338 /**
339 * Updates the state of the screen and backlight asynchronously on a separate thread.
340 */
Jeff Brown0a434772014-09-30 14:42:27 -0700341 private final class PhotonicModulator extends Thread {
Jeff Brown037c33e2014-04-09 00:31:55 -0700342 private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off
Jeff Brown32dafe22012-10-19 17:04:30 -0700343 private static final int INITIAL_BACKLIGHT = -1; // unknown
344
345 private final Object mLock = new Object();
346
Jeff Brown037c33e2014-04-09 00:31:55 -0700347 private int mPendingState = INITIAL_SCREEN_STATE;
Jeff Brown32dafe22012-10-19 17:04:30 -0700348 private int mPendingBacklight = INITIAL_BACKLIGHT;
Jeff Brown037c33e2014-04-09 00:31:55 -0700349 private int mActualState = INITIAL_SCREEN_STATE;
Jeff Brown32dafe22012-10-19 17:04:30 -0700350 private int mActualBacklight = INITIAL_BACKLIGHT;
Jorim Jaggi6e90ea02015-08-24 13:54:19 -0700351 private boolean mStateChangeInProgress;
352 private boolean mBacklightChangeInProgress;
Jeff Brown32dafe22012-10-19 17:04:30 -0700353
Jeff Brownfaec22c82015-04-10 12:58:52 -0700354 public PhotonicModulator() {
355 super("PhotonicModulator");
356 }
357
Jeff Brown037c33e2014-04-09 00:31:55 -0700358 public boolean setState(int state, int backlight) {
Jeff Brown32dafe22012-10-19 17:04:30 -0700359 synchronized (mLock) {
Jorim Jaggi6e90ea02015-08-24 13:54:19 -0700360 boolean stateChanged = state != mPendingState;
361 boolean backlightChanged = backlight != mPendingBacklight;
362 if (stateChanged || backlightChanged) {
Jeff Brown32dafe22012-10-19 17:04:30 -0700363 if (DEBUG) {
Jeff Brown037c33e2014-04-09 00:31:55 -0700364 Slog.d(TAG, "Requesting new screen state: state="
365 + Display.stateToString(state) + ", backlight=" + backlight);
Jeff Brown32dafe22012-10-19 17:04:30 -0700366 }
367
Jeff Brown037c33e2014-04-09 00:31:55 -0700368 mPendingState = state;
Jeff Brown32dafe22012-10-19 17:04:30 -0700369 mPendingBacklight = backlight;
370
Jorim Jaggi6e90ea02015-08-24 13:54:19 -0700371 boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress;
Michael Wright640666f2017-06-06 22:01:20 +0100372 mStateChangeInProgress = stateChanged || mStateChangeInProgress;
373 mBacklightChangeInProgress = backlightChanged || mBacklightChangeInProgress;
Jorim Jaggi6e90ea02015-08-24 13:54:19 -0700374
375 if (!changeInProgress) {
Jeff Brown0a434772014-09-30 14:42:27 -0700376 mLock.notifyAll();
Jeff Brown32dafe22012-10-19 17:04:30 -0700377 }
378 }
Jorim Jaggi6e90ea02015-08-24 13:54:19 -0700379 return !mStateChangeInProgress;
Jeff Brown32dafe22012-10-19 17:04:30 -0700380 }
381 }
382
383 public void dump(PrintWriter pw) {
Jeff Brown0a434772014-09-30 14:42:27 -0700384 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);
Jorim Jaggi6e90ea02015-08-24 13:54:19 -0700391 pw.println(" mStateChangeInProgress=" + mStateChangeInProgress);
392 pw.println(" mBacklightChangeInProgress=" + mBacklightChangeInProgress);
Jeff Brown0a434772014-09-30 14:42:27 -0700393 }
Jeff Brown32dafe22012-10-19 17:04:30 -0700394 }
395
Jeff Brown0a434772014-09-30 14:42:27 -0700396 @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);
Jorim Jaggi6e90ea02015-08-24 13:54:19 -0700409 if (!stateChanged) {
410 // State changed applied, notify outer class.
Jeff Brown0a434772014-09-30 14:42:27 -0700411 postScreenUpdateThreadSafe();
Jorim Jaggi6e90ea02015-08-24 13:54:19 -0700412 mStateChangeInProgress = false;
413 }
414 if (!backlightChanged) {
415 mBacklightChangeInProgress = false;
416 }
417 if (!stateChanged && !backlightChanged) {
Jeff Brown0a434772014-09-30 14:42:27 -0700418 try {
419 mLock.wait();
420 } catch (InterruptedException ex) { }
421 continue;
Jeff Brown32dafe22012-10-19 17:04:30 -0700422 }
Jeff Brown0a434772014-09-30 14:42:27 -0700423 mActualState = state;
424 mActualBacklight = backlight;
Jeff Brown32dafe22012-10-19 17:04:30 -0700425 }
426
Jeff Brown0a434772014-09-30 14:42:27 -0700427 // Apply pending change.
428 if (DEBUG) {
429 Slog.d(TAG, "Updating screen state: state="
430 + Display.stateToString(state) + ", backlight=" + backlight);
431 }
Jeff Brown5d6443b2015-04-10 20:15:01 -0700432 mBlanker.requestDisplayState(state, backlight);
Jeff Brown0a434772014-09-30 14:42:27 -0700433 }
434 }
Jeff Brown32dafe22012-10-19 17:04:30 -0700435 }
Jeff Brown96307042012-07-27 15:51:34 -0700436}