/*
 * 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 android.os;

import android.util.Log;

import com.android.internal.os.RuntimeInit;

/**
 * This class gives you control of the power state of the device.  
 * 
 * <p><b>Device battery life will be significantly affected by the use of this API.</b>  Do not
 * acquire WakeLocks unless you really need them, use the minimum levels possible, and be sure
 * to release it as soon as you can.
 * 
 * <p>You can obtain an instance of this class by calling 
 * {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
 * 
 * <p>The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.  This will
 * create a {@link PowerManager.WakeLock} object.  You can then use methods on this object to 
 * control the power state of the device.  In practice it's quite simple:
 * 
 * {@samplecode
 * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
 * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
 * wl.acquire();
 *   ..screen will stay on during this section..
 * wl.release();
 * }
 * 
 * <p>The following flags are defined, with varying effects on system power.  <i>These flags are
 * mutually exclusive - you may only specify one of them.</i>
 * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
 *
 *     <thead>
 *     <tr><th>Flag Value</th> 
 *     <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
 *     </thead>
 *
 *     <tbody>
 *     <tr><th>{@link #PARTIAL_WAKE_LOCK}</th>
 *         <td>On*</td> <td>Off</td> <td>Off</td> 
 *     </tr>
 *     
 *     <tr><th>{@link #SCREEN_DIM_WAKE_LOCK}</th>
 *         <td>On</td> <td>Dim</td> <td>Off</td> 
 *     </tr>
 *
 *     <tr><th>{@link #SCREEN_BRIGHT_WAKE_LOCK}</th>
 *         <td>On</td> <td>Bright</td> <td>Off</td> 
 *     </tr>
 *     
 *     <tr><th>{@link #FULL_WAKE_LOCK}</th>
 *         <td>On</td> <td>Bright</td> <td>Bright</td> 
 *     </tr>
 *     </tbody>
 * </table>
 * 
 * <p>*<i>If you hold a partial wakelock, the CPU will continue to run, irrespective of any timers 
 * and even after the user presses the power button.  In all other wakelocks, the CPU will run, but
 * the user can still put the device to sleep using the power button.</i>
 * 
 * <p>In addition, you can add two more flags, which affect behavior of the screen only.  <i>These
 * flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i>
 * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
 *
 *     <thead>
 *     <tr><th>Flag Value</th> <th>Description</th></tr>
 *     </thead>
 *
 *     <tbody>
 *     <tr><th>{@link #ACQUIRE_CAUSES_WAKEUP}</th>
 *         <td>Normal wake locks don't actually turn on the illumination.  Instead, they cause
 *         the illumination to remain on once it turns on (e.g. from user activity).  This flag
 *         will force the screen and/or keyboard to turn on immediately, when the WakeLock is
 *         acquired.  A typical use would be for notifications which are important for the user to
 *         see immediately.</td> 
 *     </tr>
 *     
 *     <tr><th>{@link #ON_AFTER_RELEASE}</th>
 *         <td>If this flag is set, the user activity timer will be reset when the WakeLock is
 *         released, causing the illumination to remain on a bit longer.  This can be used to 
 *         reduce flicker if you are cycling between wake lock conditions.</td> 
 *     </tr>
 *     </tbody>
 * </table>
 * 
 * 
 */
public class PowerManager
{
    private static final String TAG = "PowerManager";
    
    /**
     * These internal values define the underlying power elements that we might
     * want to control individually.  Eventually we'd like to expose them.
     */
    private static final int WAKE_BIT_CPU_STRONG = 1;
    private static final int WAKE_BIT_CPU_WEAK = 2;
    private static final int WAKE_BIT_SCREEN_DIM = 4;
    private static final int WAKE_BIT_SCREEN_BRIGHT = 8;
    private static final int WAKE_BIT_KEYBOARD_BRIGHT = 16;
    private static final int WAKE_BIT_PROXIMITY_SCREEN_OFF = 32;
    
    private static final int LOCK_MASK = WAKE_BIT_CPU_STRONG
                                        | WAKE_BIT_CPU_WEAK
                                        | WAKE_BIT_SCREEN_DIM
                                        | WAKE_BIT_SCREEN_BRIGHT
                                        | WAKE_BIT_KEYBOARD_BRIGHT
                                        | WAKE_BIT_PROXIMITY_SCREEN_OFF;

    /**
     * Wake lock that ensures that the CPU is running.  The screen might
     * not be on.
     */
    public static final int PARTIAL_WAKE_LOCK = WAKE_BIT_CPU_STRONG;

    /**
     * Wake lock that ensures that the screen and keyboard are on at
     * full brightness.
     */
    public static final int FULL_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT 
                                            | WAKE_BIT_KEYBOARD_BRIGHT;

    /**
     * Wake lock that ensures that the screen is on at full brightness;
     * the keyboard backlight will be allowed to go off.
     */
    public static final int SCREEN_BRIGHT_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT;

