/* * Copyright (C) 2008 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.server.am.ActivityManagerService; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Debug; import android.os.Handler; import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; import android.util.Config; import android.util.EventLog; import android.util.Log; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; /** This class calls its monitor every minute. Killing this process if they don't return **/ public class Watchdog extends Thread { static final String TAG = "Watchdog"; static final boolean localLOGV = false || Config.LOGV; // Set this to true to use debug default values. static final boolean DB = false; static final int MONITOR = 2718; static final int GLOBAL_PSS = 2719; static final int TIME_TO_WAIT = DB ? 15*1000 : 60*1000; static final int EVENT_LOG_TAG = 2802; static final int EVENT_LOG_PROC_PSS_TAG = 2803; static final int EVENT_LOG_SOFT_RESET_TAG = 2804; static final int EVENT_LOG_HARD_RESET_TAG = 2805; static final int EVENT_LOG_PSS_STATS_TAG = 2806; static final int EVENT_LOG_PROC_STATS_TAG = 2807; static final int EVENT_LOG_SCHEDULED_REBOOT_TAG = 2808; static final int EVENT_LOG_MEMINFO_TAG = 2809; static final int EVENT_LOG_VMSTAT_TAG = 2810; static final int EVENT_LOG_REQUESTED_REBOOT_TAG = 2811; static final int MEMCHECK_DEFAULT_INTERVAL = DB ? 30 : 30*60; // 30 minutes static final int MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL = DB ? 60 : 2*60*60; // 2 hours static final int MEMCHECK_DEFAULT_SYSTEM_SOFT_THRESHOLD = (DB ? 10:16)*1024*1024; // 16MB static final int MEMCHECK_DEFAULT_SYSTEM_HARD_THRESHOLD = (DB ? 14:20)*1024*1024; // 20MB static final int MEMCHECK_DEFAULT_PHONE_SOFT_THRESHOLD = (DB ? 4:8)*1024*1024; // 8MB static final int MEMCHECK_DEFAULT_PHONE_HARD_THRESHOLD = (DB ? 8:12)*1024*1024; // 12MB static final int MEMCHECK_DEFAULT_EXEC_START_TIME = 1*60*60; // 1:00am static final int MEMCHECK_DEFAULT_EXEC_END_TIME = 5*60*60; // 5:00am static final int MEMCHECK_DEFAULT_MIN_SCREEN_OFF = DB ? 1*60 : 5*60; // 5 minutes static final int MEMCHECK_DEFAULT_MIN_ALARM = DB ? 1*60 : 3*60; // 3 minutes static final int MEMCHECK_DEFAULT_RECHECK_INTERVAL = DB ? 1*60 : 5*60; // 5 minutes static final int REBOOT_DEFAULT_INTERVAL = DB ? 1 : 0; // never force reboot static final int REBOOT_DEFAULT_START_TIME = 3*60*60; // 3:00am static final int REBOOT_DEFAULT_WINDOW = 60*60; // within 1 hour static final String CHECKUP_ACTION = "com.android.service.Watchdog.CHECKUP"; static final String REBOOT_ACTION = "com.android.service.Watchdog.REBOOT"; static Watchdog sWatchdog; /* This handler will be used to post message back onto the main thread */ final Handler mHandler; final Runnable mGlobalPssCollected; final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>(); ContentResolver mResolver; BatteryService mBattery; PowerManagerService mPower; AlarmManagerService mAlarm; ActivityManagerService mActivity; boolean mCompleted; boolean mForceKillSystem; Monitor mCurrentMonitor; PssRequestor mPhoneReq; int mPhonePid; int mPhonePss; long mLastMemCheckTime = -(MEMCHECK_DEFAULT_INTERVAL*1000); boolean mHavePss; long mLastMemCheckRealtime = -(MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL*1000); boolean mHaveGlobalPss; final MemMonitor mSystemMemMonitor = new MemMonitor("system", Settings.Gservices.MEMCHECK_SYSTEM_ENABLED, Settings.Gservices.MEMCHECK_SYSTEM_SOFT_THRESHOLD, MEMCHECK_DEFAULT_SYSTEM_SOFT_THRESHOLD, Settings.Gservices.MEMCHECK_SYSTEM_HARD_THRESHOLD, MEMCHECK_DEFAULT_SYSTEM_HARD_THRESHOLD); final MemMonitor mPhoneMemMonitor = new MemMonitor("com.android.phone", Settings.Gservices.MEMCHECK_PHONE_ENABLED, Settings.Gservices.MEMCHECK_PHONE_SOFT_THRESHOLD, MEMCHECK_DEFAULT_PHONE_SOFT_THRESHOLD, Settings.Gservices.MEMCHECK_PHONE_HARD_THRESHOLD, MEMCHECK_DEFAULT_PHONE_HARD_THRESHOLD); final Calendar mCalendar = Calendar.getInstance(); long mMemcheckLastTime; long mMemcheckExecStartTime; long mMemcheckExecEndTime; int mMinScreenOff = MEMCHECK_DEFAULT_MIN_SCREEN_OFF; int mMinAlarm = MEMCHECK_DEFAULT_MIN_ALARM; boolean mNeedScheduledCheck; PendingIntent mCheckupIntent; PendingIntent mRebootIntent; long mBootTime; int mRebootInterval; boolean mReqRebootNoWait; // should wait for one interval before reboot? int mReqRebootInterval = -1; // >= 0 if a reboot has been requested int mReqRebootStartTime = -1; // >= 0 if a specific start time has been requested int mReqRebootWindow = -1; // >= 0 if a specific window has been requested int mReqMinScreenOff = -1; // >= 0 if a specific screen off time has been requested int mReqMinNextAlarm = -1; // >= 0 if specific time to next alarm has been requested int mReqRecheckInterval= -1; // >= 0 if a specific recheck interval has been requested /** * This class monitors the memory in a particular process. */ final class MemMonitor { final String mProcessName; final String mEnabledSetting; final String mSoftSetting; final String mHardSetting; int mSoftThreshold; int mHardThreshold; boolean mEnabled; long mLastPss; static final int STATE_OK = 0; static final int STATE_SOFT = 1; static final int STATE_HARD = 2; int mState; MemMonitor(String processName, String enabledSetting, String softSetting, int defSoftThreshold, String hardSetting, int defHardThreshold) { mProcessName = processName; mEnabledSetting = enabledSetting; mSoftSetting = softSetting; mHardSetting = hardSetting; mSoftThreshold = defSoftThreshold; mHardThreshold = defHardThreshold; } void retrieveSettings(ContentResolver resolver) { mSoftThreshold = Settings.Gservices.getInt( resolver, mSoftSetting, mSoftThreshold); mHardThreshold = Settings.Gservices.getInt( resolver, mHardSetting, mHardThreshold); mEnabled = Settings.Gservices.getInt( resolver, mEnabledSetting, 0) != 0; } boolean checkLocked(long curTime, int pid, int pss) { mLastPss = pss; if (mLastPss < mSoftThreshold) { mState = STATE_OK; } else if (mLastPss < mHardThreshold) { mState = STATE_SOFT; } else { mState = STATE_HARD; } EventLog.writeEvent(EVENT_LOG_PROC_PSS_TAG, mProcessName, pid, mLastPss); if (mState == STATE_OK) { // Memory is good, don't recover. return false; } if (mState == STATE_HARD) { // Memory is really bad, kill right now. EventLog.writeEvent(EVENT_LOG_HARD_RESET_TAG, mProcessName, pid, mHardThreshold, mLastPss); return mEnabled; } // It is time to schedule a reset... // Check if we are currently within the time to kill processes due // to memory use. computeMemcheckTimesLocked(curTime); String skipReason = null; if (curTime < mMemcheckExecStartTime || curTime > mMemcheckExecEndTime) { skipReason = "time"; } else { skipReason = shouldWeBeBrutalLocked(curTime); } EventLog.writeEvent(EVENT_LOG_SOFT_RESET_TAG, mProcessName, pid, mSoftThreshold, mLastPss, skipReason != null ? skipReason : ""); if (skipReason != null) { mNeedScheduledCheck = true; return false; } return mEnabled; } void clear() { mLastPss = 0; mState = STATE_OK; } } /** * Used for scheduling monitor callbacks and checking memory usage. */ final class HeartbeatHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case GLOBAL_PSS: { if (mHaveGlobalPss) { // During the last pass we collected pss information, so // now it is time to report it. mHaveGlobalPss = false; if (localLOGV) Log.v(TAG, "Received global pss, logging."); logGlobalMemory(); } } break; case MONITOR: { if (mHavePss) { // During the last pass we collected pss information, so // now it is time to report it. mHavePss = false; if (localLOGV) Log.v(TAG, "Have pss, checking memory."); checkMemory(); } if (mHaveGlobalPss) { // During the last pass we collected pss information, so // now it is time to report it. mHaveGlobalPss = false; if (localLOGV) Log.v(TAG, "Have global pss, logging."); logGlobalMemory(); } long now = SystemClock.uptimeMillis(); // See if we should force a reboot. int rebootInterval = mReqRebootInterval >= 0 ? mReqRebootInterval : Settings.Gservices.getInt( mResolver, Settings.Gservices.REBOOT_INTERVAL, REBOOT_DEFAULT_INTERVAL); if (mRebootInterval != rebootInterval) { mRebootInterval = rebootInterval; // We have been running long enough that a reboot can // be considered... checkReboot(false); } // See if we should check memory conditions. long memCheckInterval = Settings.Gservices.getLong( mResolver, Settings.Gservices.MEMCHECK_INTERVAL, MEMCHECK_DEFAULT_INTERVAL) * 1000; if ((mLastMemCheckTime+memCheckInterval) < now) { // It is now time to collect pss information. This // is async so we won't report it now. And to keep // things simple, we will assume that everyone has // reported back by the next MONITOR message. mLastMemCheckTime = now; if (localLOGV) Log.v(TAG, "Collecting memory usage."); collectMemory(); mHavePss = true; long memCheckRealtimeInterval = Settings.Gservices.getLong( mResolver, Settings.Gservices.MEMCHECK_LOG_REALTIME_INTERVAL, MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL) * 1000; long realtimeNow = SystemClock.elapsedRealtime(); if ((mLastMemCheckRealtime+memCheckRealtimeInterval) < realtimeNow) { mLastMemCheckRealtime = realtimeNow; if (localLOGV) Log.v(TAG, "Collecting global memory usage."); collectGlobalMemory(); mHaveGlobalPss = true; } } final int size = mMonitors.size(); for (int i = 0 ; i < size ; i++) { mCurrentMonitor = mMonitors.get(i); mCurrentMonitor.monitor(); } synchronized (Watchdog.this) { mCompleted = true; mCurrentMonitor = null; } } break; } } } final class GlobalPssCollected implements Runnable { public void run() { mHandler.sendEmptyMessage(GLOBAL_PSS); } } final class CheckupReceiver extends BroadcastReceiver { @Override public void onReceive(Context c, Intent intent) { if (localLOGV) Log.v(TAG, "Alarm went off, checking memory."); checkMemory(); } } final class RebootReceiver extends BroadcastReceiver { @Override public void onReceive(Context c, Intent intent) { if (localLOGV) Log.v(TAG, "Alarm went off, checking reboot."); checkReboot(true); } } final class RebootRequestReceiver extends BroadcastReceiver { @Override public void onReceive(Context c, Intent intent) { mReqRebootNoWait = intent.getIntExtra("nowait", 0) != 0; mReqRebootInterval = intent.getIntExtra("interval", -1); mReqRebootStartTime = intent.getIntExtra("startTime", -1); mReqRebootWindow = intent.getIntExtra("window", -1); mReqMinScreenOff = intent.getIntExtra("minScreenOff", -1); mReqMinNextAlarm = intent.getIntExtra("minNextAlarm", -1); mReqRecheckInterval = intent.getIntExtra("recheckInterval", -1); EventLog.writeEvent(EVENT_LOG_REQUESTED_REBOOT_TAG, mReqRebootNoWait ? 1 : 0, mReqRebootInterval, mReqRecheckInterval, mReqRebootStartTime, mReqRebootWindow, mReqMinScreenOff, mReqMinNextAlarm); checkReboot(true); } } public interface Monitor { void monitor(); } public interface PssRequestor { void requestPss(); } public class PssStats { public int mEmptyPss; public int mEmptyCount; public int mBackgroundPss; public int mBackgroundCount; public int mServicePss; public int mServiceCount; public int mVisiblePss; public int mVisibleCount; public int mForegroundPss; public int mForegroundCount; public int mNoPssCount; public int mProcDeaths[] = new int[10]; } public static Watchdog getInstance() { if (sWatchdog == null) { sWatchdog = new Watchdog(); } return sWatchdog; } private Watchdog() { super("watchdog"); mHandler = new HeartbeatHandler(); mGlobalPssCollected = new GlobalPssCollected(); } public void init(Context context, BatteryService battery, PowerManagerService power, AlarmManagerService alarm, ActivityManagerService activity) { mResolver = context.getContentResolver(); mBattery = battery; mPower = power; mAlarm = alarm; mActivity = activity; context.registerReceiver(new CheckupReceiver(), new IntentFilter(CHECKUP_ACTION)); mCheckupIntent = PendingIntent.getBroadcast(context, 0, new Intent(CHECKUP_ACTION), 0); context.registerReceiver(new RebootReceiver(), new IntentFilter(REBOOT_ACTION)); mRebootIntent = PendingIntent.getBroadcast(context, 0, new Intent(REBOOT_ACTION), 0); context.registerReceiver(new RebootRequestReceiver(), new IntentFilter(Intent.ACTION_REBOOT), android.Manifest.permission.REBOOT, null); mBootTime = System.currentTimeMillis(); } public void processStarted(PssRequestor req, String name, int pid) { synchronized (this) { if ("com.android.phone".equals(name)) { mPhoneReq = req; mPhonePid = pid; mPhonePss = 0; } } } public void reportPss(PssRequestor req, String name, int pss) { synchronized (this) { if (mPhoneReq == req) { mPhonePss = pss; } } } public void addMonitor(Monitor monitor) { synchronized (this) { if (isAlive()) { throw new RuntimeException("Monitors can't be added while the Watchdog is running"); } mMonitors.add(monitor); } } /** * Retrieve memory usage information from specific processes being * monitored. This is an async operation, so must be done before doing * memory checks. */ void collectMemory() { synchronized (this) { if (mPhoneReq != null) { mPhoneReq.requestPss(); } } } /** * Retrieve memory usage over all application processes. This is an * async operation, so must be done before doing memory checks. */ void collectGlobalMemory() { mActivity.requestPss(mGlobalPssCollected); } /** * Check memory usage in the system, scheduling kills/reboots as needed. * This always runs on the mHandler thread. */ void checkMemory() { boolean needScheduledCheck; long curTime; long nextTime = 0; long recheckInterval = Settings.Gservices.getLong( mResolver, Settings.Gservices.MEMCHECK_RECHECK_INTERVAL, MEMCHECK_DEFAULT_RECHECK_INTERVAL) * 1000; mSystemMemMonitor.retrieveSettings(mResolver); mPhoneMemMonitor.retrieveSettings(mResolver); retrieveBrutalityAmount(); synchronized (this) { curTime = System.currentTimeMillis(); mNeedScheduledCheck = false; // How is the system doing? if (mSystemMemMonitor.checkLocked(curTime, Process.myPid(), (int)Process.getPss(Process.myPid()))) { // Not good! Time to suicide. mForceKillSystem = true; notifyAll(); return; } // How is the phone process doing? if (mPhoneReq != null) { if (mPhoneMemMonitor.checkLocked(curTime, mPhonePid, mPhonePss)) { // Just kill the phone process and let it restart. Process.killProcess(mPhonePid); } } else { mPhoneMemMonitor.clear(); } needScheduledCheck = mNeedScheduledCheck; if (needScheduledCheck) { // Something is going bad, but now is not a good time to // tear things down... schedule an alarm to check again soon. nextTime = curTime + recheckInterval; if (nextTime < mMemcheckExecStartTime) { nextTime = mMemcheckExecStartTime; } else if (nextTime >= mMemcheckExecEndTime){ // Need to check during next exec time... so that needs // to be computed. if (localLOGV) Log.v(TAG, "Computing next time range"); computeMemcheckTimesLocked(nextTime); nextTime = mMemcheckExecStartTime; } if (localLOGV) { mCalendar.setTimeInMillis(nextTime); Log.v(TAG, "Next Alarm Time: " + mCalendar); } } } if (needScheduledCheck) { if (localLOGV) Log.v(TAG, "Scheduling next memcheck alarm for " + ((nextTime-curTime)/1000/60) + "m from now"); mAlarm.remove(mCheckupIntent); mAlarm.set(AlarmManager.RTC_WAKEUP, nextTime, mCheckupIntent); } else { if (localLOGV) Log.v(TAG, "No need to schedule a memcheck alarm!"); mAlarm.remove(mCheckupIntent); } } final PssStats mPssStats = new PssStats(); final String[] mMemInfoFields = new String[] { "MemFree:", "Buffers:", "Cached:", "Active:", "Inactive:", "AnonPages:", "Mapped:", "Slab:", "SReclaimable:", "SUnreclaim:", "PageTables:" }; final long[] mMemInfoSizes = new long[mMemInfoFields.length]; final String[] mVMStatFields = new String[] { "pgfree ", "pgactivate ", "pgdeactivate ", "pgfault ", "pgmajfault " }; final long[] mVMStatSizes = new long[mVMStatFields.length]; final long[] mPrevVMStatSizes = new long[mVMStatFields.length]; long mLastLogGlobalMemoryTime; void logGlobalMemory() { PssStats stats = mPssStats; mActivity.collectPss(stats); EventLog.writeEvent(EVENT_LOG_PSS_STATS_TAG, stats.mEmptyPss, stats.mEmptyCount, stats.mBackgroundPss, stats.mBackgroundCount, stats.mServicePss, stats.mServiceCount, stats.mVisiblePss, stats.mVisibleCount, stats.mForegroundPss, stats.mForegroundCount, stats.mNoPssCount); EventLog.writeEvent(EVENT_LOG_PROC_STATS_TAG, stats.mProcDeaths[0], stats.mProcDeaths[1], stats.mProcDeaths[2], stats.mProcDeaths[3], stats.mProcDeaths[4]); Process.readProcLines("/proc/meminfo", mMemInfoFields, mMemInfoSizes); for (int i=0; i<mMemInfoSizes.length; i++) { mMemInfoSizes[i] *= 1024; } EventLog.writeEvent(EVENT_LOG_MEMINFO_TAG, (int)mMemInfoSizes[0], (int)mMemInfoSizes[1], (int)mMemInfoSizes[2], (int)mMemInfoSizes[3], (int)mMemInfoSizes[4], (int)mMemInfoSizes[5], (int)mMemInfoSizes[6], (int)mMemInfoSizes[7], (int)mMemInfoSizes[8], (int)mMemInfoSizes[9], (int)mMemInfoSizes[10]); long now = SystemClock.uptimeMillis(); long dur = now - mLastLogGlobalMemoryTime; mLastLogGlobalMemoryTime = now; Process.readProcLines("/proc/vmstat", mVMStatFields, mVMStatSizes); for (int i=0; i<mVMStatSizes.length; i++) { long v = mVMStatSizes[i]; mVMStatSizes[i] -= mPrevVMStatSizes[i]; mPrevVMStatSizes[i] = v; } EventLog.writeEvent(EVENT_LOG_VMSTAT_TAG, dur, (int)mVMStatSizes[0], (int)mVMStatSizes[1], (int)mVMStatSizes[2], (int)mVMStatSizes[3], (int)mVMStatSizes[4]); } void checkReboot(boolean fromAlarm) { int rebootInterval = mReqRebootInterval >= 0 ? mReqRebootInterval : Settings.Gservices.getInt( mResolver, Settings.Gservices.REBOOT_INTERVAL, REBOOT_DEFAULT_INTERVAL); mRebootInterval = rebootInterval; if (rebootInterval <= 0) { // No reboot interval requested. if (localLOGV) Log.v(TAG, "No need to schedule a reboot alarm!"); mAlarm.remove(mRebootIntent); return; } long rebootStartTime = mReqRebootStartTime >= 0 ? mReqRebootStartTime : Settings.Gservices.getLong( mResolver, Settings.Gservices.REBOOT_START_TIME, REBOOT_DEFAULT_START_TIME); long rebootWindowMillis = (mReqRebootWindow >= 0 ? mReqRebootWindow : Settings.Gservices.getLong( mResolver, Settings.Gservices.REBOOT_WINDOW, REBOOT_DEFAULT_WINDOW)) * 1000; long recheckInterval = (mReqRecheckInterval >= 0 ? mReqRecheckInterval : Settings.Gservices.getLong( mResolver, Settings.Gservices.MEMCHECK_RECHECK_INTERVAL, MEMCHECK_DEFAULT_RECHECK_INTERVAL)) * 1000; retrieveBrutalityAmount(); long realStartTime; long now; synchronized (this) { now = System.currentTimeMillis(); realStartTime = computeCalendarTime(mCalendar, now, rebootStartTime); long rebootIntervalMillis = rebootInterval*24*60*60*1000; if (DB || mReqRebootNoWait || (now-mBootTime) >= (rebootIntervalMillis-rebootWindowMillis)) { if (fromAlarm && rebootWindowMillis <= 0) { // No reboot window -- just immediately reboot. EventLog.writeEvent(EVENT_LOG_SCHEDULED_REBOOT_TAG, now, (int)rebootIntervalMillis, (int)rebootStartTime*1000, (int)rebootWindowMillis, ""); rebootSystem("Checkin scheduled forced"); return; } // Are we within the reboot window? if (now < realStartTime) { // Schedule alarm for next check interval. realStartTime = computeCalendarTime(mCalendar, now, rebootStartTime); } else if (now < (realStartTime+rebootWindowMillis)) { String doit = shouldWeBeBrutalLocked(now); EventLog.writeEvent(EVENT_LOG_SCHEDULED_REBOOT_TAG, now, (int)rebootInterval, (int)rebootStartTime*1000, (int)rebootWindowMillis, doit != null ? doit : ""); if (doit == null) { rebootSystem("Checked scheduled range"); return; } // Schedule next alarm either within the window or in the // next interval. if ((now+recheckInterval) >= (realStartTime+rebootWindowMillis)) { realStartTime = computeCalendarTime(mCalendar, now + rebootIntervalMillis, rebootStartTime); } else { realStartTime = now + recheckInterval; } } else { // Schedule alarm for next check interval. realStartTime = computeCalendarTime(mCalendar, now + rebootIntervalMillis, rebootStartTime); } } } if (localLOGV) Log.v(TAG, "Scheduling next reboot alarm for " + ((realStartTime-now)/1000/60) + "m from now"); mAlarm.remove(mRebootIntent); mAlarm.set(AlarmManager.RTC_WAKEUP, realStartTime, mRebootIntent); } /** * Perform a full reboot of the system. */ void rebootSystem(String reason) { Log.i(TAG, "Rebooting system because: " + reason); try { android.os.Power.reboot(reason); } catch (IOException e) { Log.e(TAG, "Reboot failed!", e); } } /** * Load the current Gservices settings for when * {@link #shouldWeBeBrutalLocked} will allow the brutality to happen. * Must not be called with the lock held. */ void retrieveBrutalityAmount() { mMinScreenOff = (mReqMinScreenOff >= 0 ? mReqMinScreenOff : Settings.Gservices.getInt( mResolver, Settings.Gservices.MEMCHECK_MIN_SCREEN_OFF, MEMCHECK_DEFAULT_MIN_SCREEN_OFF)) * 1000; mMinAlarm = (mReqMinNextAlarm >= 0 ? mReqMinNextAlarm : Settings.Gservices.getInt( mResolver, Settings.Gservices.MEMCHECK_MIN_ALARM, MEMCHECK_DEFAULT_MIN_ALARM)) * 1000; } /** * Determine whether it is a good time to kill, crash, or otherwise * plunder the current situation for the overall long-term benefit of * the world. * * @param curTime The current system time. * @return Returns null if this is a good time, else a String with the * text of why it is not a good time. */ String shouldWeBeBrutalLocked(long curTime) { if (mBattery == null || !mBattery.isPowered()) { return "battery"; } if (mMinScreenOff >= 0 && (mPower == null || mPower.timeSinceScreenOn() < mMinScreenOff)) { return "screen"; } if (mMinAlarm >= 0 && (mAlarm == null || mAlarm.timeToNextAlarm() < mMinAlarm)) { return "alarm"; } return null; } /** * Compute the times during which we next would like to perform process * restarts. * * @param curTime The current system time. */ void computeMemcheckTimesLocked(long curTime) { if (mMemcheckLastTime == curTime) { return; } mMemcheckLastTime = curTime; long memcheckExecStartTime = Settings.Gservices.getLong( mResolver, Settings.Gservices.MEMCHECK_EXEC_START_TIME, MEMCHECK_DEFAULT_EXEC_START_TIME); long memcheckExecEndTime = Settings.Gservices.getLong( mResolver, Settings.Gservices.MEMCHECK_EXEC_END_TIME, MEMCHECK_DEFAULT_EXEC_END_TIME); mMemcheckExecEndTime = computeCalendarTime(mCalendar, curTime, memcheckExecEndTime); if (mMemcheckExecEndTime < curTime) { memcheckExecStartTime += 24*60*60; memcheckExecEndTime += 24*60*60; mMemcheckExecEndTime = computeCalendarTime(mCalendar, curTime, memcheckExecEndTime); } mMemcheckExecStartTime = computeCalendarTime(mCalendar, curTime, memcheckExecStartTime); if (localLOGV) { mCalendar.setTimeInMillis(curTime); Log.v(TAG, "Current Time: " + mCalendar); mCalendar.setTimeInMillis(mMemcheckExecStartTime); Log.v(TAG, "Start Check Time: " + mCalendar); mCalendar.setTimeInMillis(mMemcheckExecEndTime); Log.v(TAG, "End Check Time: " + mCalendar); } } static long computeCalendarTime(Calendar c, long curTime, long secondsSinceMidnight) { // start with now c.setTimeInMillis(curTime); int val = (int)secondsSinceMidnight / (60*60); c.set(Calendar.HOUR_OF_DAY, val); secondsSinceMidnight -= val * (60*60); val = (int)secondsSinceMidnight / 60; c.set(Calendar.MINUTE, val); c.set(Calendar.SECOND, (int)secondsSinceMidnight - (val*60)); c.set(Calendar.MILLISECOND, 0); long newTime = c.getTimeInMillis(); if (newTime < curTime) { // The given time (in seconds since midnight) has already passed for today, so advance // by one day (due to daylight savings, etc., the delta may differ from 24 hours). c.add(Calendar.DAY_OF_MONTH, 1); newTime = c.getTimeInMillis(); } return newTime; } @Override public void run() { while (true) { mCompleted = false; mHandler.sendEmptyMessage(MONITOR); synchronized (this) { long timeout = TIME_TO_WAIT; // NOTE: We use uptimeMillis() here because we do not want to increment the time we // wait while asleep. If the device is asleep then the thing that we are waiting // to timeout on is asleep as well and won't have a chance to run. Causing a false // positive on when to kill things. long start = SystemClock.uptimeMillis(); do { try { wait(timeout); } catch (InterruptedException e) { if (SystemProperties.getBoolean("ro.secure", false)) { // If this is a secure build, just log the error. Log.e("WatchDog", "Woof! Woof! Interrupter!"); } else { throw new AssertionError("Someone interrupted the watchdog"); } } timeout = TIME_TO_WAIT - (SystemClock.uptimeMillis() - start); } while (timeout > 0 && !mForceKillSystem); if (mCompleted && !mForceKillSystem) { // The monitors have returned. continue; } } // If we got here, that means that the system is most likely hung. // First send a SIGQUIT so that we can see where it was hung. Then // kill this process so that the system will restart. String name = (mCurrentMonitor != null) ? mCurrentMonitor.getClass().getName() : "null"; EventLog.writeEvent(EVENT_LOG_TAG, name); Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT); // Wait a bit longer before killing so we can make sure that the stacks are captured. try { Thread.sleep(10*1000); } catch (InterruptedException e) { } // Only kill the process if the debugger is not attached. if (!Debug.isDebuggerConnected()) { Process.killProcess(Process.myPid()); } } } }