/*
 * Copyright (C) 2012 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.power;

import android.app.AppOpsManager;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.server.EventLogTags;

import android.app.ActivityManagerNative;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.BatteryStats;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
import android.view.WindowManagerPolicy;

/**
 * Sends broadcasts about important power state changes.
 * <p>
 * This methods of this class may be called by the power manager service while
 * its lock is being held.  Internally it takes care of sending broadcasts to
 * notify other components of the system or applications asynchronously.
 * </p><p>
 * The notifier is designed to collapse unnecessary broadcasts when it is not
 * possible for the system to have observed an intermediate state.
 * </p><p>
 * For example, if the device wakes up, goes to sleep, wakes up again and goes to
 * sleep again before the wake up notification is sent, then the system will
 * be told about only one wake up and sleep.  However, we always notify the
 * fact that at least one transition occurred.  It is especially important to
 * tell the system when we go to sleep so that it can lock the keyguard if needed.
 * </p>
 */
final class Notifier {
    private static final String TAG = "PowerManagerNotifier";

    private static final boolean DEBUG = false;

    private static final int POWER_STATE_UNKNOWN = 0;
    private static final int POWER_STATE_AWAKE = 1;
    private static final int POWER_STATE_ASLEEP = 2;

    private static final int MSG_USER_ACTIVITY = 1;
    private static final int MSG_BROADCAST = 2;
    private static final int MSG_WIRELESS_CHARGING_STARTED = 3;

    private final Object mLock = new Object();

    private final Context mContext;
    private final IBatteryStats mBatteryStats;
    private final IAppOpsService mAppOps;
    private final SuspendBlocker mSuspendBlocker;
    private final ScreenOnBlocker mScreenOnBlocker;
    private final WindowManagerPolicy mPolicy;

    private final NotifierHandler mHandler;
    private final Intent mScreenOnIntent;
    private final Intent mScreenOffIntent;

    // The current power state.
    private int mActualPowerState;
    private int mLastGoToSleepReason;

    // True if there is a pending transition that needs to be reported.
    private boolean mPendingWakeUpBroadcast;
    private boolean mPendingGoToSleepBroadcast;

    // The currently broadcasted power state.  This reflects what other parts of the
    // system have observed.
    private int mBroadcastedPowerState;
    private boolean mBroadcastInProgress;
    private long mBroadcastStartTime;

    // True if a user activity message should be sent.
    private boolean mUserActivityPending;

    // True if the screen on blocker has been acquired.
    private boolean mScreenOnBlockerAcquired;