    /**
     * Wake lock that ensures that the screen is on (but may be dimmed);
     * the keyboard backlight will be allowed to go off.
     */
    public static final int SCREEN_DIM_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_DIM;

    /**
     * Wake lock that turns the screen off when the proximity sensor activates.
     * Since not all devices have proximity sensors, use
     * {@link #getSupportedWakeLockFlags() getSupportedWakeLockFlags()} to determine if
     * this wake lock mode is supported.
     *
     * {@hide}
     */
    public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = WAKE_BIT_PROXIMITY_SCREEN_OFF;

    /**
     * Flag for {@link WakeLock#release release(int)} to defer releasing a
     * {@link #WAKE_BIT_PROXIMITY_SCREEN_OFF} wakelock until the proximity sensor returns
     * a negative value.
     *
     * {@hide}
     */
    public static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1;

    /**
     * Normally wake locks don't actually wake the device, they just cause
     * it to remain on once it's already on.  Think of the video player
     * app as the normal behavior.  Notifications that pop up and want
     * the device to be on are the exception; use this flag to be like them.
     * <p> 
     * Does not work with PARTIAL_WAKE_LOCKs.
     */
    public static final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;

    /**
     * When this wake lock is released, poke the user activity timer
     * so the screen stays on for a little longer.
     * <p>
     * Will not turn the screen on if it is not already on.  See {@link #ACQUIRE_CAUSES_WAKEUP}
     * if you want that.
     * <p>
     * Does not work with PARTIAL_WAKE_LOCKs.
     */
    public static final int ON_AFTER_RELEASE = 0x20000000;
    
    /**
     * Class lets you say that you need to have the device on.
     *
     * <p>Call release when you are done and don't need the lock anymore.
     */
    public class WakeLock
    {
        static final int RELEASE_WAKE_LOCK = 1;

        Runnable mReleaser = new Runnable() {
            public void run() {
                release();
            }
        };
	
        int mFlags;
        String mTag;
        IBinder mToken;
        int mCount = 0;
        boolean mRefCounted = true;
        boolean mHeld = false;

        WakeLock(int flags, String tag)
        {
            switch (flags & LOCK_MASK) {
            case PARTIAL_WAKE_LOCK:
            case SCREEN_DIM_WAKE_LOCK:
            case SCREEN_BRIGHT_WAKE_LOCK:
            case FULL_WAKE_LOCK:
            case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
                break;
            default:
                throw new IllegalArgumentException();
            }

            mFlags = flags;
            mTag = tag;
            mToken = new Binder();
        }

        /**
         * Sets whether this WakeLock is ref counted.
         *
         * @param value true for ref counted, false for not ref counted.
         */
        public void setReferenceCounted(boolean value)
        {
            mRefCounted = value;
        }

        /**
         * Makes sure the device is on at the level you asked when you created
         * the wake lock.
         */
        public void acquire()
        {
            synchronized (mToken) {
                if (!mRefCounted || mCount++ == 0) {
                    try {
                        mService.acquireWakeLock(mFlags, mToken, mTag);
                    } catch (RemoteException e) {
                    }
                    mHeld = true;
                }
            }
        }
        
        /**
         * Makes sure the device is on at the level you asked when you created
         * the wake lock. The lock will be released after the given timeout.
         * 
         * @param timeout Release the lock after the give timeout in milliseconds.
         */
        public void acquire(long timeout) {
            acquire();
            mHandler.postDelayed(mReleaser, timeout);
        }
        

        /**
         * Release your claim to the CPU or screen being on.
         *
         * <p>
         * It may turn off shortly after you release it, or it may not if there
         * are other wake locks held.
         */
        public void release()
        {
            release(0);
        }

        /**
         * Release your claim to the CPU or screen being on.
         * @param flags Combination of flag values to modify the release behavior.
         *              Currently only {@link #WAIT_FOR_PROXIMITY_NEGATIVE} is supported.
         *
         * <p>
         * It may turn off shortly after you release it, or it may not if there
         * are other wake locks held.
         *
         * {@hide}
         */
        public void release(int flags)
        {
            synchronized (mToken) {
                if (!mRefCounted || --mCount == 0) {
                    try {
                        mService.releaseWakeLock(mToken, flags);
                    } catch (RemoteException e) {
                    }
                    mHeld = false;
                }
                if (mCount < 0) {
                    throw new RuntimeException("WakeLock under-locked " + mTag);
                }
            }
        }

        public boolean isHeld()
        {
            synchronized (mToken) {
                return mHeld;
            }
        }

        public String toString() {
            synchronized (mToken) {
                return "WakeLock{"
                    + Integer.toHexString(System.identityHashCode(this))
                    + " held=" + mHeld + ", refCount=" + mCount + "}";
            }
        }

