/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server;

import com.android.internal.app.IBatteryStats;
import com.android.internal.app.ShutdownThread;
import com.android.server.am.BatteryStatsService;

import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.ContentQueryMap;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IPowerManager;
import android.os.LocalPowerManager;
import android.os.Power;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.WorkSource;
import android.provider.Settings.SettingNotFoundException;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.view.WindowManagerPolicy;
import static android.provider.Settings.System.DIM_SCREEN;
import static android.provider.Settings.System.SCREEN_BRIGHTNESS;
import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
import static android.provider.Settings.System.STAY_ON_WHILE_PLUGGED_IN;
import static android.provider.Settings.System.WINDOW_ANIMATION_SCALE;
import static android.provider.Settings.System.TRANSITION_ANIMATION_SCALE;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

public class PowerManagerService extends IPowerManager.Stub
        implements LocalPowerManager, Watchdog.Monitor {

    private static final String TAG = "PowerManagerService";
    static final String PARTIAL_NAME = "PowerManagerService";

    private static final boolean LOG_PARTIAL_WL = false;

    // Indicates whether touch-down cycles should be logged as part of the
    // LOG_POWER_SCREEN_STATE log events
    private static final boolean LOG_TOUCH_DOWNS = true;

    private static final int LOCK_MASK = PowerManager.PARTIAL_WAKE_LOCK
                                        | PowerManager.SCREEN_DIM_WAKE_LOCK
                                        | PowerManager.SCREEN_BRIGHT_WAKE_LOCK
                                        | PowerManager.FULL_WAKE_LOCK
                                        | PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK;

    //                       time since last state:               time since last event:
    // The short keylight delay comes from secure settings; this is the default.
    private static final int SHORT_KEYLIGHT_DELAY_DEFAULT = 6000; // t+6 sec
    private static final int MEDIUM_KEYLIGHT_DELAY = 15000;       // t+15 sec
    private static final int LONG_KEYLIGHT_DELAY = 6000;        // t+6 sec
    private static final int LONG_DIM_TIME = 7000;              // t+N-5 sec

    // How long to wait to debounce light sensor changes.
    private static final int LIGHT_SENSOR_DELAY = 2000;

    // For debouncing the proximity sensor.
    private static final int PROXIMITY_SENSOR_DELAY = 1000;

    // trigger proximity if distance is less than 5 cm
    private static final float PROXIMITY_THRESHOLD = 5.0f;

    // Cached secure settings; see updateSettingsValues()
    private int mShortKeylightDelay = SHORT_KEYLIGHT_DELAY_DEFAULT;

    // Default timeout for screen off, if not found in settings database = 15 seconds.
    private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15000;

    // flags for setPowerState
    private static final int SCREEN_ON_BIT          = 0x00000001;
    private static final int SCREEN_BRIGHT_BIT      = 0x00000002;
    private static final int BUTTON_BRIGHT_BIT      = 0x00000004;
    private static final int KEYBOARD_BRIGHT_BIT    = 0x00000008;
    private static final int BATTERY_LOW_BIT        = 0x00000010;

    // values for setPowerState

    // SCREEN_OFF == everything off
    private static final int SCREEN_OFF         = 0x00000000;

    // SCREEN_DIM == screen on, screen backlight dim
    private static final int SCREEN_DIM         = SCREEN_ON_BIT;

    // SCREEN_BRIGHT == screen on, screen backlight bright
    private static final int SCREEN_BRIGHT      = SCREEN_ON_BIT | SCREEN_BRIGHT_BIT;

    // SCREEN_BUTTON_BRIGHT == screen on, screen and button backlights bright
    private static final int SCREEN_BUTTON_BRIGHT  = SCREEN_BRIGHT | BUTTON_BRIGHT_BIT;

    // SCREEN_BUTTON_BRIGHT == screen on, screen, button and keyboard backlights bright
    private static final int ALL_BRIGHT         = SCREEN_BUTTON_BRIGHT | KEYBOARD_BRIGHT_BIT;

    // used for noChangeLights in setPowerState()
    private static final int LIGHTS_MASK        = SCREEN_BRIGHT_BIT | BUTTON_BRIGHT_BIT | KEYBOARD_BRIGHT_BIT;

    boolean mAnimateScreenLights = true;

    static final int ANIM_STEPS = 60/4;
    // Slower animation for autobrightness changes
    static final int AUTOBRIGHTNESS_ANIM_STEPS = 60;

    // These magic numbers are the initial state of the LEDs at boot.  Ideally
    // we should read them from the driver, but our current hardware returns 0
    // for the initial value.  Oops!
    static final int INITIAL_SCREEN_BRIGHTNESS = 255;
    static final int INITIAL_BUTTON_BRIGHTNESS = Power.BRIGHTNESS_OFF;
    static final int INITIAL_KEYBOARD_BRIGHTNESS = Power.BRIGHTNESS_OFF;

    private final int MY_UID;
    private final int MY_PID;

    private boolean mDoneBooting = false;
    private boolean mBootCompleted = false;
    private int mStayOnConditions = 0;
    private final int[] mBroadcastQueue = new int[] { -1, -1, -1 };
    private final int[] mBroadcastWhy = new int[3];
    private int mPartialCount = 0;
    private int mPowerState;
    // mScreenOffReason can be WindowManagerPolicy.OFF_BECAUSE_OF_USER,
    // WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT or WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR
    private int mScreenOffReason;
    private int mUserState;
    private boolean mKeyboardVisible = false;
    private boolean mUserActivityAllowed = true;
    private int mProximityWakeLockCount = 0;
    private boolean mProximitySensorEnabled = false;
    private boolean mProximitySensorActive = false;
    private int mProximityPendingValue = -1; // -1 == nothing, 0 == inactive, 1 == active
    private long mLastProximityEventTime;
    private int mScreenOffTimeoutSetting;
    private int mMaximumScreenOffTimeout = Integer.MAX_VALUE;
    private int mKeylightDelay;
    private int mDimDelay;
    private int mScreenOffDelay;
    private int mWakeLockState;
    private long mLastEventTime = 0;
    private long mScreenOffTime;
    private volatile WindowManagerPolicy mPolicy;
    private final LockList mLocks = new LockList();
    private Intent mScreenOffIntent;
    private Intent mScreenOnIntent;
    private LightsService mLightsService;
    private Context mContext;
    private LightsService.Light mLcdLight;
    private LightsService.Light mButtonLight;
    private LightsService.Light mKeyboardLight;
    private LightsService.Light mAttentionLight;
    private UnsynchronizedWakeLock mBroadcastWakeLock;
    private UnsynchronizedWakeLock mStayOnWhilePluggedInScreenDimLock;
    private UnsynchronizedWakeLock mStayOnWhilePluggedInPartialLock;
    private UnsynchronizedWakeLock mPreventScreenOnPartialLock;
    private UnsynchronizedWakeLock mProximityPartialLock;
    private HandlerThread mHandlerThread;
    private HandlerThread mScreenOffThread;
    private Handler mScreenOffHandler;
    private Handler mHandler;
    private final TimeoutTask mTimeoutTask = new TimeoutTask();
    private final BrightnessState mScreenBrightness
            = new BrightnessState(SCREEN_BRIGHT_BIT);
    private boolean mStillNeedSleepNotification;
    private boolean mIsPowered = false;
    private IActivityManager mActivityService;
    private IBatteryStats mBatteryStats;
    private BatteryService mBatteryService;
    private SensorManager mSensorManager;
    private Sensor mProximitySensor;
    private Sensor mLightSensor;
    private boolean mLightSensorEnabled;
    private float mLightSensorValue = -1;
    private boolean mProxIgnoredBecauseScreenTurnedOff = false;
    private int mHighestLightSensorValue = -1;
    private boolean mLightSensorPendingDecrease = false;
    private boolean mLightSensorPendingIncrease = false;
    private float mLightSensorPendingValue = -1;
    private int mLightSensorScreenBrightness = -1;
    private int mLightSensorButtonBrightness = -1;
    private int mLightSensorKeyboardBrightness = -1;
    private boolean mDimScreen = true;
    private boolean mIsDocked = false;
    private long mNextTimeout;
    private volatile int mPokey = 0;
    private volatile boolean mPokeAwakeOnSet = false;
    private volatile boolean mInitComplete = false;
    private final HashMap<IBinder,PokeLock> mPokeLocks = new HashMap<IBinder,PokeLock>();
    // mLastScreenOnTime is the time the screen was last turned on
    private long mLastScreenOnTime;
    private boolean mPreventScreenOn;
    private int mScreenBrightnessOverride = -1;
    private int mButtonBrightnessOverride = -1;
    private boolean mUseSoftwareAutoBrightness;
    private boolean mAutoBrightessEnabled;
    private int[] mAutoBrightnessLevels;
    private int[] mLcdBacklightValues;
    private int[] mButtonBacklightValues;
    private int[] mKeyboardBacklightValues;
    private int mLightSensorWarmupTime;
    boolean mUnplugTurnsOnScreen;
    private int mWarningSpewThrottleCount;
    private long mWarningSpewThrottleTime;
    private int mAnimationSetting = ANIM_SETTING_OFF;

    // Must match with the ISurfaceComposer constants in C++.
    private static final int ANIM_SETTING_ON = 0x01;
    private static final int ANIM_SETTING_OFF = 0x10;

    // Used when logging number and duration of touch-down cycles
    private long mTotalTouchDownTime;
    private long mLastTouchDown;
    private int mTouchCycles;

    // could be either static or controllable at runtime
    private static final boolean mSpew = false;
    private static final boolean mDebugProximitySensor = (false || mSpew);
    private static final boolean mDebugLightSensor = (false || mSpew);
    
    private native void nativeInit();
    private native void nativeSetPowerState(boolean screenOn, boolean screenBright);
    private native void nativeStartSurfaceFlingerAnimation(int mode);

    /*
    static PrintStream mLog;
    static {
        try {
            mLog = new PrintStream("/data/power.log");
        }
        catch (FileNotFoundException e) {
            android.util.Slog.e(TAG, "Life is hard", e);
        }
    }
    static class Log {
        static void d(String tag, String s) {
            mLog.println(s);
            android.util.Slog.d(tag, s);
        }
        static void i(String tag, String s) {
            mLog.println(s);
            android.util.Slog.i(tag, s);
        }
        static void w(String tag, String s) {
            mLog.println(s);
            android.util.Slog.w(tag, s);
        }
        static void e(String tag, String s) {
            mLog.println(s);
            android.util.Slog.e(tag, s);
        }
    }
    */

    /**
     * This class works around a deadlock between the lock in PowerManager.WakeLock
     * and our synchronizing on mLocks.  PowerManager.WakeLock synchronizes on its
     * mToken object so it can be accessed from any thread, but it calls into here
     * with its lock held.  This class is essentially a reimplementation of
     * PowerManager.WakeLock, but without that extra synchronized block, because we'll
     * only call it with our own locks held.
     */
    private class UnsynchronizedWakeLock {
        int mFlags;
        String mTag;
        IBinder mToken;
        int mCount = 0;
        boolean mRefCounted;
        boolean mHeld;

        UnsynchronizedWakeLock(int flags, String tag, boolean refCounted) {
            mFlags = flags;
            mTag = tag;
            mToken = new Binder();
            mRefCounted = refCounted;
        }

        public void acquire() {
            if (!mRefCounted || mCount++ == 0) {
                long ident = Binder.clearCallingIdentity();
                try {
                    PowerManagerService.this.acquireWakeLockLocked(mFlags, mToken,
                            MY_UID, MY_PID, mTag, null);
                    mHeld = true;
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }

        public void release() {
            if (!mRefCounted || --mCount == 0) {
                PowerManagerService.this.releaseWakeLockLocked(mToken, 0, false);
                mHeld = false;
            }
            if (mCount < 0) {
                throw new RuntimeException("WakeLock under-locked " + mTag);
            }
        }

        public boolean isHeld()
        {
            return mHeld;
        }

        public String toString() {
            return "UnsynchronizedWakeLock(mFlags=0x" + Integer.toHexString(mFlags)
                    + " mCount=" + mCount + " mHeld=" + mHeld + ")";
        }
    }

    private final class BatteryReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            synchronized (mLocks) {
                boolean wasPowered = mIsPowered;
                mIsPowered = mBatteryService.isPowered();

                if (mIsPowered != wasPowered) {
                    // update mStayOnWhilePluggedIn wake lock
                    updateWakeLockLocked();

                    // treat plugging and unplugging the devices as a user activity.
                    // users find it disconcerting when they unplug the device
                    // and it shuts off right away.
                    // to avoid turning on the screen when unplugging, we only trigger
                    // user activity when screen was already on.
                    // temporarily set mUserActivityAllowed to true so this will work
                    // even when the keyguard is on.
                    // However, you can also set config_unplugTurnsOnScreen to have it
                    // turn on.  Some devices want this because they don't have a
                    // charging LED.
                    synchronized (mLocks) {
                        if (!wasPowered || (mPowerState & SCREEN_ON_BIT) != 0 ||
                                mUnplugTurnsOnScreen) {
                            forceUserActivityLocked();
                        }
                    }
                }
            }
        }
    }

    private final class BootCompletedReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            bootCompleted();
        }
    }

    private final class DockReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
            dockStateChanged(state);
        }
    }

    /**
     * Set the setting that determines whether the device stays on when plugged in.
     * The argument is a bit string, with each bit specifying a power source that,
     * when the device is connected to that source, causes the device to stay on.
     * See {@link android.os.BatteryManager} for the list of power sources that
     * can be specified. Current values include {@link android.os.BatteryManager#BATTERY_PLUGGED_AC}
     * and {@link android.os.BatteryManager#BATTERY_PLUGGED_USB}
     * @param val an {@code int} containing the bits that specify which power sources
     * should cause the device to stay on.
     */
    public void setStayOnSetting(int val) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS, null);
        Settings.System.putInt(mContext.getContentResolver(),
                Settings.System.STAY_ON_WHILE_PLUGGED_IN, val);
    }

    public void setMaximumScreenOffTimeount(int timeMs) {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.WRITE_SECURE_SETTINGS, null);
        synchronized (mLocks) {
            mMaximumScreenOffTimeout = timeMs;
            // recalculate everything
            setScreenOffTimeoutsLocked();
        }
    }

    private class SettingsObserver implements Observer {
        private int getInt(String name, int defValue) {
            ContentValues values = mSettings.getValues(name);
            Integer iVal = values != null ? values.getAsInteger(Settings.System.VALUE) : null;
            return iVal != null ? iVal : defValue;
        }

        private float getFloat(String name, float defValue) {
            ContentValues values = mSettings.getValues(name);
            Float fVal = values != null ? values.getAsFloat(Settings.System.VALUE) : null;
            return fVal != null ? fVal : defValue;
        }

        public void update(Observable o, Object arg) {
            synchronized (mLocks) {
                // STAY_ON_WHILE_PLUGGED_IN, default to when plugged into AC
                mStayOnConditions = getInt(STAY_ON_WHILE_PLUGGED_IN,
                        BatteryManager.BATTERY_PLUGGED_AC);
                updateWakeLockLocked();

                // SCREEN_OFF_TIMEOUT, default to 15 seconds
                mScreenOffTimeoutSetting = getInt(SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT);

                // DIM_SCREEN
                //mDimScreen = getInt(DIM_SCREEN) != 0;

                // SCREEN_BRIGHTNESS_MODE, default to manual
                setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE,
                        Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL));

                // recalculate everything
                setScreenOffTimeoutsLocked();

                final float windowScale = getFloat(WINDOW_ANIMATION_SCALE, 1.0f);
                final float transitionScale = getFloat(TRANSITION_ANIMATION_SCALE, 1.0f);
                mAnimationSetting = 0;
                if (windowScale > 0.5f) {
                    mAnimationSetting |= ANIM_SETTING_OFF;
                }
                if (transitionScale > 0.5f) {
                    // Uncomment this if you want the screen-on animation.
                    // mAnimationSetting |= ANIM_SETTING_ON;
                }
            }
        }
    }

    PowerManagerService() {
        // Hack to get our uid...  should have a func for this.
        long token = Binder.clearCallingIdentity();
        MY_UID = Process.myUid();
        MY_PID = Process.myPid();
        Binder.restoreCallingIdentity(token);

        // XXX remove this when the kernel doesn't timeout wake locks
        Power.setLastUserActivityTimeout(7*24*3600*1000); // one week

        // assume nothing is on yet
        mUserState = mPowerState = 0;

        // Add ourself to the Watchdog monitors.
        Watchdog.getInstance().addMonitor(this);
    }

    private ContentQueryMap mSettings;

    void init(Context context, LightsService lights, IActivityManager activity,
            BatteryService battery) {
        mLightsService = lights;
        mContext = context;
        mActivityService = activity;
        mBatteryStats = BatteryStatsService.getService();
        mBatteryService = battery;

        mLcdLight = lights.getLight(LightsService.LIGHT_ID_BACKLIGHT);
        mButtonLight = lights.getLight(LightsService.LIGHT_ID_BUTTONS);
        mKeyboardLight = lights.getLight(LightsService.LIGHT_ID_KEYBOARD);
        mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);

        nativeInit();
        synchronized (mLocks) {
            updateNativePowerStateLocked();
        }

        mInitComplete = false;
        mScreenOffThread = new HandlerThread("PowerManagerService.mScreenOffThread") {
            @Override
            protected void onLooperPrepared() {
                mScreenOffHandler = new Handler();
                synchronized (mScreenOffThread) {
                    mInitComplete = true;
                    mScreenOffThread.notifyAll();
                }
            }
        };
        mScreenOffThread.start();

        synchronized (mScreenOffThread) {
            while (!mInitComplete) {
                try {
                    mScreenOffThread.wait();
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
        }
        
        mInitComplete = false;
        mHandlerThread = new HandlerThread("PowerManagerService") {
            @Override
            protected void onLooperPrepared() {
                super.onLooperPrepared();
                initInThread();
            }
        };
        mHandlerThread.start();

        synchronized (mHandlerThread) {
            while (!mInitComplete) {
                try {
                    mHandlerThread.wait();
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
        }
        
        nativeInit();
        synchronized (mLocks) {
            updateNativePowerStateLocked();
        }
    }

    void initInThread() {
        mHandler = new Handler();

        mBroadcastWakeLock = new UnsynchronizedWakeLock(
                                PowerManager.PARTIAL_WAKE_LOCK, "sleep_broadcast", true);
        mStayOnWhilePluggedInScreenDimLock = new UnsynchronizedWakeLock(
                                PowerManager.SCREEN_DIM_WAKE_LOCK, "StayOnWhilePluggedIn Screen Dim", false);
        mStayOnWhilePluggedInPartialLock = new UnsynchronizedWakeLock(
                                PowerManager.PARTIAL_WAKE_LOCK, "StayOnWhilePluggedIn Partial", false);
        mPreventScreenOnPartialLock = new UnsynchronizedWakeLock(
                                PowerManager.PARTIAL_WAKE_LOCK, "PreventScreenOn Partial", false);
        mProximityPartialLock = new UnsynchronizedWakeLock(
                                PowerManager.PARTIAL_WAKE_LOCK, "Proximity Partial", false);

        mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
        mScreenOnIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
        mScreenOffIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);

        Resources resources = mContext.getResources();

        mAnimateScreenLights = resources.getBoolean(
                com.android.internal.R.bool.config_animateScreenLights);

        mUnplugTurnsOnScreen = resources.getBoolean(
                com.android.internal.R.bool.config_unplugTurnsOnScreen);

        // read settings for auto-brightness
        mUseSoftwareAutoBrightness = resources.getBoolean(
                com.android.internal.R.bool.config_automatic_brightness_available);
        if (mUseSoftwareAutoBrightness) {
            mAutoBrightnessLevels = resources.getIntArray(
                    com.android.internal.R.array.config_autoBrightnessLevels);
            mLcdBacklightValues = resources.getIntArray(
                    com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
            mButtonBacklightValues = resources.getIntArray(
                    com.android.internal.R.array.config_autoBrightnessButtonBacklightValues);
            mKeyboardBacklightValues = resources.getIntArray(
                    com.android.internal.R.array.config_autoBrightnessKeyboardBacklightValues);
            mLightSensorWarmupTime = resources.getInteger(
                    com.android.internal.R.integer.config_lightSensorWarmupTime);
        }

       ContentResolver resolver = mContext.getContentResolver();
        Cursor settingsCursor = resolver.query(Settings.System.CONTENT_URI, null,
                "(" + Settings.System.NAME + "=?) or ("
                        + Settings.System.NAME + "=?) or ("
                        + Settings.System.NAME + "=?) or ("
                        + Settings.System.NAME + "=?) or ("
                        + Settings.System.NAME + "=?) or ("
                        + Settings.System.NAME + "=?)",
                new String[]{STAY_ON_WHILE_PLUGGED_IN, SCREEN_OFF_TIMEOUT, DIM_SCREEN,
                        SCREEN_BRIGHTNESS_MODE, WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE},
                null);
        mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mHandler);
        SettingsObserver settingsObserver = new SettingsObserver();
        mSettings.addObserver(settingsObserver);

        // pretend that the settings changed so we will get their initial state
        settingsObserver.update(mSettings, null);

        // register for the battery changed notifications
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
        mContext.registerReceiver(new BatteryReceiver(), filter);
        filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BOOT_COMPLETED);
        mContext.registerReceiver(new BootCompletedReceiver(), filter);
        filter = new IntentFilter();
        filter.addAction(Intent.ACTION_DOCK_EVENT);
        mContext.registerReceiver(new DockReceiver(), filter);

        // Listen for secure settings changes
        mContext.getContentResolver().registerContentObserver(
            Settings.Secure.CONTENT_URI, true,
            new ContentObserver(new Handler()) {
                public void onChange(boolean selfChange) {
                    updateSettingsValues();
                }
            });
        updateSettingsValues();

        synchronized (mHandlerThread) {
            mInitComplete = true;
            mHandlerThread.notifyAll();
        }
    }

    private class WakeLock implements IBinder.DeathRecipient
    {
        WakeLock(int f, IBinder b, String t, int u, int p) {
            super();
            flags = f;
            binder = b;
            tag = t;
            uid = u == MY_UID ? Process.SYSTEM_UID : u;
            pid = p;
            if (u != MY_UID || (
                    !"KEEP_SCREEN_ON_FLAG".equals(tag)
                    && !"KeyInputQueue".equals(tag))) {
                monitorType = (f & LOCK_MASK) == PowerManager.PARTIAL_WAKE_LOCK
                        ? BatteryStats.WAKE_TYPE_PARTIAL
                        : BatteryStats.WAKE_TYPE_FULL;
            } else {
                monitorType = -1;
            }
            try {
                b.linkToDeath(this, 0);
            } catch (RemoteException e) {
                binderDied();
            }
        }
        public void binderDied() {
            synchronized (mLocks) {
                releaseWakeLockLocked(this.binder, 0, true);
            }
        }
        final int flags;
        final IBinder binder;
        final String tag;
        final int uid;
        final int pid;
        final int monitorType;
        WorkSource ws;
        boolean activated = true;
        int minState;
    }

    private void updateWakeLockLocked() {
        if (mStayOnConditions != 0 && mBatteryService.isPowered(mStayOnConditions)) {
            // keep the device on if we're plugged in and mStayOnWhilePluggedIn is set.
            mStayOnWhilePluggedInScreenDimLock.acquire();
            mStayOnWhilePluggedInPartialLock.acquire();
        } else {
            mStayOnWhilePluggedInScreenDimLock.release();
            mStayOnWhilePluggedInPartialLock.release();
        }
    }

    private boolean isScreenLock(int flags)
    {
        int n = flags & LOCK_MASK;
        return n == PowerManager.FULL_WAKE_LOCK
                || n == PowerManager.SCREEN_BRIGHT_WAKE_LOCK
                || n == PowerManager.SCREEN_DIM_WAKE_LOCK
                || n == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK;
    }

    void enforceWakeSourcePermission(int uid, int pid) {
        if (uid == Process.myUid()) {
            return;
        }
        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
                pid, uid, null);
    }

    public void acquireWakeLock(int flags, IBinder lock, String tag, WorkSource ws) {
        int uid = Binder.getCallingUid();
        int pid = Binder.getCallingPid();
        if (uid != Process.myUid()) {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
        }
        if (ws != null) {
            enforceWakeSourcePermission(uid, pid);
        }
        long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mLocks) {
                acquireWakeLockLocked(flags, lock, uid, pid, tag, ws);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    void noteStartWakeLocked(WakeLock wl, WorkSource ws) {
        if (wl.monitorType >= 0) {
            long origId = Binder.clearCallingIdentity();
            try {
                if (ws != null) {
                    mBatteryStats.noteStartWakelockFromSource(ws, wl.pid, wl.tag,
                            wl.monitorType);
                } else {
                    mBatteryStats.noteStartWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType);
                }
            } catch (RemoteException e) {
                // Ignore
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
        }
    }

    void noteStopWakeLocked(WakeLock wl, WorkSource ws) {
        if (wl.monitorType >= 0) {
            long origId = Binder.clearCallingIdentity();
            try {
                if (ws != null) {
                    mBatteryStats.noteStopWakelockFromSource(ws, wl.pid, wl.tag,
                            wl.monitorType);
                } else {
                    mBatteryStats.noteStopWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType);
                }
            } catch (RemoteException e) {
                // Ignore
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
        }
    }

    public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag,
            WorkSource ws) {
        if (mSpew) {
            Slog.d(TAG, "acquireWakeLock flags=0x" + Integer.toHexString(flags) + " tag=" + tag);
        }

        if (ws != null && ws.size() == 0) {
            ws = null;
        }

        int index = mLocks.getIndex(lock);
        WakeLock wl;
        boolean newlock;
        boolean diffsource;
        WorkSource oldsource;
        if (index < 0) {
            wl = new WakeLock(flags, lock, tag, uid, pid);
            switch (wl.flags & LOCK_MASK)
            {
                case PowerManager.FULL_WAKE_LOCK:
                    if (mUseSoftwareAutoBrightness) {
                        wl.minState = SCREEN_BRIGHT;
                    } else {
                        wl.minState = (mKeyboardVisible ? ALL_BRIGHT : SCREEN_BUTTON_BRIGHT);
                    }
                    break;
                case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
                    wl.minState = SCREEN_BRIGHT;
                    break;
                case PowerManager.SCREEN_DIM_WAKE_LOCK:
                    wl.minState = SCREEN_DIM;
                    break;
                case PowerManager.PARTIAL_WAKE_LOCK:
                case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
                    break;
                default:
                    // just log and bail.  we're in the server, so don't
                    // throw an exception.
                    Slog.e(TAG, "bad wakelock type for lock '" + tag + "' "
                            + " flags=" + flags);
                    return;
            }
            mLocks.addLock(wl);
            if (ws != null) {
                wl.ws = new WorkSource(ws);
            }
            newlock = true;
            diffsource = false;
            oldsource = null;
        } else {
            wl = mLocks.get(index);
            newlock = false;
            oldsource = wl.ws;
            if (oldsource != null) {
                if (ws == null) {
                    wl.ws = null;
                    diffsource = true;
                } else {
                    diffsource = oldsource.diff(ws);
                }
            } else if (ws != null) {
                diffsource = true;
            } else {
                diffsource = false;
            }
            if (diffsource) {
                wl.ws = new WorkSource(ws);
            }
        }
        if (isScreenLock(flags)) {
            // if this causes a wakeup, we reactivate all of the locks and
            // set it to whatever they want.  otherwise, we modulate that
            // by the current state so we never turn it more on than
            // it already is.
            if ((flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) {
                mProximityWakeLockCount++;
                if (mProximityWakeLockCount == 1) {
                    enableProximityLockLocked();
                }
            } else {
                if ((wl.flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) {
                    int oldWakeLockState = mWakeLockState;
                    mWakeLockState = mLocks.reactivateScreenLocksLocked();
                    if (mSpew) {
                        Slog.d(TAG, "wakeup here mUserState=0x" + Integer.toHexString(mUserState)
                                + " mWakeLockState=0x"
                                + Integer.toHexString(mWakeLockState)
                                + " previous wakeLockState=0x"
                                + Integer.toHexString(oldWakeLockState));
                    }
                } else {
                    if (mSpew) {
                        Slog.d(TAG, "here mUserState=0x" + Integer.toHexString(mUserState)
                                + " mLocks.gatherState()=0x"
                                + Integer.toHexString(mLocks.gatherState())
                                + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState));
                    }
                    mWakeLockState = (mUserState | mWakeLockState) & mLocks.gatherState();
                }
                setPowerState(mWakeLockState | mUserState);
            }
        }
        else if ((flags & LOCK_MASK) == PowerManager.PARTIAL_WAKE_LOCK) {
            if (newlock) {
                mPartialCount++;
                if (mPartialCount == 1) {
                    if (LOG_PARTIAL_WL) EventLog.writeEvent(EventLogTags.POWER_PARTIAL_WAKE_STATE, 1, tag);
                }
            }
            Power.acquireWakeLock(Power.PARTIAL_WAKE_LOCK,PARTIAL_NAME);
        }

        if (diffsource) {
            // If the lock sources have changed, need to first release the
            // old ones.
            noteStopWakeLocked(wl, oldsource);
        }
        if (newlock || diffsource) {
            noteStartWakeLocked(wl, ws);
        }
    }

    public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) {
        int uid = Binder.getCallingUid();
        int pid = Binder.getCallingPid();
        if (ws != null && ws.size() == 0) {
            ws = null;
        }
        if (ws != null) {
            enforceWakeSourcePermission(uid, pid);
        }
        synchronized (mLocks) {
            int index = mLocks.getIndex(lock);
            if (index < 0) {
                throw new IllegalArgumentException("Wake lock not active");
            }
            WakeLock wl = mLocks.get(index);
            WorkSource oldsource = wl.ws;
            wl.ws = ws != null ? new WorkSource(ws) : null;
            noteStopWakeLocked(wl, oldsource);
            noteStartWakeLocked(wl, ws);
        }
    }

    public void releaseWakeLock(IBinder lock, int flags) {
        int uid = Binder.getCallingUid();
        if (uid != Process.myUid()) {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
        }

        synchronized (mLocks) {
            releaseWakeLockLocked(lock, flags, false);
        }
    }

    private void releaseWakeLockLocked(IBinder lock, int flags, boolean death) {
        WakeLock wl = mLocks.removeLock(lock);
        if (wl == null) {
            return;
        }

        if (mSpew) {
            Slog.d(TAG, "releaseWakeLock flags=0x"
                    + Integer.toHexString(wl.flags) + " tag=" + wl.tag);
        }

        if (isScreenLock(wl.flags)) {
            if ((wl.flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) {
                mProximityWakeLockCount--;
                if (mProximityWakeLockCount == 0) {
                    if (mProximitySensorActive &&
                            ((flags & PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE) != 0)) {
                        // wait for proximity sensor to go negative before disabling sensor
                        if (mDebugProximitySensor) {
                            Slog.d(TAG, "waiting for proximity sensor to go negative");
                        }
                    } else {
                        disableProximityLockLocked();
                    }
                }
            } else {
                mWakeLockState = mLocks.gatherState();
                // goes in the middle to reduce flicker
                if ((wl.flags & PowerManager.ON_AFTER_RELEASE) != 0) {
                    userActivity(SystemClock.uptimeMillis(), -1, false, OTHER_EVENT, false);
                }
                setPowerState(mWakeLockState | mUserState);
            }
        }
        else if ((wl.flags & LOCK_MASK) == PowerManager.PARTIAL_WAKE_LOCK) {
            mPartialCount--;
            if (mPartialCount == 0) {
                if (LOG_PARTIAL_WL) EventLog.writeEvent(EventLogTags.POWER_PARTIAL_WAKE_STATE, 0, wl.tag);
                Power.releaseWakeLock(PARTIAL_NAME);
            }
        }
        // Unlink the lock from the binder.
        wl.binder.unlinkToDeath(wl, 0);

        noteStopWakeLocked(wl, wl.ws);
    }

    private class PokeLock implements IBinder.DeathRecipient
    {
        PokeLock(int p, IBinder b, String t) {
            super();
            this.pokey = p;
            this.binder = b;
            this.tag = t;
            try {
                b.linkToDeath(this, 0);
            } catch (RemoteException e) {
                binderDied();
            }
        }
        public void binderDied() {
            setPokeLock(0, this.binder, this.tag);
        }
        int pokey;
        IBinder binder;
        String tag;
        boolean awakeOnSet;
    }

    public void setPokeLock(int pokey, IBinder token, String tag) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
        if (token == null) {
            Slog.e(TAG, "setPokeLock got null token for tag='" + tag + "'");
            return;
        }

        if ((pokey & POKE_LOCK_TIMEOUT_MASK) == POKE_LOCK_TIMEOUT_MASK) {
            throw new IllegalArgumentException("setPokeLock can't have both POKE_LOCK_SHORT_TIMEOUT"
                    + " and POKE_LOCK_MEDIUM_TIMEOUT");
        }

        synchronized (mLocks) {
            if (pokey != 0) {
                PokeLock p = mPokeLocks.get(token);
                int oldPokey = 0;
                if (p != null) {
                    oldPokey = p.pokey;
                    p.pokey = pokey;
                } else {
                    p = new PokeLock(pokey, token, tag);
                    mPokeLocks.put(token, p);
                }
                int oldTimeout = oldPokey & POKE_LOCK_TIMEOUT_MASK;
                int newTimeout = pokey & POKE_LOCK_TIMEOUT_MASK;
                if (((mPowerState & SCREEN_ON_BIT) == 0) && (oldTimeout != newTimeout)) {
                    p.awakeOnSet = true;
                }
            } else {
                PokeLock rLock = mPokeLocks.remove(token);
                if (rLock != null) {
                    token.unlinkToDeath(rLock, 0);
                }
            }

            int oldPokey = mPokey;
            int cumulative = 0;
            boolean oldAwakeOnSet = mPokeAwakeOnSet;
            boolean awakeOnSet = false;
            for (PokeLock p: mPokeLocks.values()) {
                cumulative |= p.pokey;
                if (p.awakeOnSet) {
                    awakeOnSet = true;
                }
            }
            mPokey = cumulative;
            mPokeAwakeOnSet = awakeOnSet;

            int oldCumulativeTimeout = oldPokey & POKE_LOCK_TIMEOUT_MASK;
            int newCumulativeTimeout = pokey & POKE_LOCK_TIMEOUT_MASK;

            if (oldCumulativeTimeout != newCumulativeTimeout) {
                setScreenOffTimeoutsLocked();
                // reset the countdown timer, but use the existing nextState so it doesn't
                // change anything
                setTimeoutLocked(SystemClock.uptimeMillis(), mTimeoutTask.nextState);
            }
        }
    }

    private static String lockType(int type)
    {
        switch (type)
        {
            case PowerManager.FULL_WAKE_LOCK:
                return "FULL_WAKE_LOCK                ";
            case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
                return "SCREEN_BRIGHT_WAKE_LOCK       ";
            case PowerManager.SCREEN_DIM_WAKE_LOCK:
                return "SCREEN_DIM_WAKE_LOCK          ";
            case PowerManager.PARTIAL_WAKE_LOCK:
                return "PARTIAL_WAKE_LOCK             ";
            case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
                return "PROXIMITY_SCREEN_OFF_WAKE_LOCK";
            default:
                return "???                           ";
        }
    }

    private static String dumpPowerState(int state) {
        return (((state & KEYBOARD_BRIGHT_BIT) != 0)
                        ? "KEYBOARD_BRIGHT_BIT " : "")
                + (((state & SCREEN_BRIGHT_BIT) != 0)
                        ? "SCREEN_BRIGHT_BIT " : "")
                + (((state & SCREEN_ON_BIT) != 0)
                        ? "SCREEN_ON_BIT " : "")
                + (((state & BATTERY_LOW_BIT) != 0)
                        ? "BATTERY_LOW_BIT " : "");
    }

    @Override
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                != PackageManager.PERMISSION_GRANTED) {
            pw.println("Permission Denial: can't dump PowerManager from from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid());
            return;
        }

        long now = SystemClock.uptimeMillis();

        synchronized (mLocks) {
            pw.println("Power Manager State:");
            pw.println("  mIsPowered=" + mIsPowered
                    + " mPowerState=" + mPowerState
                    + " mScreenOffTime=" + (SystemClock.elapsedRealtime()-mScreenOffTime)
                    + " ms");
            pw.println("  mPartialCount=" + mPartialCount);
            pw.println("  mWakeLockState=" + dumpPowerState(mWakeLockState));
            pw.println("  mUserState=" + dumpPowerState(mUserState));
            pw.println("  mPowerState=" + dumpPowerState(mPowerState));
            pw.println("  mLocks.gather=" + dumpPowerState(mLocks.gatherState()));
            pw.println("  mNextTimeout=" + mNextTimeout + " now=" + now
                    + " " + ((mNextTimeout-now)/1000) + "s from now");
            pw.println("  mDimScreen=" + mDimScreen
                    + " mStayOnConditions=" + mStayOnConditions);
            pw.println("  mScreenOffReason=" + mScreenOffReason
                    + " mUserState=" + mUserState);
            pw.println("  mBroadcastQueue={" + mBroadcastQueue[0] + ',' + mBroadcastQueue[1]
                    + ',' + mBroadcastQueue[2] + "}");
            pw.println("  mBroadcastWhy={" + mBroadcastWhy[0] + ',' + mBroadcastWhy[1]
                    + ',' + mBroadcastWhy[2] + "}");
            pw.println("  mPokey=" + mPokey + " mPokeAwakeonSet=" + mPokeAwakeOnSet);
            pw.println("  mKeyboardVisible=" + mKeyboardVisible
                    + " mUserActivityAllowed=" + mUserActivityAllowed);
            pw.println("  mKeylightDelay=" + mKeylightDelay + " mDimDelay=" + mDimDelay
                    + " mScreenOffDelay=" + mScreenOffDelay);
            pw.println("  mPreventScreenOn=" + mPreventScreenOn
                    + "  mScreenBrightnessOverride=" + mScreenBrightnessOverride
                    + "  mButtonBrightnessOverride=" + mButtonBrightnessOverride);
            pw.println("  mScreenOffTimeoutSetting=" + mScreenOffTimeoutSetting
                    + " mMaximumScreenOffTimeout=" + mMaximumScreenOffTimeout);
            pw.println("  mLastScreenOnTime=" + mLastScreenOnTime);
            pw.println("  mBroadcastWakeLock=" + mBroadcastWakeLock);
            pw.println("  mStayOnWhilePluggedInScreenDimLock=" + mStayOnWhilePluggedInScreenDimLock);
            pw.println("  mStayOnWhilePluggedInPartialLock=" + mStayOnWhilePluggedInPartialLock);
            pw.println("  mPreventScreenOnPartialLock=" + mPreventScreenOnPartialLock);
            pw.println("  mProximityPartialLock=" + mProximityPartialLock);
            pw.println("  mProximityWakeLockCount=" + mProximityWakeLockCount);
            pw.println("  mProximitySensorEnabled=" + mProximitySensorEnabled);
            pw.println("  mProximitySensorActive=" + mProximitySensorActive);
            pw.println("  mProximityPendingValue=" + mProximityPendingValue);
            pw.println("  mLastProximityEventTime=" + mLastProximityEventTime);
            pw.println("  mLightSensorEnabled=" + mLightSensorEnabled);
            pw.println("  mLightSensorValue=" + mLightSensorValue
                    + " mLightSensorPendingValue=" + mLightSensorPendingValue);
            pw.println("  mLightSensorPendingDecrease=" + mLightSensorPendingDecrease
                    + " mLightSensorPendingIncrease=" + mLightSensorPendingIncrease);
            pw.println("  mLightSensorScreenBrightness=" + mLightSensorScreenBrightness
                    + " mLightSensorButtonBrightness=" + mLightSensorButtonBrightness
                    + " mLightSensorKeyboardBrightness=" + mLightSensorKeyboardBrightness);
            pw.println("  mUseSoftwareAutoBrightness=" + mUseSoftwareAutoBrightness);
            pw.println("  mAutoBrightessEnabled=" + mAutoBrightessEnabled);
            mScreenBrightness.dump(pw, "  mScreenBrightness: ");

            int N = mLocks.size();
            pw.println();
            pw.println("mLocks.size=" + N + ":");
            for (int i=0; i<N; i++) {
                WakeLock wl = mLocks.get(i);
                String type = lockType(wl.flags & LOCK_MASK);
                String acquireCausesWakeup = "";
                if ((wl.flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) {
                    acquireCausesWakeup = "ACQUIRE_CAUSES_WAKEUP ";
                }
                String activated = "";
                if (wl.activated) {
                   activated = " activated";
                }
                pw.println("  " + type + " '" + wl.tag + "'" + acquireCausesWakeup
                        + activated + " (minState=" + wl.minState + ", uid=" + wl.uid
                        + ", pid=" + wl.pid + ")");
            }

            pw.println();
            pw.println("mPokeLocks.size=" + mPokeLocks.size() + ":");
            for (PokeLock p: mPokeLocks.values()) {
                pw.println("    poke lock '" + p.tag + "':"
                        + ((p.pokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0
                                ? " POKE_LOCK_IGNORE_TOUCH_EVENTS" : "")
                        + ((p.pokey & POKE_LOCK_SHORT_TIMEOUT) != 0
                                ? " POKE_LOCK_SHORT_TIMEOUT" : "")
                        + ((p.pokey & POKE_LOCK_MEDIUM_TIMEOUT) != 0
                                ? " POKE_LOCK_MEDIUM_TIMEOUT" : ""));
            }

            pw.println();
        }
    }

    private void setTimeoutLocked(long now, int nextState) {
        setTimeoutLocked(now, -1, nextState);
    }

    // If they gave a timeoutOverride it is the number of seconds
    // to screen-off.  Figure out where in the countdown cycle we
    // should jump to.
    private void setTimeoutLocked(long now, final long originalTimeoutOverride, int nextState) {
        long timeoutOverride = originalTimeoutOverride;
        if (mBootCompleted) {
            synchronized (mLocks) {
                long when = 0;
                if (timeoutOverride <= 0) {
                    switch (nextState)
                    {
                        case SCREEN_BRIGHT:
                            when = now + mKeylightDelay;
                            break;
                        case SCREEN_DIM:
                            if (mDimDelay >= 0) {
                                when = now + mDimDelay;
                                break;
                            } else {
                                Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim");
                            }
                       case SCREEN_OFF:
                            synchronized (mLocks) {
                                when = now + mScreenOffDelay;
                            }
                            break;
                        default:
                            when = now;
                            break;
                    }
                } else {
                    override: {
                        if (timeoutOverride <= mScreenOffDelay) {
                            when = now + timeoutOverride;
                            nextState = SCREEN_OFF;
                            break override;
                        }
                        timeoutOverride -= mScreenOffDelay;

                        if (mDimDelay >= 0) {
                             if (timeoutOverride <= mDimDelay) {
                                when = now + timeoutOverride;
                                nextState = SCREEN_DIM;
                                break override;
                            }
                            timeoutOverride -= mDimDelay;
                        }

                        when = now + timeoutOverride;
                        nextState = SCREEN_BRIGHT;
                    }
                }
                if (mSpew) {
                    Slog.d(TAG, "setTimeoutLocked now=" + now
                            + " timeoutOverride=" + timeoutOverride
                            + " nextState=" + nextState + " when=" + when);
                }

                mHandler.removeCallbacks(mTimeoutTask);
                mTimeoutTask.nextState = nextState;
                mTimeoutTask.remainingTimeoutOverride = timeoutOverride > 0
                        ? (originalTimeoutOverride - timeoutOverride)
                        : -1;
                mHandler.postAtTime(mTimeoutTask, when);
                mNextTimeout = when; // for debugging
            }
        }
    }

    private void cancelTimerLocked()
    {
        mHandler.removeCallbacks(mTimeoutTask);
        mTimeoutTask.nextState = -1;
    }

    private class TimeoutTask implements Runnable
    {
        int nextState; // access should be synchronized on mLocks
        long remainingTimeoutOverride;
        public void run()
        {
            synchronized (mLocks) {
                if (mSpew) {
                    Slog.d(TAG, "user activity timeout timed out nextState=" + this.nextState);
                }

                if (nextState == -1) {
                    return;
                }

                mUserState = this.nextState;
                setPowerState(this.nextState | mWakeLockState);

                long now = SystemClock.uptimeMillis();

                switch (this.nextState)
                {
                    case SCREEN_BRIGHT:
                        if (mDimDelay >= 0) {
                            setTimeoutLocked(now, remainingTimeoutOverride, SCREEN_DIM);
                            break;
                        }
                    case SCREEN_DIM:
                        setTimeoutLocked(now, remainingTimeoutOverride, SCREEN_OFF);
                        break;
                }
            }
        }
    }

    private void sendNotificationLocked(boolean on, int why)
    {
        if (!on) {
            mStillNeedSleepNotification = false;
        }

        // Add to the queue.
        int index = 0;
        while (mBroadcastQueue[index] != -1) {
            index++;
        }
        mBroadcastQueue[index] = on ? 1 : 0;
        mBroadcastWhy[index] = why;

        // If we added it position 2, then there is a pair that can be stripped.
        // If we added it position 1 and we're turning the screen off, we can strip
        // the pair and do nothing, because the screen is already off, and therefore
        // keyguard has already been enabled.
        // However, if we added it at position 1 and we're turning it on, then position
        // 0 was to turn it off, and we can't strip that, because keyguard needs to come
        // on, so have to run the queue then.
        if (index == 2) {
            // While we're collapsing them, if it's going off, and the new reason
            // is more significant than the first, then use the new one.
            if (!on && mBroadcastWhy[0] > why) {
                mBroadcastWhy[0] = why;
            }
            mBroadcastQueue[0] = on ? 1 : 0;
            mBroadcastQueue[1] = -1;
            mBroadcastQueue[2] = -1;
            mBroadcastWakeLock.release();
            mBroadcastWakeLock.release();
            index = 0;
        }
        if (index == 1 && !on) {
            mBroadcastQueue[0] = -1;
            mBroadcastQueue[1] = -1;
            index = -1;
            // The wake lock was being held, but we're not actually going to do any
            // broadcasts, so release the wake lock.
            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 1, mBroadcastWakeLock.mCount);
            mBroadcastWakeLock.release();
        }

        // Now send the message.
        if (index >= 0) {
            // Acquire the broadcast wake lock before changing the power
            // state. It will be release after the broadcast is sent.
            // We always increment the ref count for each notification in the queue
            // and always decrement when that notification is handled.
            mBroadcastWakeLock.acquire();
            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, mBroadcastWakeLock.mCount);
            mHandler.post(mNotificationTask);
        }
    }

    private Runnable mNotificationTask = new Runnable()
    {
        public void run()
        {
            while (true) {
                int value;
                int why;
                WindowManagerPolicy policy;
                synchronized (mLocks) {
                    value = mBroadcastQueue[0];
                    why = mBroadcastWhy[0];
                    for (int i=0; i<2; i++) {
                        mBroadcastQueue[i] = mBroadcastQueue[i+1];
                        mBroadcastWhy[i] = mBroadcastWhy[i+1];
                    }
                    policy = getPolicyLocked();
                }
                if (value == 1) {
                    mScreenOnStart = SystemClock.uptimeMillis();

                    policy.screenTurnedOn();
                    try {
                        ActivityManagerNative.getDefault().wakingUp();
                    } catch (RemoteException e) {
                        // ignore it
                    }

                    if (mSpew) {
                        Slog.d(TAG, "mBroadcastWakeLock=" + mBroadcastWakeLock);
                    }
                    if (mContext != null && ActivityManagerNative.isSystemReady()) {
                        mContext.sendOrderedBroadcast(mScreenOnIntent, null,
                                mScreenOnBroadcastDone, mHandler, 0, null, null);
                    } else {
                        synchronized (mLocks) {
                            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2,
                                    mBroadcastWakeLock.mCount);
                            mBroadcastWakeLock.release();
                        }
                    }
                }
                else if (value == 0) {
                    mScreenOffStart = SystemClock.uptimeMillis();

                    policy.screenTurnedOff(why);
                    try {
                        ActivityManagerNative.getDefault().goingToSleep();
                    } catch (RemoteException e) {
                        // ignore it.
                    }

                    if (mContext != null && ActivityManagerNative.isSystemReady()) {
                        mContext.sendOrderedBroadcast(mScreenOffIntent, null,
                                mScreenOffBroadcastDone, mHandler, 0, null, null);
                    } else {
                        synchronized (mLocks) {
                            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3,
                                    mBroadcastWakeLock.mCount);
                            mBroadcastWakeLock.release();
                        }
                    }
                }
                else {
                    // If we're in this case, then this handler is running for a previous
                    // paired transaction.  mBroadcastWakeLock will already have been released.
                    break;
                }
            }
        }
    };

    long mScreenOnStart;
    private BroadcastReceiver mScreenOnBroadcastDone = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            synchronized (mLocks) {
                EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 1,
                        SystemClock.uptimeMillis() - mScreenOnStart, mBroadcastWakeLock.mCount);
                mBroadcastWakeLock.release();
            }
        }
    };

    long mScreenOffStart;
    private BroadcastReceiver mScreenOffBroadcastDone = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            synchronized (mLocks) {
                EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 0,
                        SystemClock.uptimeMillis() - mScreenOffStart, mBroadcastWakeLock.mCount);
                mBroadcastWakeLock.release();
            }
        }
    };

    void logPointerUpEvent() {
        if (LOG_TOUCH_DOWNS) {
            mTotalTouchDownTime += SystemClock.elapsedRealtime() - mLastTouchDown;
            mLastTouchDown = 0;
        }
    }

    void logPointerDownEvent() {
        if (LOG_TOUCH_DOWNS) {
            // If we are not already timing a down/up sequence
            if (mLastTouchDown == 0) {
                mLastTouchDown = SystemClock.elapsedRealtime();
                mTouchCycles++;
            }
        }
    }

    /**
     * Prevents the screen from turning on even if it *should* turn on due
     * to a subsequent full wake lock being acquired.
     * <p>
     * This is a temporary hack that allows an activity to "cover up" any
     * display glitches that happen during the activity's startup
     * sequence.  (Specifically, this API was added to work around a
     * cosmetic bug in the "incoming call" sequence, where the lock screen
     * would flicker briefly before the incoming call UI became visible.)
     * TODO: There ought to be a more elegant way of doing this,
     * probably by having the PowerManager and ActivityManager
     * work together to let apps specify that the screen on/off
     * state should be synchronized with the Activity lifecycle.
     * <p>
     * Note that calling preventScreenOn(true) will NOT turn the screen
     * off if it's currently on.  (This API only affects *future*
     * acquisitions of full wake locks.)
     * But calling preventScreenOn(false) WILL turn the screen on if
     * it's currently off because of a prior preventScreenOn(true) call.
     * <p>
     * Any call to preventScreenOn(true) MUST be followed promptly by a call
     * to preventScreenOn(false).  In fact, if the preventScreenOn(false)
     * call doesn't occur within 5 seconds, we'll turn the screen back on
     * ourselves (and log a warning about it); this prevents a buggy app
     * from disabling the screen forever.)
     * <p>
     * TODO: this feature should really be controlled by a new type of poke
     * lock (rather than an IPowerManager call).
     */
    public void preventScreenOn(boolean prevent) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);

        synchronized (mLocks) {
            if (prevent) {
                // First of all, grab a partial wake lock to
                // make sure the CPU stays on during the entire
                // preventScreenOn(true) -> preventScreenOn(false) sequence.
                mPreventScreenOnPartialLock.acquire();

                // Post a forceReenableScreen() call (for 5 seconds in the
                // future) to make sure the matching preventScreenOn(false) call
                // has happened by then.
                mHandler.removeCallbacks(mForceReenableScreenTask);
                mHandler.postDelayed(mForceReenableScreenTask, 5000);

                // Finally, set the flag that prevents the screen from turning on.
                // (Below, in setPowerState(), we'll check mPreventScreenOn and
                // we *won't* call setScreenStateLocked(true) if it's set.)
                mPreventScreenOn = true;
            } else {
                // (Re)enable the screen.
                mPreventScreenOn = false;

                // We're "undoing" a the prior preventScreenOn(true) call, so we
                // no longer need the 5-second safeguard.
                mHandler.removeCallbacks(mForceReenableScreenTask);

                // Forcibly turn on the screen if it's supposed to be on.  (This
                // handles the case where the screen is currently off because of
                // a prior preventScreenOn(true) call.)
                if (!mProximitySensorActive && (mPowerState & SCREEN_ON_BIT) != 0) {
                    if (mSpew) {
                        Slog.d(TAG,
                              "preventScreenOn: turning on after a prior preventScreenOn(true)!");
                    }
                    int err = setScreenStateLocked(true);
                    if (err != 0) {
                        Slog.w(TAG, "preventScreenOn: error from setScreenStateLocked(): " + err);
                    }
                }

                // Release the partial wake lock that we held during the
                // preventScreenOn(true) -> preventScreenOn(false) sequence.
                mPreventScreenOnPartialLock.release();
            }
        }
    }

    public void setScreenBrightnessOverride(int brightness) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);

        if (mSpew) Slog.d(TAG, "setScreenBrightnessOverride " + brightness);
        synchronized (mLocks) {
            if (mScreenBrightnessOverride != brightness) {
                mScreenBrightnessOverride = brightness;
                if (isScreenOn()) {
                    updateLightsLocked(mPowerState, SCREEN_ON_BIT);
                }
            }
        }
    }

    public void setButtonBrightnessOverride(int brightness) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);

        if (mSpew) Slog.d(TAG, "setButtonBrightnessOverride " + brightness);
         synchronized (mLocks) {
           if (mButtonBrightnessOverride != brightness) {
                mButtonBrightnessOverride = brightness;
                if (isScreenOn()) {
                    updateLightsLocked(mPowerState, BUTTON_BRIGHT_BIT | KEYBOARD_BRIGHT_BIT);
                }
            }
        }
    }

    /**
     * Sanity-check that gets called 5 seconds after any call to
     * preventScreenOn(true).  This ensures that the original call
     * is followed promptly by a call to preventScreenOn(false).
     */
    private void forceReenableScreen() {
        // We shouldn't get here at all if mPreventScreenOn is false, since
        // we should have already removed any existing
        // mForceReenableScreenTask messages...
        if (!mPreventScreenOn) {
            Slog.w(TAG, "forceReenableScreen: mPreventScreenOn is false, nothing to do");
            return;
        }

        // Uh oh.  It's been 5 seconds since a call to
        // preventScreenOn(true) and we haven't re-enabled the screen yet.
        // This means the app that called preventScreenOn(true) is either
        // slow (i.e. it took more than 5 seconds to call preventScreenOn(false)),
        // or buggy (i.e. it forgot to call preventScreenOn(false), or
        // crashed before doing so.)

        // Log a warning, and forcibly turn the screen back on.
        Slog.w(TAG, "App called preventScreenOn(true) but didn't promptly reenable the screen! "
              + "Forcing the screen back on...");
        preventScreenOn(false);
    }

    private Runnable mForceReenableScreenTask = new Runnable() {
            public void run() {
                forceReenableScreen();
            }
        };

    private int setScreenStateLocked(boolean on) {
        int err = Power.setScreenState(on);
        if (err == 0) {
            mLastScreenOnTime = (on ? SystemClock.elapsedRealtime() : 0);
            if (mUseSoftwareAutoBrightness) {
                enableLightSensorLocked(on);
                if (!on) {
                    // make sure button and key backlights are off too
                    mButtonLight.turnOff();
                    mKeyboardLight.turnOff();
                    // clear current value so we will update based on the new conditions
                    // when the sensor is reenabled.
                    mLightSensorValue = -1;
                    // reset our highest light sensor value when the screen turns off
                    mHighestLightSensorValue = -1;
                }
            }
        }
        return err;
    }

    private void setPowerState(int state)
    {
        setPowerState(state, false, WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT);
    }

    private void setPowerState(int newState, boolean noChangeLights, int reason)
    {
        synchronized (mLocks) {
            int err;

            if (mSpew) {
                Slog.d(TAG, "setPowerState: mPowerState=0x" + Integer.toHexString(mPowerState)
                        + " newState=0x" + Integer.toHexString(newState)
                        + " noChangeLights=" + noChangeLights
                        + " reason=" + reason);
            }

            if (noChangeLights) {
                newState = (newState & ~LIGHTS_MASK) | (mPowerState & LIGHTS_MASK);
            }
            if (mProximitySensorActive) {
                // don't turn on the screen when the proximity sensor lock is held
                newState = (newState & ~SCREEN_BRIGHT);
            }

            if (batteryIsLow()) {
                newState |= BATTERY_LOW_BIT;
            } else {
                newState &= ~BATTERY_LOW_BIT;
            }
            if (newState == mPowerState) {
                return;
            }

            if (!mBootCompleted && !mUseSoftwareAutoBrightness) {
                newState |= ALL_BRIGHT;
            }

            boolean oldScreenOn = (mPowerState & SCREEN_ON_BIT) != 0;
            boolean newScreenOn = (newState & SCREEN_ON_BIT) != 0;

            if (mSpew) {
                Slog.d(TAG, "setPowerState: mPowerState=" + mPowerState
                        + " newState=" + newState + " noChangeLights=" + noChangeLights);
                Slog.d(TAG, "  oldKeyboardBright=" + ((mPowerState & KEYBOARD_BRIGHT_BIT) != 0)
                         + " newKeyboardBright=" + ((newState & KEYBOARD_BRIGHT_BIT) != 0));
                Slog.d(TAG, "  oldScreenBright=" + ((mPowerState & SCREEN_BRIGHT_BIT) != 0)
                         + " newScreenBright=" + ((newState & SCREEN_BRIGHT_BIT) != 0));
                Slog.d(TAG, "  oldButtonBright=" + ((mPowerState & BUTTON_BRIGHT_BIT) != 0)
                         + " newButtonBright=" + ((newState & BUTTON_BRIGHT_BIT) != 0));
                Slog.d(TAG, "  oldScreenOn=" + oldScreenOn
                         + " newScreenOn=" + newScreenOn);
                Slog.d(TAG, "  oldBatteryLow=" + ((mPowerState & BATTERY_LOW_BIT) != 0)
                         + " newBatteryLow=" + ((newState & BATTERY_LOW_BIT) != 0));
            }

            if (mPowerState != newState) {
                updateLightsLocked(newState, 0);
                mPowerState = (mPowerState & ~LIGHTS_MASK) | (newState & LIGHTS_MASK);
            }

            if (oldScreenOn != newScreenOn) {
                if (newScreenOn) {
                    // When the user presses the power button, we need to always send out the
                    // notification that it's going to sleep so the keyguard goes on.  But
                    // we can't do that until the screen fades out, so we don't show the keyguard
                    // too early.
                    if (mStillNeedSleepNotification) {
                        sendNotificationLocked(false, WindowManagerPolicy.OFF_BECAUSE_OF_USER);
                    }

                    // Turn on the screen UNLESS there was a prior
                    // preventScreenOn(true) request.  (Note that the lifetime
                    // of a single preventScreenOn() request is limited to 5
                    // seconds to prevent a buggy app from disabling the
                    // screen forever; see forceReenableScreen().)
                    boolean reallyTurnScreenOn = true;
                    if (mSpew) {
                        Slog.d(TAG, "- turning screen on...  mPreventScreenOn = "
                              + mPreventScreenOn);
                    }

                    if (mPreventScreenOn) {
                        if (mSpew) {
                            Slog.d(TAG, "- PREVENTING screen from really turning on!");
                        }
                        reallyTurnScreenOn = false;
                    }
                    if (reallyTurnScreenOn) {
                        err = setScreenStateLocked(true);
                        long identity = Binder.clearCallingIdentity();
                        try {
                            mBatteryStats.noteScreenBrightness(getPreferredBrightness());
                            mBatteryStats.noteScreenOn();
                        } catch (RemoteException e) {
                            Slog.w(TAG, "RemoteException calling noteScreenOn on BatteryStatsService", e);
                        } finally {
                            Binder.restoreCallingIdentity(identity);
                        }
                    } else {
                        setScreenStateLocked(false);
                        // But continue as if we really did turn the screen on...
                        err = 0;
                    }

                    mLastTouchDown = 0;
                    mTotalTouchDownTime = 0;
                    mTouchCycles = 0;
                    EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, reason,
                            mTotalTouchDownTime, mTouchCycles);
                    if (err == 0) {
                        mPowerState |= SCREEN_ON_BIT;
                        sendNotificationLocked(true, -1);
                    }
                } else {
                    // cancel light sensor task
                    mHandler.removeCallbacks(mAutoBrightnessTask);
                    mLightSensorPendingDecrease = false;
                    mLightSensorPendingIncrease = false;
                    mScreenOffTime = SystemClock.elapsedRealtime();
                    long identity = Binder.clearCallingIdentity();
                    try {
                        mBatteryStats.noteScreenOff();
                    } catch (RemoteException e) {
                        Slog.w(TAG, "RemoteException calling noteScreenOff on BatteryStatsService", e);
                    } finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                    mPowerState &= ~SCREEN_ON_BIT;
                    mScreenOffReason = reason;
                    if (!mScreenBrightness.animating) {
                        err = screenOffFinishedAnimatingLocked(reason);
                    } else {
                        err = 0;
                        mLastTouchDown = 0;
                    }
                }
            }
            
            updateNativePowerStateLocked();
        }
    }
    
    private void updateNativePowerStateLocked() {
        nativeSetPowerState(
                (mPowerState & SCREEN_ON_BIT) != 0,
                (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT);
    }

    private int screenOffFinishedAnimatingLocked(int reason) {
        // I don't think we need to check the current state here because all of these
        // Power.setScreenState and sendNotificationLocked can both handle being
        // called multiple times in the same state. -joeo
        EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, reason, mTotalTouchDownTime,
                mTouchCycles);
        mLastTouchDown = 0;
        int err = setScreenStateLocked(false);
        if (err == 0) {
            mScreenOffReason = reason;
            sendNotificationLocked(false, reason);
        }
        return err;
    }

    private boolean batteryIsLow() {
        return (!mIsPowered &&
                mBatteryService.getBatteryLevel() <= Power.LOW_BATTERY_THRESHOLD);
    }

    private void updateLightsLocked(int newState, int forceState) {
        final int oldState = mPowerState;
        if ((newState & SCREEN_ON_BIT) != 0) {
            // Only turn on the buttons or keyboard if the screen is also on.
            // We should never see the buttons on but not the screen.
            newState = applyButtonState(newState);
            newState = applyKeyboardState(newState);
        }
        final int realDifference = (newState ^ oldState);
        final int difference = realDifference | forceState;
        if (difference == 0) {
            return;
        }

        int offMask = 0;
        int dimMask = 0;
        int onMask = 0;

        int preferredBrightness = getPreferredBrightness();

        if ((difference & KEYBOARD_BRIGHT_BIT) != 0) {
            if ((newState & KEYBOARD_BRIGHT_BIT) == 0) {
                offMask |= KEYBOARD_BRIGHT_BIT;
            } else {
                onMask |= KEYBOARD_BRIGHT_BIT;
            }
        }

        if ((difference & BUTTON_BRIGHT_BIT) != 0) {
            if ((newState & BUTTON_BRIGHT_BIT) == 0) {
                offMask |= BUTTON_BRIGHT_BIT;
            } else {
                onMask |= BUTTON_BRIGHT_BIT;
            }
        }

        if ((difference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) {
            int nominalCurrentValue = -1;
            // If there was an actual difference in the light state, then
            // figure out the "ideal" current value based on the previous
            // state.  Otherwise, this is a change due to the brightness
            // override, so we want to animate from whatever the current
            // value is.
            if ((realDifference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) {
                switch (oldState & (SCREEN_BRIGHT_BIT|SCREEN_ON_BIT)) {
                    case SCREEN_BRIGHT_BIT | SCREEN_ON_BIT:
                        nominalCurrentValue = preferredBrightness;
                        break;
                    case SCREEN_ON_BIT:
                        nominalCurrentValue = Power.BRIGHTNESS_DIM;
                        break;
                    case 0:
                        nominalCurrentValue = Power.BRIGHTNESS_OFF;
                        break;
                    case SCREEN_BRIGHT_BIT:
                    default:
                        // not possible
                        nominalCurrentValue = (int)mScreenBrightness.curValue;
                        break;
                }
            }
            int brightness = preferredBrightness;
            int steps = ANIM_STEPS;
            if ((newState & SCREEN_BRIGHT_BIT) == 0) {
                // dim or turn off backlight, depending on if the screen is on
                // the scale is because the brightness ramp isn't linear and this biases
                // it so the later parts take longer.
                final float scale = 1.5f;
                float ratio = (((float)Power.BRIGHTNESS_DIM)/preferredBrightness);
                if (ratio > 1.0f) ratio = 1.0f;
                if ((newState & SCREEN_ON_BIT) == 0) {
                    if ((oldState & SCREEN_BRIGHT_BIT) != 0) {
                        // was bright
                        steps = ANIM_STEPS;
                    } else {
                        // was dim
                        steps = (int)(ANIM_STEPS*ratio*scale);
                    }
                    brightness = Power.BRIGHTNESS_OFF;
                } else {
                    if ((oldState & SCREEN_ON_BIT) != 0) {
                        // was bright
                        steps = (int)(ANIM_STEPS*(1.0f-ratio)*scale);
                    } else {
                        // was dim
                        steps = (int)(ANIM_STEPS*ratio);
                    }
                    if (mStayOnConditions != 0 && mBatteryService.isPowered(mStayOnConditions)) {
                        // If the "stay on while plugged in" option is
                        // turned on, then the screen will often not
                        // automatically turn off while plugged in.  To
                        // still have a sense of when it is inactive, we
                        // will then count going dim as turning off.
                        mScreenOffTime = SystemClock.elapsedRealtime();
                    }
                    brightness = Power.BRIGHTNESS_DIM;
                }
            }
            long identity = Binder.clearCallingIdentity();
            try {
                mBatteryStats.noteScreenBrightness(brightness);
            } catch (RemoteException e) {
                // Nothing interesting to do.
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
            mScreenBrightness.setTargetLocked(brightness, steps,
                    INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue);
        }

        if (mSpew) {
            Slog.d(TAG, "offMask=0x" + Integer.toHexString(offMask)
                    + " dimMask=0x" + Integer.toHexString(dimMask)
                    + " onMask=0x" + Integer.toHexString(onMask)
                    + " difference=0x" + Integer.toHexString(difference)
                    + " realDifference=0x" + Integer.toHexString(realDifference)
                    + " forceState=0x" + Integer.toHexString(forceState)
                    );
        }

        if (offMask != 0) {
            if (mSpew) Slog.i(TAG, "Setting brightess off: " + offMask);
            setLightBrightness(offMask, Power.BRIGHTNESS_OFF);
        }
        if (dimMask != 0) {
            int brightness = Power.BRIGHTNESS_DIM;
            if ((newState & BATTERY_LOW_BIT) != 0 &&
                    brightness > Power.BRIGHTNESS_LOW_BATTERY) {
                brightness = Power.BRIGHTNESS_LOW_BATTERY;
            }
            if (mSpew) Slog.i(TAG, "Setting brightess dim " + brightness + ": " + dimMask);
            setLightBrightness(dimMask, brightness);
        }
        if (onMask != 0) {
            int brightness = getPreferredBrightness();
            if ((newState & BATTERY_LOW_BIT) != 0 &&
                    brightness > Power.BRIGHTNESS_LOW_BATTERY) {
                brightness = Power.BRIGHTNESS_LOW_BATTERY;
            }
            if (mSpew) Slog.i(TAG, "Setting brightess on " + brightness + ": " + onMask);
            setLightBrightness(onMask, brightness);
        }
    }

    private void setLightBrightness(int mask, int value) {
        int brightnessMode = (mAutoBrightessEnabled
                            ? LightsService.BRIGHTNESS_MODE_SENSOR
                            : LightsService.BRIGHTNESS_MODE_USER);
        if ((mask & SCREEN_BRIGHT_BIT) != 0) {
            mLcdLight.setBrightness(value, brightnessMode);
        }
        if ((mask & BUTTON_BRIGHT_BIT) != 0) {
            mButtonLight.setBrightness(value);
        }
        if ((mask & KEYBOARD_BRIGHT_BIT) != 0) {
            mKeyboardLight.setBrightness(value);
        }
    }

    class BrightnessState implements Runnable {
        final int mask;

        boolean initialized;
        int targetValue;
        float curValue;
        float delta;
        boolean animating;

        BrightnessState(int m) {
            mask = m;
        }

        public void dump(PrintWriter pw, String prefix) {
            pw.println(prefix + "animating=" + animating
                    + " targetValue=" + targetValue
                    + " curValue=" + curValue
                    + " delta=" + delta);
        }

        void setTargetLocked(int target, int stepsToTarget, int initialValue,
                int nominalCurrentValue) {
            if (!initialized) {
                initialized = true;
                curValue = (float)initialValue;
            } else if (targetValue == target) {
                return;
            }
            targetValue = target;
            delta = (targetValue -
                    (nominalCurrentValue >= 0 ? nominalCurrentValue : curValue))
                    / stepsToTarget;
            if (mSpew) {
                String noticeMe = nominalCurrentValue == curValue ? "" : "  ******************";
                Slog.i(TAG, "setTargetLocked mask=" + mask + " curValue=" + curValue
                        + " target=" + target + " targetValue=" + targetValue + " delta=" + delta
                        + " nominalCurrentValue=" + nominalCurrentValue
                        + noticeMe);
            }
            animating = true;

            if (mSpew) {
                Slog.i(TAG, "scheduling light animator");
            }
            mScreenOffHandler.removeCallbacks(this);
            mScreenOffHandler.post(this);
        }

        boolean stepLocked() {
            if (!animating) return false;
            if (false && mSpew) {
                Slog.i(TAG, "Step target " + mask + ": cur=" + curValue
                        + " target=" + targetValue + " delta=" + delta);
            }
            curValue += delta;
            int curIntValue = (int)curValue;
            boolean more = true;
            if (delta == 0) {
                curValue = curIntValue = targetValue;
                more = false;
            } else if (delta > 0) {
                if (curIntValue >= targetValue) {
                    curValue = curIntValue = targetValue;
                    more = false;
                }
            } else {
                if (curIntValue <= targetValue) {
                    curValue = curIntValue = targetValue;
                    more = false;
                }
            }
            if (mSpew) Slog.d(TAG, "Animating curIntValue=" + curIntValue + ": " + mask);
            setLightBrightness(mask, curIntValue);
            finishAnimationLocked(more, curIntValue);
            return more;
        }

        void jumpToTargetLocked() {
            if (mSpew) Slog.d(TAG, "jumpToTargetLocked targetValue=" + targetValue + ": " + mask);
            setLightBrightness(mask, targetValue);
            final int tv = targetValue;
            curValue = tv;
            targetValue = -1;
            finishAnimationLocked(false, tv);
        }

        private void finishAnimationLocked(boolean more, int curIntValue) {
            animating = more;
            if (!more) {
                if (mask == SCREEN_BRIGHT_BIT && curIntValue == Power.BRIGHTNESS_OFF) {
                    screenOffFinishedAnimatingLocked(mScreenOffReason);
                }
            }
        }

        public void run() {
            if (mAnimateScreenLights) {
                synchronized (mLocks) {
                    long now = SystemClock.uptimeMillis();
                    boolean more = mScreenBrightness.stepLocked();
                    if (more) {
                        mScreenOffHandler.postAtTime(this, now+(1000/60));
                    }
                }
            } else {
                synchronized (mLocks) {
                    // we're turning off
                    final boolean animate = animating && targetValue == Power.BRIGHTNESS_OFF;
                    if (animate) {
                        // It's pretty scary to hold mLocks for this long, and we should
                        // redesign this, but it works for now.
                        nativeStartSurfaceFlingerAnimation(
                                mScreenOffReason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR
                                ? 0 : mAnimationSetting);
                    }
                    mScreenBrightness.jumpToTargetLocked();
                }
            }
        }
    }

    private int getPreferredBrightness() {
        try {
            if (mScreenBrightnessOverride >= 0) {
                return mScreenBrightnessOverride;
            } else if (mLightSensorScreenBrightness >= 0 && mUseSoftwareAutoBrightness
                    && mAutoBrightessEnabled) {
                return mLightSensorScreenBrightness;
            }
            final int brightness = Settings.System.getInt(mContext.getContentResolver(),
                                                          SCREEN_BRIGHTNESS);
             // Don't let applications turn the screen all the way off
            return Math.max(brightness, Power.BRIGHTNESS_DIM);
        } catch (SettingNotFoundException snfe) {
            return Power.BRIGHTNESS_ON;
        }
    }

    private int applyButtonState(int state) {
        int brightness = -1;
        if ((state & BATTERY_LOW_BIT) != 0) {
            // do not override brightness if the battery is low
            return state;
        }
        if (mButtonBrightnessOverride >= 0) {
            brightness = mButtonBrightnessOverride;
        } else if (mLightSensorButtonBrightness >= 0 && mUseSoftwareAutoBrightness) {
            brightness = mLightSensorButtonBrightness;
        }
        if (brightness > 0) {
            return state | BUTTON_BRIGHT_BIT;
        } else if (brightness == 0) {
            return state & ~BUTTON_BRIGHT_BIT;
        } else {
            return state;
        }
    }

    private int applyKeyboardState(int state) {
        int brightness = -1;
        if ((state & BATTERY_LOW_BIT) != 0) {
            // do not override brightness if the battery is low
            return state;
        }
        if (!mKeyboardVisible) {
            brightness = 0;
        } else if (mButtonBrightnessOverride >= 0) {
            brightness = mButtonBrightnessOverride;
        } else if (mLightSensorKeyboardBrightness >= 0 && mUseSoftwareAutoBrightness) {
            brightness =  mLightSensorKeyboardBrightness;
        }
        if (brightness > 0) {
            return state | KEYBOARD_BRIGHT_BIT;
        } else if (brightness == 0) {
            return state & ~KEYBOARD_BRIGHT_BIT;
        } else {
            return state;
        }
    }

    public boolean isScreenOn() {
        synchronized (mLocks) {
            return (mPowerState & SCREEN_ON_BIT) != 0;
        }
    }

    boolean isScreenBright() {
        synchronized (mLocks) {
            return (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT;
        }
    }

    private boolean isScreenTurningOffLocked() {
        return (mScreenBrightness.animating && mScreenBrightness.targetValue == 0);
    }

    private boolean shouldLog(long time) {
        synchronized (mLocks) {
            if (time > (mWarningSpewThrottleTime + (60*60*1000))) {
                mWarningSpewThrottleTime = time;
                mWarningSpewThrottleCount = 0;
                return true;
            } else if (mWarningSpewThrottleCount < 30) {
                mWarningSpewThrottleCount++;
                return true;
            } else {
                return false;
            }
        }
    }

    private void forceUserActivityLocked() {
        if (isScreenTurningOffLocked()) {
            // cancel animation so userActivity will succeed
            mScreenBrightness.animating = false;
        }
        boolean savedActivityAllowed = mUserActivityAllowed;
        mUserActivityAllowed = true;
        userActivity(SystemClock.uptimeMillis(), false);
        mUserActivityAllowed = savedActivityAllowed;
    }

    public void userActivityWithForce(long time, boolean noChangeLights, boolean force) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
        userActivity(time, -1, noChangeLights, OTHER_EVENT, force);
    }

    public void userActivity(long time, boolean noChangeLights) {
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
                != PackageManager.PERMISSION_GRANTED) {
            if (shouldLog(time)) {
                Slog.w(TAG, "Caller does not have DEVICE_POWER permission.  pid="
                        + Binder.getCallingPid() + " uid=" + Binder.getCallingUid());
            }
            return;
        }

        userActivity(time, -1, noChangeLights, OTHER_EVENT, false);
    }

    public void userActivity(long time, boolean noChangeLights, int eventType) {
        userActivity(time, -1, noChangeLights, eventType, false);
    }

    public void userActivity(long time, boolean noChangeLights, int eventType, boolean force) {
        userActivity(time, -1, noChangeLights, eventType, force);
    }

    /*
     * Reset the user activity timeout to now + timeout.  This overrides whatever else is going
     * on with user activity.  Don't use this function.
     */
    public void clearUserActivityTimeout(long now, long timeout) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
        Slog.i(TAG, "clearUserActivity for " + timeout + "ms from now");
        userActivity(now, timeout, false, OTHER_EVENT, false);
    }

    private void userActivity(long time, long timeoutOverride, boolean noChangeLights,
            int eventType, boolean force) {

        if (((mPokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0) && (eventType == TOUCH_EVENT)) {
            if (false) {
                Slog.d(TAG, "dropping touch mPokey=0x" + Integer.toHexString(mPokey));
            }
            return;
        }

        synchronized (mLocks) {
            if (mSpew) {
                Slog.d(TAG, "userActivity mLastEventTime=" + mLastEventTime + " time=" + time
                        + " mUserActivityAllowed=" + mUserActivityAllowed
                        + " mUserState=0x" + Integer.toHexString(mUserState)
                        + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState)
                        + " mProximitySensorActive=" + mProximitySensorActive
                        + " timeoutOverride=" + timeoutOverride
                        + " force=" + force);
            }
            // ignore user activity if we are in the process of turning off the screen
            if (isScreenTurningOffLocked()) {
                Slog.d(TAG, "ignoring user activity while turning off screen");
                return;
            }
            // Disable proximity sensor if if user presses power key while we are in the
            // "waiting for proximity sensor to go negative" state.
            if (mProximitySensorActive && mProximityWakeLockCount == 0) {
                mProximitySensorActive = false;
            }
            if (mLastEventTime <= time || force) {
                mLastEventTime = time;
                if ((mUserActivityAllowed && !mProximitySensorActive) || force) {
                    // Only turn on button backlights if a button was pressed
                    // and auto brightness is disabled
                    if (eventType == BUTTON_EVENT && !mUseSoftwareAutoBrightness) {
                        mUserState = (mKeyboardVisible ? ALL_BRIGHT : SCREEN_BUTTON_BRIGHT);
                    } else {
                        // don't clear button/keyboard backlights when the screen is touched.
                        mUserState |= SCREEN_BRIGHT;
                    }

                    int uid = Binder.getCallingUid();
                    long ident = Binder.clearCallingIdentity();
                    try {
                        mBatteryStats.noteUserActivity(uid, eventType);
                    } catch (RemoteException e) {
                        // Ignore
                    } finally {
                        Binder.restoreCallingIdentity(ident);
                    }

                    mWakeLockState = mLocks.reactivateScreenLocksLocked();
                    setPowerState(mUserState | mWakeLockState, noChangeLights,
                            WindowManagerPolicy.OFF_BECAUSE_OF_USER);
                    setTimeoutLocked(time, timeoutOverride, SCREEN_BRIGHT);
                }
            }
        }

        if (mPolicy != null) {
            mPolicy.userActivity();
        }
    }

    private int getAutoBrightnessValue(int sensorValue, int[] values) {
        try {
            int i;
            for (i = 0; i < mAutoBrightnessLevels.length; i++) {
                if (sensorValue < mAutoBrightnessLevels[i]) {
                    break;
                }
            }
            return values[i];
        } catch (Exception e) {
            // guard against null pointer or index out of bounds errors
            Slog.e(TAG, "getAutoBrightnessValue", e);
            return 255;
        }
    }

    private Runnable mProximityTask = new Runnable() {
        public void run() {
            synchronized (mLocks) {
                if (mProximityPendingValue != -1) {
                    proximityChangedLocked(mProximityPendingValue == 1);
                    mProximityPendingValue = -1;
                }
                if (mProximityPartialLock.isHeld()) {
                    mProximityPartialLock.release();
                }
            }
        }
    };

    private Runnable mAutoBrightnessTask = new Runnable() {
        public void run() {
            synchronized (mLocks) {
                if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) {
                    int value = (int)mLightSensorPendingValue;
                    mLightSensorPendingDecrease = false;
                    mLightSensorPendingIncrease = false;
                    lightSensorChangedLocked(value);
                }
            }
        }
    };

    private void dockStateChanged(int state) {
        synchronized (mLocks) {
            mIsDocked = (state != Intent.EXTRA_DOCK_STATE_UNDOCKED);
            if (mIsDocked) {
                mHighestLightSensorValue = -1;
            }
            if ((mPowerState & SCREEN_ON_BIT) != 0) {
                // force lights recalculation
                int value = (int)mLightSensorValue;
                mLightSensorValue = -1;
                lightSensorChangedLocked(value);
            }
        }
    }

    private void lightSensorChangedLocked(int value) {
        if (mDebugLightSensor) {
            Slog.d(TAG, "lightSensorChangedLocked " + value);
        }

        // Don't do anything if the screen is off.
        if ((mPowerState & SCREEN_ON_BIT) == 0) {
            if (mDebugLightSensor) {
                Slog.d(TAG, "dropping lightSensorChangedLocked because screen is off");
            }
            return;
        }

        // do not allow light sensor value to decrease
        if (mHighestLightSensorValue < value) {
            mHighestLightSensorValue = value;
        }

        if (mLightSensorValue != value) {
            mLightSensorValue = value;
            if ((mPowerState & BATTERY_LOW_BIT) == 0) {
                // use maximum light sensor value seen since screen went on for LCD to avoid flicker
                // we only do this if we are undocked, since lighting should be stable when
                // stationary in a dock.
                int lcdValue = getAutoBrightnessValue(
                        (mIsDocked ? value : mHighestLightSensorValue),
                        mLcdBacklightValues);
                int buttonValue = getAutoBrightnessValue(value, mButtonBacklightValues);
                int keyboardValue;
                if (mKeyboardVisible) {
                    keyboardValue = getAutoBrightnessValue(value, mKeyboardBacklightValues);
                } else {
                    keyboardValue = 0;
                }
                mLightSensorScreenBrightness = lcdValue;
                mLightSensorButtonBrightness = buttonValue;
                mLightSensorKeyboardBrightness = keyboardValue;

                if (mDebugLightSensor) {
                    Slog.d(TAG, "lcdValue " + lcdValue);
                    Slog.d(TAG, "buttonValue " + buttonValue);
                    Slog.d(TAG, "keyboardValue " + keyboardValue);
                }

                if (mAutoBrightessEnabled && mScreenBrightnessOverride < 0) {
                    mScreenBrightness.setTargetLocked(lcdValue, AUTOBRIGHTNESS_ANIM_STEPS,
                            INITIAL_SCREEN_BRIGHTNESS, (int)mScreenBrightness.curValue);
                }
                if (mButtonBrightnessOverride < 0) {
                    mButtonLight.setBrightness(buttonValue);
                }
                if (mButtonBrightnessOverride < 0 || !mKeyboardVisible) {
                    mKeyboardLight.setBrightness(keyboardValue);
                }
            }
        }
    }

    /**
     * The user requested that we go to sleep (probably with the power button).
     * This overrides all wake locks that are held.
     */
    public void goToSleep(long time)
    {
        goToSleepWithReason(time, WindowManagerPolicy.OFF_BECAUSE_OF_USER);
    }

    /**
     * The user requested that we go to sleep (probably with the power button).
     * This overrides all wake locks that are held.
     */
    public void goToSleepWithReason(long time, int reason)
    {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
        synchronized (mLocks) {
            goToSleepLocked(time, reason);
        }
    }

    /**
     * Reboot the device immediately, passing 'reason' (may be null)
     * to the underlying __reboot system call.  Should not return.
     */
    public void reboot(String reason)
    {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);

        if (mHandler == null || !ActivityManagerNative.isSystemReady()) {
            throw new IllegalStateException("Too early to call reboot()");
        }

        final String finalReason = reason;
        Runnable runnable = new Runnable() {
            public void run() {
                synchronized (this) {
                    ShutdownThread.reboot(mContext, finalReason, false);
                }
                
            }
        };
        // ShutdownThread must run on a looper capable of displaying the UI.
        mHandler.post(runnable);

        // PowerManager.reboot() is documented not to return so just wait for the inevitable.
        synchronized (runnable) {
            while (true) {
                try {
                    runnable.wait();
                } catch (InterruptedException e) {
                }
            }
        }
    }

    /**
     * Crash the runtime (causing a complete restart of the Android framework).
     * Requires REBOOT permission.  Mostly for testing.  Should not return.
     */
    public void crash(final String message)
    {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
        Thread t = new Thread("PowerManagerService.crash()") {
            public void run() { throw new RuntimeException(message); }
        };
        try {
            t.start();
            t.join();
        } catch (InterruptedException e) {
            Log.wtf(TAG, e);
        }
    }

    private void goToSleepLocked(long time, int reason) {

        if (mLastEventTime <= time) {
            mLastEventTime = time;
            // cancel all of the wake locks
            mWakeLockState = SCREEN_OFF;
            int N = mLocks.size();
            int numCleared = 0;
            boolean proxLock = false;
            for (int i=0; i<N; i++) {
                WakeLock wl = mLocks.get(i);
                if (isScreenLock(wl.flags)) {
                    if (((wl.flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)
                            && reason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) {
                        proxLock = true;
                    } else {
                        mLocks.get(i).activated = false;
                        numCleared++;
                    }
                }
            }
            if (!proxLock) {
                mProxIgnoredBecauseScreenTurnedOff = true;
                if (mDebugProximitySensor) {
                    Slog.d(TAG, "setting mProxIgnoredBecauseScreenTurnedOff");
                }
            }
            EventLog.writeEvent(EventLogTags.POWER_SLEEP_REQUESTED, numCleared);
            mStillNeedSleepNotification = true;
            mUserState = SCREEN_OFF;
            setPowerState(SCREEN_OFF, false, reason);
            cancelTimerLocked();
        }
    }

    public long timeSinceScreenOn() {
        synchronized (mLocks) {
            if ((mPowerState & SCREEN_ON_BIT) != 0) {
                return 0;
            }
            return SystemClock.elapsedRealtime() - mScreenOffTime;
        }
    }

    public void setKeyboardVisibility(boolean visible) {
        synchronized (mLocks) {
            if (mSpew) {
                Slog.d(TAG, "setKeyboardVisibility: " + visible);
            }
            if (mKeyboardVisible != visible) {
                mKeyboardVisible = visible;
                // don't signal user activity if the screen is off; other code
                // will take care of turning on due to a true change to the lid
                // switch and synchronized with the lock screen.
                if ((mPowerState & SCREEN_ON_BIT) != 0) {
                    if (mUseSoftwareAutoBrightness) {
                        // force recompute of backlight values
                        if (mLightSensorValue >= 0) {
                            int value = (int)mLightSensorValue;
                            mLightSensorValue = -1;
                            lightSensorChangedLocked(value);
                        }
                    }
                    userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true);
                }
            }
        }
    }

    /**
     * When the keyguard is up, it manages the power state, and userActivity doesn't do anything.
     * When disabling user activity we also reset user power state so the keyguard can reset its
     * short screen timeout when keyguard is unhidden.
     */
    public void enableUserActivity(boolean enabled) {
        if (mSpew) {
            Slog.d(TAG, "enableUserActivity " + enabled);
        }
        synchronized (mLocks) {
            mUserActivityAllowed = enabled;
            if (!enabled) {
                // cancel timeout and clear mUserState so the keyguard can set a short timeout
                setTimeoutLocked(SystemClock.uptimeMillis(), 0);
            }
        }
    }

    private void setScreenBrightnessMode(int mode) {
        synchronized (mLocks) {
            boolean enabled = (mode == SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
            if (mUseSoftwareAutoBrightness && mAutoBrightessEnabled != enabled) {
                mAutoBrightessEnabled = enabled;
                // This will get us a new value
                enableLightSensorLocked(mAutoBrightessEnabled && isScreenOn());
            }
        }
    }

    /** Sets the screen off timeouts:
     *      mKeylightDelay
     *      mDimDelay
     *      mScreenOffDelay
     * */
    private void setScreenOffTimeoutsLocked() {
        if ((mPokey & POKE_LOCK_SHORT_TIMEOUT) != 0) {
            mKeylightDelay = mShortKeylightDelay;  // Configurable via secure settings
            mDimDelay = -1;
            mScreenOffDelay = 0;
        } else if ((mPokey & POKE_LOCK_MEDIUM_TIMEOUT) != 0) {
            mKeylightDelay = MEDIUM_KEYLIGHT_DELAY;
            mDimDelay = -1;
            mScreenOffDelay = 0;
        } else {
            int totalDelay = mScreenOffTimeoutSetting;
            if (totalDelay > mMaximumScreenOffTimeout) {
                totalDelay = mMaximumScreenOffTimeout;
            }
            mKeylightDelay = LONG_KEYLIGHT_DELAY;
            if (totalDelay < 0) {
                // negative number means stay on as long as possible.
                mScreenOffDelay = mMaximumScreenOffTimeout;
            } else if (mKeylightDelay < totalDelay) {
                // subtract the time that the keylight delay. This will give us the
                // remainder of the time that we need to sleep to get the accurate
                // screen off timeout.
                mScreenOffDelay = totalDelay - mKeylightDelay;
            } else {
                mScreenOffDelay = 0;
            }
            if (mDimScreen && totalDelay >= (LONG_KEYLIGHT_DELAY + LONG_DIM_TIME)) {
                mDimDelay = mScreenOffDelay - LONG_DIM_TIME;
                mScreenOffDelay = LONG_DIM_TIME;
            } else {
                mDimDelay = -1;
            }
        }
        if (mSpew) {
            Slog.d(TAG, "setScreenOffTimeouts mKeylightDelay=" + mKeylightDelay
                    + " mDimDelay=" + mDimDelay + " mScreenOffDelay=" + mScreenOffDelay
                    + " mDimScreen=" + mDimScreen);
        }
    }

    /**
     * Refreshes cached secure settings.  Called once on startup, and
     * on subsequent changes to secure settings.
     */
    private void updateSettingsValues() {
        mShortKeylightDelay = Settings.Secure.getInt(
                mContext.getContentResolver(),
                Settings.Secure.SHORT_KEYLIGHT_DELAY_MS,
                SHORT_KEYLIGHT_DELAY_DEFAULT);
        // Slog.i(TAG, "updateSettingsValues(): mShortKeylightDelay now " + mShortKeylightDelay);
    }

    private class LockList extends ArrayList<WakeLock>
    {
        void addLock(WakeLock wl)
        {
            int index = getIndex(wl.binder);
            if (index < 0) {
                this.add(wl);
            }
        }

        WakeLock removeLock(IBinder binder)
        {
            int index = getIndex(binder);
            if (index >= 0) {
                return this.remove(index);
            } else {
                return null;
            }
        }

        int getIndex(IBinder binder)
        {
            int N = this.size();
            for (int i=0; i<N; i++) {
                if (this.get(i).binder == binder) {
                    return i;
                }
            }
            return -1;
        }

        int gatherState()
        {
            int result = 0;
            int N = this.size();
            for (int i=0; i<N; i++) {
                WakeLock wl = this.get(i);
                if (wl.activated) {
                    if (isScreenLock(wl.flags)) {
                        result |= wl.minState;
                    }
                }
            }
            return result;
        }

        int reactivateScreenLocksLocked()
        {
            int result = 0;
            int N = this.size();
            for (int i=0; i<N; i++) {
                WakeLock wl = this.get(i);
                if (isScreenLock(wl.flags)) {
                    wl.activated = true;
                    result |= wl.minState;
                }
            }
            if (mDebugProximitySensor) {
                Slog.d(TAG, "reactivateScreenLocksLocked mProxIgnoredBecauseScreenTurnedOff="
                        + mProxIgnoredBecauseScreenTurnedOff);
            }
            mProxIgnoredBecauseScreenTurnedOff = false;
            return result;
        }
    }

    public void setPolicy(WindowManagerPolicy p) {
        synchronized (mLocks) {
            mPolicy = p;
            mLocks.notifyAll();
        }
    }

    WindowManagerPolicy getPolicyLocked() {
        while (mPolicy == null || !mDoneBooting) {
            try {
                mLocks.wait();
            } catch (InterruptedException e) {
                // Ignore
            }
        }
        return mPolicy;
    }

    void systemReady() {
        mSensorManager = new SensorManager(mHandlerThread.getLooper());
        mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
        // don't bother with the light sensor if auto brightness is handled in hardware
        if (mUseSoftwareAutoBrightness) {
            mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
        }

        // wait until sensors are enabled before turning on screen.
        // some devices will not activate the light sensor properly on boot
        // unless we do this.
        if (mUseSoftwareAutoBrightness) {
            // turn the screen on
            setPowerState(SCREEN_BRIGHT);
        } else {
            // turn everything on
            setPowerState(ALL_BRIGHT);
        }

        synchronized (mLocks) {
            Slog.d(TAG, "system ready!");
            mDoneBooting = true;

            enableLightSensorLocked(mUseSoftwareAutoBrightness && mAutoBrightessEnabled);

            long identity = Binder.clearCallingIdentity();
            try {
                mBatteryStats.noteScreenBrightness(getPreferredBrightness());
                mBatteryStats.noteScreenOn();
            } catch (RemoteException e) {
                // Nothing interesting to do.
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }
    }

    void bootCompleted() {
        Slog.d(TAG, "bootCompleted");
        synchronized (mLocks) {
            mBootCompleted = true;
            userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true);
            updateWakeLockLocked();
            mLocks.notifyAll();
        }
    }

    // for watchdog
    public void monitor() {
        synchronized (mLocks) { }
    }

    public int getSupportedWakeLockFlags() {
        int result = PowerManager.PARTIAL_WAKE_LOCK
                   | PowerManager.FULL_WAKE_LOCK
                   | PowerManager.SCREEN_DIM_WAKE_LOCK;

        if (mProximitySensor != null) {
            result |= PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK;
        }

        return result;
    }

    public void setBacklightBrightness(int brightness) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
        // Don't let applications turn the screen all the way off
        synchronized (mLocks) {
            brightness = Math.max(brightness, Power.BRIGHTNESS_DIM);
            mLcdLight.setBrightness(brightness);
            mKeyboardLight.setBrightness(mKeyboardVisible ? brightness : 0);
            mButtonLight.setBrightness(brightness);
            long identity = Binder.clearCallingIdentity();
            try {
                mBatteryStats.noteScreenBrightness(brightness);
            } catch (RemoteException e) {
                Slog.w(TAG, "RemoteException calling noteScreenBrightness on BatteryStatsService", e);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }

            // update our animation state
            synchronized (mLocks) {
                mScreenBrightness.targetValue = brightness;
                mScreenBrightness.jumpToTargetLocked();
            }
        }
    }

    public void setAttentionLight(boolean on, int color) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
        mAttentionLight.setFlashing(color, LightsService.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0);
    }

    private void enableProximityLockLocked() {
        if (mDebugProximitySensor) {
            Slog.d(TAG, "enableProximityLockLocked");
        }
        if (!mProximitySensorEnabled) {
            // clear calling identity so sensor manager battery stats are accurate
            long identity = Binder.clearCallingIdentity();
            try {
                mSensorManager.registerListener(mProximityListener, mProximitySensor,
                        SensorManager.SENSOR_DELAY_NORMAL);
                mProximitySensorEnabled = true;
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }
    }

    private void disableProximityLockLocked() {
        if (mDebugProximitySensor) {
            Slog.d(TAG, "disableProximityLockLocked");
        }
        if (mProximitySensorEnabled) {
            // clear calling identity so sensor manager battery stats are accurate
            long identity = Binder.clearCallingIdentity();
            try {
                mSensorManager.unregisterListener(mProximityListener);
                mHandler.removeCallbacks(mProximityTask);
                if (mProximityPartialLock.isHeld()) {
                    mProximityPartialLock.release();
                }
                mProximitySensorEnabled = false;
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
            if (mProximitySensorActive) {
                mProximitySensorActive = false;
                if (mDebugProximitySensor) {
                    Slog.d(TAG, "disableProximityLockLocked mProxIgnoredBecauseScreenTurnedOff="
                            + mProxIgnoredBecauseScreenTurnedOff);
                }
                if (!mProxIgnoredBecauseScreenTurnedOff) {
                    forceUserActivityLocked();
                }
            }
        }
    }

    private void proximityChangedLocked(boolean active) {
        if (mDebugProximitySensor) {
            Slog.d(TAG, "proximityChangedLocked, active: " + active);
        }
        if (!mProximitySensorEnabled) {
            Slog.d(TAG, "Ignoring proximity change after sensor is disabled");
            return;
        }
        if (active) {
            if (mDebugProximitySensor) {
                Slog.d(TAG, "b mProxIgnoredBecauseScreenTurnedOff="
                        + mProxIgnoredBecauseScreenTurnedOff);
            }
            if (!mProxIgnoredBecauseScreenTurnedOff) {
                goToSleepLocked(SystemClock.uptimeMillis(),
                        WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR);
            }
            mProximitySensorActive = true;
        } else {
            // proximity sensor negative events trigger as user activity.
            // temporarily set mUserActivityAllowed to true so this will work
            // even when the keyguard is on.
            mProximitySensorActive = false;
            if (mDebugProximitySensor) {
                Slog.d(TAG, "b mProxIgnoredBecauseScreenTurnedOff="
                        + mProxIgnoredBecauseScreenTurnedOff);
            }
            if (!mProxIgnoredBecauseScreenTurnedOff) {
                forceUserActivityLocked();
            }

            if (mProximityWakeLockCount == 0) {
                // disable sensor if we have no listeners left after proximity negative
                disableProximityLockLocked();
            }
        }
    }

    private void enableLightSensorLocked(boolean enable) {
        if (mDebugLightSensor) {
            Slog.d(TAG, "enableLightSensorLocked enable=" + enable
                    + " mAutoBrightessEnabled=" + mAutoBrightessEnabled);
        }
        if (!mAutoBrightessEnabled) {
            enable = false;
        }
        if (mSensorManager != null && mLightSensorEnabled != enable) {
            mLightSensorEnabled = enable;
            // clear calling identity so sensor manager battery stats are accurate
            long identity = Binder.clearCallingIdentity();
            try {
                if (enable) {
                    mSensorManager.registerListener(mLightListener, mLightSensor,
                            SensorManager.SENSOR_DELAY_NORMAL);
                } else {
                    mSensorManager.unregisterListener(mLightListener);
                    mHandler.removeCallbacks(mAutoBrightnessTask);
                }
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }
    }

    SensorEventListener mProximityListener = new SensorEventListener() {
        public void onSensorChanged(SensorEvent event) {
            long milliseconds = SystemClock.elapsedRealtime();
            synchronized (mLocks) {
                float distance = event.values[0];
                long timeSinceLastEvent = milliseconds - mLastProximityEventTime;
                mLastProximityEventTime = milliseconds;
                mHandler.removeCallbacks(mProximityTask);
                boolean proximityTaskQueued = false;

                // compare against getMaximumRange to support sensors that only return 0 or 1
                boolean active = (distance >= 0.0 && distance < PROXIMITY_THRESHOLD &&
                        distance < mProximitySensor.getMaximumRange());

                if (mDebugProximitySensor) {
                    Slog.d(TAG, "mProximityListener.onSensorChanged active: " + active);
                }
                if (timeSinceLastEvent < PROXIMITY_SENSOR_DELAY) {
                    // enforce delaying atleast PROXIMITY_SENSOR_DELAY before processing
                    mProximityPendingValue = (active ? 1 : 0);
                    mHandler.postDelayed(mProximityTask, PROXIMITY_SENSOR_DELAY - timeSinceLastEvent);
                    proximityTaskQueued = true;
                } else {
                    // process the value immediately
                    mProximityPendingValue = -1;
                    proximityChangedLocked(active);
                }

                // update mProximityPartialLock state
                boolean held = mProximityPartialLock.isHeld();
                if (!held && proximityTaskQueued) {
                    // hold wakelock until mProximityTask runs
                    mProximityPartialLock.acquire();
                } else if (held && !proximityTaskQueued) {
                    mProximityPartialLock.release();
                }
            }
        }

        public void onAccuracyChanged(Sensor sensor, int accuracy) {
            // ignore
        }
    };

    SensorEventListener mLightListener = new SensorEventListener() {
        public void onSensorChanged(SensorEvent event) {
            synchronized (mLocks) {
                // ignore light sensor while screen is turning off
                if (isScreenTurningOffLocked()) {
                    return;
                }

                int value = (int)event.values[0];
                long milliseconds = SystemClock.elapsedRealtime();
                if (mDebugLightSensor) {
                    Slog.d(TAG, "onSensorChanged: light value: " + value);
                }
                if (mLightSensorValue == -1 ||
                        milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) {
                    // process the value immediately if screen has just turned on
                    mHandler.removeCallbacks(mAutoBrightnessTask);
                    mLightSensorPendingDecrease = false;
                    mLightSensorPendingIncrease = false;
                    lightSensorChangedLocked(value);
                } else {
                    if ((value > mLightSensorValue && mLightSensorPendingDecrease) ||
                            (value < mLightSensorValue && mLightSensorPendingIncrease) ||
                            (value == mLightSensorValue) ||
                            (!mLightSensorPendingDecrease && !mLightSensorPendingIncrease)) {
                        // delay processing to debounce the sensor
                        mHandler.removeCallbacks(mAutoBrightnessTask);
                        mLightSensorPendingDecrease = (value < mLightSensorValue);
                        mLightSensorPendingIncrease = (value > mLightSensorValue);
                        if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) {
                            mLightSensorPendingValue = value;
                            mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY);
                        }
                    } else {
                        mLightSensorPendingValue = value;
                    }
                }
            }
        }

        public void onAccuracyChanged(Sensor sensor, int accuracy) {
            // ignore
        }
    };
}