    public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
            IAppOpsService appOps, SuspendBlocker suspendBlocker, ScreenOnBlocker screenOnBlocker,
            WindowManagerPolicy policy) {
        mContext = context;
        mBatteryStats = batteryStats;
        mAppOps = appOps;
        mSuspendBlocker = suspendBlocker;
        mScreenOnBlocker = screenOnBlocker;
        mPolicy = policy;

        mHandler = new NotifierHandler(looper);
        mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
        mScreenOnIntent.addFlags(
                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
        mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
        mScreenOffIntent.addFlags(
                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
    }

    /**
     * Called when a wake lock is acquired.
     */
    public void onWakeLockAcquired(int flags, String tag, String packageName,
            int ownerUid, int ownerPid, WorkSource workSource) {
        if (DEBUG) {
            Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag
                    + "\", packageName=" + packageName
                    + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                    + ", workSource=" + workSource);
        }

        try {
            final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
            if (workSource != null) {
                mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag, monitorType);
            } else {
                mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, monitorType);
                // XXX need to deal with disabled operations.
                mAppOps.startOperation(AppOpsManager.getToken(mAppOps),
                        AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
            }
        } catch (RemoteException ex) {
            // Ignore
        }
    }

    /**
     * Called when a wake lock is released.
     */
    public void onWakeLockReleased(int flags, String tag, String packageName,
            int ownerUid, int ownerPid, WorkSource workSource) {
        if (DEBUG) {
            Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag
                    + "\", packageName=" + packageName
                    + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
                    + ", workSource=" + workSource);
        }

        try {
            final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
            if (workSource != null) {
                mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag, monitorType);
            } else {
                mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag, monitorType);
                mAppOps.finishOperation(AppOpsManager.getToken(mAppOps),
                        AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
            }
        } catch (RemoteException ex) {
            // Ignore
        }
    }

    private static int getBatteryStatsWakeLockMonitorType(int flags) {
        switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
            case PowerManager.PARTIAL_WAKE_LOCK:
            case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
                return BatteryStats.WAKE_TYPE_PARTIAL;
            default:
                return BatteryStats.WAKE_TYPE_FULL;
        }
    }

    /**
     * Called when the screen is turned on.
     */
    public void onScreenOn() {
        if (DEBUG) {
            Slog.d(TAG, "onScreenOn");
        }

        try {
            mBatteryStats.noteScreenOn();
        } catch (RemoteException ex) {
            // Ignore
        }
    }

    /**
     * Called when the screen is turned off.
     */
    public void onScreenOff() {
        if (DEBUG) {
            Slog.d(TAG, "onScreenOff");
        }

        try {
            mBatteryStats.noteScreenOff();
        } catch (RemoteException ex) {
            // Ignore
        }
    }

    /**
     * Called when the screen changes brightness.
     */
    public void onScreenBrightness(int brightness) {
        if (DEBUG) {
            Slog.d(TAG, "onScreenBrightness: brightness=" + brightness);
        }

        try {
            mBatteryStats.noteScreenBrightness(brightness);
        } catch (RemoteException ex) {
            // Ignore
        }
    }

    /**
     * Called when the device is waking up from sleep and the
     * display is about to be turned on.
     */
    public void onWakeUpStarted() {
        if (DEBUG) {
            Slog.d(TAG, "onWakeUpStarted");
        }

        synchronized (mLock) {
            if (mActualPowerState != POWER_STATE_AWAKE) {
                mActualPowerState = POWER_STATE_AWAKE;
                mPendingWakeUpBroadcast = true;
                if (!mScreenOnBlockerAcquired) {
                    mScreenOnBlockerAcquired = true;
                    mScreenOnBlocker.acquire();
                }
                updatePendingBroadcastLocked();
            }
        }
    }

    /**
     * Called when the device has finished waking up from sleep
     * and the display has been turned on.
     */
    public void onWakeUpFinished() {
        if (DEBUG) {
            Slog.d(TAG, "onWakeUpFinished");
        }
    }

    /**
     * Called when the device is going to sleep.
     */
    public void onGoToSleepStarted(int reason) {
        if (DEBUG) {
            Slog.d(TAG, "onGoToSleepStarted");
        }

        synchronized (mLock) {
            mLastGoToSleepReason = reason;
        }
    }

    /**
     * Called when the device has finished going to sleep and the
     * display has been turned off.
     *
     * This is a good time to make transitions that we don't want the user to see,
     * such as bringing the key guard to focus.  There's no guarantee for this,
     * however because the user could turn the device on again at any time.
     * Some things may need to be protected by other mechanisms that defer screen on.
     */
    public void onGoToSleepFinished() {
        if (DEBUG) {
            Slog.d(TAG, "onGoToSleepFinished");
        }

        synchronized (mLock) {
            if (mActualPowerState != POWER_STATE_ASLEEP) {
                mActualPowerState = POWER_STATE_ASLEEP;
                mPendingGoToSleepBroadcast = true;
                if (mUserActivityPending) {
                    mUserActivityPending = false;
                    mHandler.removeMessages(MSG_USER_ACTIVITY);
                }
                updatePendingBroadcastLocked();
            }
        }
    }

    /**
     * Called when there has been user activity.
     */
    public void onUserActivity(int event, int uid) {
        if (DEBUG) {
            Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
        }

        try {
            mBatteryStats.noteUserActivity(uid, event);
        } catch (RemoteException ex) {
            // Ignore
        }

        synchronized (mLock) {
            if (!mUserActivityPending) {
                mUserActivityPending = true;
                Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY);
                msg.setAsynchronous(true);
                mHandler.sendMessage(msg);
            }
        }
    }

    /**
     * Called when wireless charging has started so as to provide user feedback.
     */
    public void onWirelessChargingStarted() {
        if (DEBUG) {
            Slog.d(TAG, "onWirelessChargingStarted");
        }

        mSuspendBlocker.acquire();
        Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED);
        msg.setAsynchronous(true);
        mHandler.sendMessage(msg);
    }

    private void updatePendingBroadcastLocked() {
        if (!mBroadcastInProgress
                && mActualPowerState != POWER_STATE_UNKNOWN
                && (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
                        || mActualPowerState != mBroadcastedPowerState)) {
            mBroadcastInProgress = true;
            mSuspendBlocker.acquire();
            Message msg = mHandler.obtainMessage(MSG_BROADCAST);
            msg.setAsynchronous(true);
            mHandler.sendMessage(msg);
        }
    }

    private void finishPendingBroadcastLocked() {
        mBroadcastInProgress = false;
        mSuspendBlocker.release();
    }

    private void sendUserActivity() {
        synchronized (mLock) {
            if (!mUserActivityPending) {
                return;
            }
            mUserActivityPending = false;
        }

        mPolicy.userActivity();
    }

    private void sendNextBroadcast() {
        final int powerState;
        final int goToSleepReason;
        synchronized (mLock) {
            if (mBroadcastedPowerState == POWER_STATE_UNKNOWN) {
                // Broadcasted power state is unknown.  Send wake up.
                mPendingWakeUpBroadcast = false;
                mBroadcastedPowerState = POWER_STATE_AWAKE;
            } else if (mBroadcastedPowerState == POWER_STATE_AWAKE) {
                // Broadcasted power state is awake.  Send asleep if needed.
                if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
                        || mActualPowerState == POWER_STATE_ASLEEP) {
                    mPendingGoToSleepBroadcast = false;
                    mBroadcastedPowerState = POWER_STATE_ASLEEP;
                } else {
                    finishPendingBroadcastLocked();
                    return;
                }
            } else {
                // Broadcasted power state is asleep.  Send awake if needed.
                if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
                        || mActualPowerState == POWER_STATE_AWAKE) {
                    mPendingWakeUpBroadcast = false;
                    mBroadcastedPowerState = POWER_STATE_AWAKE;
                } else {
                    finishPendingBroadcastLocked();
                    return;
                }
            }

            mBroadcastStartTime = SystemClock.uptimeMillis();
            powerState = mBroadcastedPowerState;
            goToSleepReason = mLastGoToSleepReason;
        }

        EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, 1);

        if (powerState == POWER_STATE_AWAKE) {
            sendWakeUpBroadcast();
        } else {
            sendGoToSleepBroadcast(goToSleepReason);
        }
    }

    private void sendWakeUpBroadcast() {
        if (DEBUG) {
            Slog.d(TAG, "Sending wake up broadcast.");
        }

        EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0);

        mPolicy.screenTurningOn(mScreenOnListener);

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

        if (ActivityManagerNative.isSystemReady()) {
            mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null,
                    mWakeUpBroadcastDone, mHandler, 0, null, null);
        } else {
            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
            sendNextBroadcast();
        }
    }

    private final WindowManagerPolicy.ScreenOnListener mScreenOnListener =
            new WindowManagerPolicy.ScreenOnListener() {
        @Override
        public void onScreenOn() {
            synchronized (mLock) {
                if (mScreenOnBlockerAcquired && !mPendingWakeUpBroadcast) {
                    mScreenOnBlockerAcquired = false;
                    mScreenOnBlocker.release();
                }
            }
        }
    };

    private final BroadcastReceiver mWakeUpBroadcastDone = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 1,
                    SystemClock.uptimeMillis() - mBroadcastStartTime, 1);
            sendNextBroadcast();
        }
    };

    private void sendGoToSleepBroadcast(int reason) {
        if (DEBUG) {
            Slog.d(TAG, "Sending go to sleep broadcast.");
        }

        int why = WindowManagerPolicy.OFF_BECAUSE_OF_USER;
        switch (reason) {
            case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
                why = WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN;
                break;
            case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
                why = WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT;
                break;
        }

        EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0);

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

        if (ActivityManagerNative.isSystemReady()) {
            mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,
                    mGoToSleepBroadcastDone, mHandler, 0, null, null);
        } else {
            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
            sendNextBroadcast();
        }
    }

    private final BroadcastReceiver mGoToSleepBroadcastDone = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 0,
                    SystemClock.uptimeMillis() - mBroadcastStartTime, 1);
            sendNextBroadcast();
        }
    };

    private void playWirelessChargingStartedSound() {
        final String soundPath = Settings.Global.getString(mContext.getContentResolver(),
                Settings.Global.WIRELESS_CHARGING_STARTED_SOUND);
        if (soundPath != null) {
            final Uri soundUri = Uri.parse("file://" + soundPath);
            if (soundUri != null) {
                final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
                if (sfx != null) {
                    sfx.setStreamType(AudioManager.STREAM_SYSTEM);
                    sfx.play();
                }
            }
        }

        mSuspendBlocker.release();
    }

    private final class NotifierHandler extends Handler {
        public NotifierHandler(Looper looper) {
            super(looper, null, true /*async*/);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_USER_ACTIVITY:
                    sendUserActivity();
                    break;

                case MSG_BROADCAST:
                    sendNextBroadcast();
                    break;

                case MSG_WIRELESS_CHARGING_STARTED:
                    playWirelessChargingStartedSound();
                    break;
            }
        }
    }
}
