blob: 85861bbfe81a70d2e3efbcf3863a65e0549d78da [file] [log] [blame]
/*
* Copyright (C) 2007-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.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StatFs;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings.Gservices;
import android.util.Config;
import android.util.EventLog;
import android.util.Log;
import android.provider.Settings;
/**
* This class implements a service to monitor the amount of disk storage space
* on the device. If the free storage on device is less than a tunable threshold value
* (default is 10%. this value is a gservices parameter) a low memory notification is
* displayed to alert the user. If the user clicks on the low memory notification the
* Application Manager application gets launched to let the user free storage space.
* Event log events:
* A low memory event with the free storage on device in bytes is logged to the event log
* when the device goes low on storage space.
* The amount of free storage on the device is periodically logged to the event log. The log
* interval is a gservices parameter with a default value of 12 hours
* When the free storage differential goes below a threshold(again a gservices parameter with
* a default value of 2MB), the free memory is logged to the event log
*/
class DeviceStorageMonitorService extends Binder {
private static final String TAG = "DeviceStorageMonitorService";
private static final boolean DEBUG = false;
private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
private static final int DEVICE_MEMORY_WHAT = 1;
private static final int MONITOR_INTERVAL = 1; //in minutes
private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
private static final int EVENT_LOG_STORAGE_BELOW_THRESHOLD = 2744;
private static final int EVENT_LOG_LOW_STORAGE_NOTIFICATION = 2745;
private static final int EVENT_LOG_FREE_STORAGE_LEFT = 2746;
private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
private long mFreeMem;
private long mLastReportedFreeMem;
private long mLastReportedFreeMemTime;
private boolean mLowMemFlag=false;
private Context mContext;
private ContentResolver mContentResolver;
int mBlkSize;
long mTotalMemory;
StatFs mFileStats;
private static final String DATA_PATH="/data";
long mThreadStartTime = -1;
boolean mClearSucceeded = false;
boolean mClearingCache;
private Intent mStorageLowIntent;
private Intent mStorageOkIntent;
private CachePackageDataObserver mClearCacheObserver;
private static final int _TRUE = 1;
private static final int _FALSE = 0;
/**
* This string is used for ServiceManager access to this class.
*/
static final String SERVICE = "devicestoragemonitor";
/**
* Handler that checks the amount of disk space on the device and sends a
* notification if the device runs low on disk space
*/
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//dont handle an invalid message
if (msg.what != DEVICE_MEMORY_WHAT) {
Log.e(TAG, "Will not process invalid message");
return;
}
checkMemory(msg.arg1 == _TRUE);
}
};
class CachePackageDataObserver extends IPackageDataObserver.Stub {
public void onRemoveCompleted(String packageName, boolean succeeded) {
mClearSucceeded = succeeded;
mClearingCache = false;
if(localLOGV) Log.i(TAG, " Clear succeeded:"+mClearSucceeded
+", mClearingCache:"+mClearingCache+" Forcing memory check");
postCheckMemoryMsg(false, 0);
}
}
private final void restatDataDir() {
mFileStats.restat(DATA_PATH);
mFreeMem = mFileStats.getAvailableBlocks()*mBlkSize;
// Allow freemem to be overridden by debug.freemem for testing
String debugFreeMem = SystemProperties.get("debug.freemem");
if (!"".equals(debugFreeMem)) {
mFreeMem = Long.parseLong(debugFreeMem);
}
// Read the log interval from Gservices
long freeMemLogInterval = Gservices.getLong(mContentResolver,
Gservices.SYS_FREE_STORAGE_LOG_INTERVAL,
DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
//log the amount of free memory in event log
long currTime = SystemClock.elapsedRealtime();
if((mLastReportedFreeMemTime == 0) ||
(currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
mLastReportedFreeMemTime = currTime;
EventLog.writeEvent(EVENT_LOG_FREE_STORAGE_LEFT, mFreeMem);
}
// Read the reporting threshold from Gservices
long threshold = Gservices.getLong(mContentResolver,
Gservices.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
// If mFree changed significantly log the new value
long delta = mFreeMem - mLastReportedFreeMem;
if (delta > threshold || delta < -threshold) {
mLastReportedFreeMem = mFreeMem;
EventLog.writeEvent(EVENT_LOG_STORAGE_BELOW_THRESHOLD, mFreeMem);
}
}
private final void clearCache() {
if (mClearCacheObserver == null) {
// Lazy instantiation
mClearCacheObserver = new CachePackageDataObserver();
}
mClearingCache = true;
try {
if (localLOGV) Log.i(TAG, "Clearing cache");
IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
freeStorageAndNotify(getMemThreshold(), mClearCacheObserver);
} catch (RemoteException e) {
Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
mClearingCache = false;
mClearSucceeded = false;
}
}
private final void checkMemory(boolean checkCache) {
//if the thread that was started to clear cache is still running do nothing till its
//finished clearing cache. Ideally this flag could be modified by clearCache
// and should be accessed via a lock but even if it does this test will fail now and
//hopefully the next time this flag will be set to the correct value.
if(mClearingCache) {
if(localLOGV) Log.i(TAG, "Thread already running just skip");
//make sure the thread is not hung for too long
long diffTime = System.currentTimeMillis() - mThreadStartTime;
if(diffTime > (10*60*1000)) {
Log.w(TAG, "Thread that clears cache file seems to run for ever");
}
} else {
restatDataDir();
if (localLOGV) Log.v(TAG, "freeMemory="+mFreeMem);
//post intent to NotificationManager to display icon if necessary
long memThreshold = getMemThreshold();
if (mFreeMem < memThreshold) {
if (!mLowMemFlag) {
if (checkCache) {
// See if clearing cache helps
// Note that clearing cache is asynchronous and so we do a
// memory check again once the cache has been cleared.
mThreadStartTime = System.currentTimeMillis();
mClearSucceeded = false;
clearCache();
} else {
Log.i(TAG, "Running low on memory. Sending notification");
sendNotification();
mLowMemFlag = true;
}
} else {
if (localLOGV) Log.v(TAG, "Running low on memory " +
"notification already sent. do nothing");
}
} else {
if (mLowMemFlag) {
Log.i(TAG, "Memory available. Cancelling notification");
cancelNotification();
mLowMemFlag = false;
}
}
}
if(localLOGV) Log.i(TAG, "Posting Message again");
//keep posting messages to itself periodically
postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
}
private void postCheckMemoryMsg(boolean clearCache, long delay) {
// Remove queued messages
mHandler.removeMessages(DEVICE_MEMORY_WHAT);
mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
clearCache ?_TRUE : _FALSE, 0),
delay);
}
/*
* just query settings to retrieve the memory threshold.
* Preferred this over using a ContentObserver since Settings.Gservices caches the value
* any way
*/
private long getMemThreshold() {
int value = Settings.Gservices.getInt(
mContentResolver,
Settings.Gservices.SYS_STORAGE_THRESHOLD_PERCENTAGE,
DEFAULT_THRESHOLD_PERCENTAGE);
if(localLOGV) Log.v(TAG, "Threshold Percentage="+value);
//evaluate threshold value
return mTotalMemory*value;
}
/**
* Constructor to run service. initializes the disk space threshold value
* and posts an empty message to kickstart the process.
*/
public DeviceStorageMonitorService(Context context) {
mLastReportedFreeMemTime = 0;
mContext = context;
mContentResolver = mContext.getContentResolver();
//create StatFs object
mFileStats = new StatFs(DATA_PATH);
//initialize block size
mBlkSize = mFileStats.getBlockSize();
//initialize total storage on device
mTotalMemory = (mFileStats.getBlockCount()*mBlkSize)/100;
mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
checkMemory(true);
}
/**
* This method sends a notification to NotificationManager to display
* an error dialog indicating low disk space and launch the Installer
* application
*/
private final void sendNotification() {
if(localLOGV) Log.i(TAG, "Sending low memory notification");
//log the event to event log with the amount of free storage(in bytes) left on the device
EventLog.writeEvent(EVENT_LOG_LOW_STORAGE_NOTIFICATION, mFreeMem);
// Pack up the values and broadcast them to everyone
Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
lowMemIntent.putExtra("memory", mFreeMem);
lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
NotificationManager mNotificationMgr =
(NotificationManager)mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
CharSequence title = mContext.getText(
com.android.internal.R.string.low_internal_storage_view_title);
CharSequence details = mContext.getText(
com.android.internal.R.string.low_internal_storage_view_text);
PendingIntent intent = PendingIntent.getActivity(mContext, 0, lowMemIntent, 0);
Notification notification = new Notification();
notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
notification.tickerText = title;
notification.flags |= Notification.FLAG_NO_CLEAR;
notification.setLatestEventInfo(mContext, title, details, intent);
mNotificationMgr.notify(LOW_MEMORY_NOTIFICATION_ID, notification);
mContext.sendStickyBroadcast(mStorageLowIntent);
}
/**
* Cancels low storage notification and sends OK intent.
*/
private final void cancelNotification() {
if(localLOGV) Log.i(TAG, "Canceling low memory notification");
NotificationManager mNotificationMgr =
(NotificationManager)mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
//cancel notification since memory has been freed
mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID);
mContext.removeStickyBroadcast(mStorageLowIntent);
mContext.sendBroadcast(mStorageOkIntent);
}
public void updateMemory() {
int callingUid = getCallingUid();
if(callingUid != Process.SYSTEM_UID) {
return;
}
// force an early check
postCheckMemoryMsg(true, 0);
}
}