        @Override
        protected void finalize() throws Throwable
        {
            synchronized (mToken) {
                if (mHeld) {
                    Log.wtf(TAG, "WakeLock finalized while still held: " + mTag);
                    try {
                        mService.releaseWakeLock(mToken, 0);
                    } catch (RemoteException e) {
                    }
                }
            }
        }
    }

    /**
     * Get a wake lock at the level of the flags parameter.  Call
     * {@link WakeLock#acquire() acquire()} on the object to acquire the
     * wake lock, and {@link WakeLock#release release()} when you are done.
     *
     * {@samplecode
     *PowerManager pm = (PowerManager)mContext.getSystemService(
     *                                          Context.POWER_SERVICE);
     *PowerManager.WakeLock wl = pm.newWakeLock(
     *                                      PowerManager.SCREEN_DIM_WAKE_LOCK
     *                                      | PowerManager.ON_AFTER_RELEASE,
     *                                      TAG);
     *wl.acquire();
     * // ...
     *wl.release();
     * }
     *
     * @param flags Combination of flag values defining the requested behavior of the WakeLock.
     * @param tag Your class name (or other tag) for debugging purposes.
     *
     * @see WakeLock#acquire()
     * @see WakeLock#release()
     */
    public WakeLock newWakeLock(int flags, String tag)
    {
        if (tag == null) {
            throw new NullPointerException("tag is null in PowerManager.newWakeLock");
        }
        return new WakeLock(flags, tag);
    }

    /**
     * User activity happened.
     * <p>
     * Turns the device from whatever state it's in to full on, and resets
     * the auto-off timer.
     *
     * @param when is used to order this correctly with the wake lock calls.
     *          This time should be in the {@link SystemClock#uptimeMillis
     *          SystemClock.uptimeMillis()} time base.
     * @param noChangeLights should be true if you don't want the lights to
     *          turn on because of this event.  This is set when the power
     *          key goes down.  We want the device to stay on while the button
     *          is down, but we're about to turn off.  Otherwise the lights
     *          flash on and then off and it looks weird.
     */
    public void userActivity(long when, boolean noChangeLights)
    {
        try {
            mService.userActivity(when, noChangeLights);
        } catch (RemoteException e) {
        }
    }

   /**
     * Force the device to go to sleep. Overrides all the wake locks that are
     * held.
     * 
     * @param time is used to order this correctly with the wake lock calls. 
     *          The time  should be in the {@link SystemClock#uptimeMillis 
     *          SystemClock.uptimeMillis()} time base.
     */
    public void goToSleep(long time) 
    {
        try {
            mService.goToSleep(time);
        } catch (RemoteException e) {
        }
    }

    /**
     * sets the brightness of the backlights (screen, keyboard, button).
     *
     * @param brightness value from 0 to 255
     *
     * {@hide}
     */
    public void setBacklightBrightness(int brightness)
    {
        try {
            mService.setBacklightBrightness(brightness);
        } catch (RemoteException e) {
        }
    }

   /**
     * Returns the set of flags for {@link #newWakeLock(int, String) newWakeLock()}
     * that are supported on the device.
     * For example, to test to see if the {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK}
     * is supported:
     *
     * {@samplecode
     * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
     * int supportedFlags = pm.getSupportedWakeLockFlags();
     *  boolean proximitySupported = ((supportedFlags & PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)
     *                                  == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK);
     * }
     *
     * @return the set of supported WakeLock flags.
     *
     * {@hide}
     */
    public int getSupportedWakeLockFlags()
    {
        try {
            return mService.getSupportedWakeLockFlags();
        } catch (RemoteException e) {
            return 0;
        }
    }

    /**
      * Returns whether the screen is currently on. The screen could be bright
      * or dim.
      *
      * {@samplecode
      * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
      * boolean isScreenOn = pm.isScreenOn();
      * }
      *
      * @return whether the screen is on (bright or dim).
      */
    public boolean isScreenOn()
    {
        try {
            return mService.isScreenOn();
        } catch (RemoteException e) {
            return false;
        }
    }

    /**
     * Reboot the device.  Will not return if the reboot is
     * successful.  Requires the {@link android.Manifest.permission#REBOOT}
     * permission.
     *
     * @param reason code to pass to the kernel (e.g., "recovery") to
     *               request special boot modes, or null.
     */
    public void reboot(String reason)
    {
        try {
            mService.reboot(reason);
        } catch (RemoteException e) {
        }
    }

    private PowerManager()
    {
    }

    /**
     * {@hide}
     */
    public PowerManager(IPowerManager service, Handler handler)
    {
        mService = service;
        mHandler = handler;
    }

    /**
     *  TODO: It would be nice to be able to set the poke lock here,
     *  but I'm not sure what would be acceptable as an interface -
     *  either a PokeLock object (like WakeLock) or, possibly just a
     *  method call to set the poke lock. 
     */
    
    IPowerManager mService;
    Handler mHandler;
}
