WebStorageSizeManager.java revision eb9032c67077aa5aa6ddf928bf14a45534f24776
179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu/* 279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * Copyright (C) 2009 The Android Open Source Project 379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * Licensed under the Apache License, Version 2.0 (the "License"); 579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * you may not use this file except in compliance with the License. 679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * You may obtain a copy of the License at 779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * http://www.apache.org/licenses/LICENSE-2.0 979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 1079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * Unless required by applicable law or agreed to in writing, software 1179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * distributed under the License is distributed on an "AS IS" BASIS, 1279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * See the License for the specific language governing permissions and 1479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * limitations under the License. 1579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu */ 1679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 1779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescupackage com.android.browser; 1879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 1986943777260a399df282517241085d0c5e10ee88Andrei Popescuimport android.app.Notification; 2086943777260a399df282517241085d0c5e10ee88Andrei Popescuimport android.app.NotificationManager; 2186943777260a399df282517241085d0c5e10ee88Andrei Popescuimport android.app.PendingIntent; 2279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescuimport android.content.Context; 2386943777260a399df282517241085d0c5e10ee88Andrei Popescuimport android.content.Intent; 2479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescuimport android.os.StatFs; 2579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescuimport android.util.Log; 2679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescuimport android.webkit.WebStorage; 2779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 2879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescuimport java.io.File; 2979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescuimport java.util.Set; 3079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 3179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 3279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu/** 3379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * Package level class for managing the disk size consumed by the WebDatabase 3479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * and ApplicationCaches APIs (henceforth called Web storage). 3579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 3679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * Currently, the situation on the WebKit side is as follows: 3779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * - WebDatabase enforces a quota for each origin. 3879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * - Session/LocalStorage do not enforce any disk limits. 3979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * - ApplicationCaches enforces a maximum size for all origins. 4079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 4179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * The WebStorageSizeManager maintains a global limit for the disk space 4279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * consumed by the WebDatabase and ApplicationCaches. As soon as WebKit will 4379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * have a limit for Session/LocalStorage, this class will manage the space used 4479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * by those APIs as well. 4579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 4679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * The global limit is computed as a function of the size of the partition where 4779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * these APIs store their data (they must store it on the same partition for 4879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * this to work) and the size of the available space on that partition. 4979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * The global limit is not subject to user configuration but we do provide 5079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * a debug-only setting. 5179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * TODO(andreip): implement the debug setting. 5279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 5379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * The size of the disk space used for Web storage is initially divided between 5479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * WebDatabase and ApplicationCaches as follows: 5579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 5679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 75% for WebDatabase 5779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 25% for ApplicationCaches 5879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 5979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * When an origin's database usage reaches its current quota, WebKit invokes 6079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * the following callback function: 6179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * - exceededDatabaseQuota(Frame* frame, const String& database_name); 6279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * Note that the default quota for a new origin is 0, so we will receive the 6379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 'exceededDatabaseQuota' callback before a new origin gets the chance to 6479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * create its first database. 6579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 6679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * When the total ApplicationCaches usage reaches its current quota, WebKit 6779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * invokes the following callback function: 6879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * - void reachedMaxAppCacheSize(int64_t spaceNeeded); 6979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 7079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * The WebStorageSizeManager's main job is to respond to the above two callbacks 7179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * by inspecting the amount of unused Web storage quota (i.e. global limit - 7279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * sum of all other origins' quota) and deciding if a quota increase for the 7379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * out-of-space origin is allowed or not. 7479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 7525a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch * The default quota for an origin is its estimated size. If we cannot satisfy 7625a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch * the estimated size, then WebCore will not create the database. 7779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * Quota increases are done in steps, where the increase step is 7879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * min(QUOTA_INCREASE_STEP, unused_quota). 7979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * 8079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * When all the Web storage space is used, the WebStorageSizeManager creates 8179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * a system notification that will guide the user to the WebSettings UI. There, 8279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * the user can free some of the Web storage space by deleting all the data used 8379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * by an origin. 8479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu */ 8579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescuclass WebStorageSizeManager { 8679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // Logging flags. 8779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED; 8879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED; 8979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu private final static String LOGTAG = "browser"; 9079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // The default quota value for an origin. 91907c5763bb700c705108948328c8892500e59185Andrei Popescu public final static long ORIGIN_DEFAULT_QUOTA = 3 * 1024 * 1024; // 3MB 9279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // The default value for quota increases. 93907c5763bb700c705108948328c8892500e59185Andrei Popescu public final static long QUOTA_INCREASE_STEP = 1 * 1024 * 1024; // 1MB 9486943777260a399df282517241085d0c5e10ee88Andrei Popescu // Extra padding space for appcache maximum size increases. This is needed 9586943777260a399df282517241085d0c5e10ee88Andrei Popescu // because WebKit sends us an estimate of the amount of space needed 9686943777260a399df282517241085d0c5e10ee88Andrei Popescu // but this estimate may, currently, be slightly less than what is actually 9786943777260a399df282517241085d0c5e10ee88Andrei Popescu // needed. We therefore add some 'padding'. 9886943777260a399df282517241085d0c5e10ee88Andrei Popescu // TODO(andreip): fix this in WebKit. 9986943777260a399df282517241085d0c5e10ee88Andrei Popescu public final static long APPCACHE_MAXSIZE_PADDING = 512 * 1024; // 512KB 10086943777260a399df282517241085d0c5e10ee88Andrei Popescu // The system status bar notification id. 10186943777260a399df282517241085d0c5e10ee88Andrei Popescu private final static int OUT_OF_SPACE_ID = 1; 102eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard // The time of the last out of space notification 103eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard private static long mLastOutOfSpaceNotificationTime = -1; 104eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard // Delay between two notification in ms 105eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard private final static long NOTIFICATION_INTERVAL = 5 * 60 * 1000; 106eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard // Delay in ms used when resetting the notification time 107eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard private final static long RESET_NOTIFICATION_INTERVAL = 3 * 1000; 10879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // The application context. 109907c5763bb700c705108948328c8892500e59185Andrei Popescu private final Context mContext; 11079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // The global Web storage limit. 111907c5763bb700c705108948328c8892500e59185Andrei Popescu private final long mGlobalLimit; 11279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // The maximum size of the application cache file. 11379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu private long mAppCacheMaxSize; 11479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 11579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu /** 116907c5763bb700c705108948328c8892500e59185Andrei Popescu * Interface used by the WebStorageSizeManager to obtain information 117907c5763bb700c705108948328c8892500e59185Andrei Popescu * about the underlying file system. This functionality is separated 118907c5763bb700c705108948328c8892500e59185Andrei Popescu * into its own interface mainly for testing purposes. 11979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu */ 120907c5763bb700c705108948328c8892500e59185Andrei Popescu public interface DiskInfo { 121907c5763bb700c705108948328c8892500e59185Andrei Popescu /** 122907c5763bb700c705108948328c8892500e59185Andrei Popescu * @return the size of the free space in the file system. 123907c5763bb700c705108948328c8892500e59185Andrei Popescu */ 124907c5763bb700c705108948328c8892500e59185Andrei Popescu public long getFreeSpaceSizeBytes(); 125907c5763bb700c705108948328c8892500e59185Andrei Popescu 126907c5763bb700c705108948328c8892500e59185Andrei Popescu /** 127907c5763bb700c705108948328c8892500e59185Andrei Popescu * @return the total size of the file system. 128907c5763bb700c705108948328c8892500e59185Andrei Popescu */ 129907c5763bb700c705108948328c8892500e59185Andrei Popescu public long getTotalSizeBytes(); 130907c5763bb700c705108948328c8892500e59185Andrei Popescu }; 131907c5763bb700c705108948328c8892500e59185Andrei Popescu 132907c5763bb700c705108948328c8892500e59185Andrei Popescu private DiskInfo mDiskInfo; 133907c5763bb700c705108948328c8892500e59185Andrei Popescu // For convenience, we provide a DiskInfo implementation that uses StatFs. 134907c5763bb700c705108948328c8892500e59185Andrei Popescu public static class StatFsDiskInfo implements DiskInfo { 135907c5763bb700c705108948328c8892500e59185Andrei Popescu private StatFs mFs; 136907c5763bb700c705108948328c8892500e59185Andrei Popescu 137907c5763bb700c705108948328c8892500e59185Andrei Popescu public StatFsDiskInfo(String path) { 138907c5763bb700c705108948328c8892500e59185Andrei Popescu mFs = new StatFs(path); 139907c5763bb700c705108948328c8892500e59185Andrei Popescu } 140907c5763bb700c705108948328c8892500e59185Andrei Popescu 141907c5763bb700c705108948328c8892500e59185Andrei Popescu public long getFreeSpaceSizeBytes() { 142907c5763bb700c705108948328c8892500e59185Andrei Popescu return mFs.getAvailableBlocks() * mFs.getBlockSize(); 143907c5763bb700c705108948328c8892500e59185Andrei Popescu } 144907c5763bb700c705108948328c8892500e59185Andrei Popescu 145907c5763bb700c705108948328c8892500e59185Andrei Popescu public long getTotalSizeBytes() { 146907c5763bb700c705108948328c8892500e59185Andrei Popescu return mFs.getBlockCount() * mFs.getBlockSize(); 147907c5763bb700c705108948328c8892500e59185Andrei Popescu } 148907c5763bb700c705108948328c8892500e59185Andrei Popescu }; 149907c5763bb700c705108948328c8892500e59185Andrei Popescu 150907c5763bb700c705108948328c8892500e59185Andrei Popescu /** 151907c5763bb700c705108948328c8892500e59185Andrei Popescu * Interface used by the WebStorageSizeManager to obtain information 152907c5763bb700c705108948328c8892500e59185Andrei Popescu * about the appcache file. This functionality is separated into its own 153907c5763bb700c705108948328c8892500e59185Andrei Popescu * interface mainly for testing purposes. 154907c5763bb700c705108948328c8892500e59185Andrei Popescu */ 155907c5763bb700c705108948328c8892500e59185Andrei Popescu public interface AppCacheInfo { 156907c5763bb700c705108948328c8892500e59185Andrei Popescu /** 157907c5763bb700c705108948328c8892500e59185Andrei Popescu * @return the current size of the appcache file. 158907c5763bb700c705108948328c8892500e59185Andrei Popescu */ 159907c5763bb700c705108948328c8892500e59185Andrei Popescu public long getAppCacheSizeBytes(); 160907c5763bb700c705108948328c8892500e59185Andrei Popescu }; 161907c5763bb700c705108948328c8892500e59185Andrei Popescu 162907c5763bb700c705108948328c8892500e59185Andrei Popescu // For convenience, we provide an AppCacheInfo implementation. 163907c5763bb700c705108948328c8892500e59185Andrei Popescu public static class WebKitAppCacheInfo implements AppCacheInfo { 164907c5763bb700c705108948328c8892500e59185Andrei Popescu // The name of the application cache file. Keep in sync with 165907c5763bb700c705108948328c8892500e59185Andrei Popescu // WebCore/loader/appcache/ApplicationCacheStorage.cpp 166907c5763bb700c705108948328c8892500e59185Andrei Popescu private final static String APPCACHE_FILE = "ApplicationCache.db"; 167907c5763bb700c705108948328c8892500e59185Andrei Popescu private String mAppCachePath; 168907c5763bb700c705108948328c8892500e59185Andrei Popescu 169907c5763bb700c705108948328c8892500e59185Andrei Popescu public WebKitAppCacheInfo(String path) { 170907c5763bb700c705108948328c8892500e59185Andrei Popescu mAppCachePath = path; 171907c5763bb700c705108948328c8892500e59185Andrei Popescu } 172907c5763bb700c705108948328c8892500e59185Andrei Popescu 173907c5763bb700c705108948328c8892500e59185Andrei Popescu public long getAppCacheSizeBytes() { 174907c5763bb700c705108948328c8892500e59185Andrei Popescu File file = new File(mAppCachePath 175907c5763bb700c705108948328c8892500e59185Andrei Popescu + File.separator 176907c5763bb700c705108948328c8892500e59185Andrei Popescu + APPCACHE_FILE); 177907c5763bb700c705108948328c8892500e59185Andrei Popescu return file.length(); 178907c5763bb700c705108948328c8892500e59185Andrei Popescu } 179907c5763bb700c705108948328c8892500e59185Andrei Popescu }; 180907c5763bb700c705108948328c8892500e59185Andrei Popescu 181907c5763bb700c705108948328c8892500e59185Andrei Popescu /** 182907c5763bb700c705108948328c8892500e59185Andrei Popescu * Public ctor 183907c5763bb700c705108948328c8892500e59185Andrei Popescu * @param ctx is the application context 184907c5763bb700c705108948328c8892500e59185Andrei Popescu * @param diskInfo is the DiskInfo instance used to query the file system. 185907c5763bb700c705108948328c8892500e59185Andrei Popescu * @param appCacheInfo is the AppCacheInfo used to query info about the 186907c5763bb700c705108948328c8892500e59185Andrei Popescu * appcache file. 187907c5763bb700c705108948328c8892500e59185Andrei Popescu */ 188907c5763bb700c705108948328c8892500e59185Andrei Popescu public WebStorageSizeManager(Context ctx, DiskInfo diskInfo, 189907c5763bb700c705108948328c8892500e59185Andrei Popescu AppCacheInfo appCacheInfo) { 190907c5763bb700c705108948328c8892500e59185Andrei Popescu mContext = ctx; 191907c5763bb700c705108948328c8892500e59185Andrei Popescu mDiskInfo = diskInfo; 192907c5763bb700c705108948328c8892500e59185Andrei Popescu mGlobalLimit = getGlobalLimit(); 193907c5763bb700c705108948328c8892500e59185Andrei Popescu // The initial max size of the app cache is either 25% of the global 194907c5763bb700c705108948328c8892500e59185Andrei Popescu // limit or the current size of the app cache file, whichever is bigger. 195907c5763bb700c705108948328c8892500e59185Andrei Popescu mAppCacheMaxSize = Math.max(mGlobalLimit / 4, 196907c5763bb700c705108948328c8892500e59185Andrei Popescu appCacheInfo.getAppCacheSizeBytes()); 19779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 19879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 19979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu /** 20079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * Returns the maximum size of the application cache. 20179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu */ 20279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu public long getAppCacheMaxSize() { 20379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu return mAppCacheMaxSize; 20479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 20579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 20679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu /** 20779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * The origin has exceeded its database quota. 20879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * @param url the URL that exceeded the quota 20979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * @param databaseIdentifier the identifier of the database on 21079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * which the transaction that caused the quota overflow was run 21179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * @param currentQuota the current quota for the origin. 21279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * @param totalUsedQuota is the sum of all origins' quota. 21379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * @param quotaUpdater The callback to run when a decision to allow or 21479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * deny quota has been made. Don't forget to call this! 21579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu */ 21679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu public void onExceededDatabaseQuota(String url, 21725a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch String databaseIdentifier, long currentQuota, long estimatedSize, 21825a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { 21979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu if(LOGV_ENABLED) { 22079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu Log.v(LOGTAG, 22179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu "Received onExceededDatabaseQuota for " 22279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu + url 22379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu + ":" 22479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu + databaseIdentifier 22579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu + "(current quota: " 22679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu + currentQuota 227907c5763bb700c705108948328c8892500e59185Andrei Popescu + ", total used quota: " 228907c5763bb700c705108948328c8892500e59185Andrei Popescu + totalUsedQuota 22979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu + ")"); 23079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 23179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu long totalUnusedQuota = mGlobalLimit - totalUsedQuota - mAppCacheMaxSize; 23279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 233907c5763bb700c705108948328c8892500e59185Andrei Popescu if (totalUnusedQuota <= 0) { 23479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // There definitely isn't any more space. Fire notifications 23586943777260a399df282517241085d0c5e10ee88Andrei Popescu // if needed and exit. 23686943777260a399df282517241085d0c5e10ee88Andrei Popescu if (totalUsedQuota > 0) { 23786943777260a399df282517241085d0c5e10ee88Andrei Popescu // We only fire the notification if there are some other websites 23886943777260a399df282517241085d0c5e10ee88Andrei Popescu // using some of the quota. This avoids the degenerate case where 23986943777260a399df282517241085d0c5e10ee88Andrei Popescu // the first ever website to use Web storage tries to use more 24086943777260a399df282517241085d0c5e10ee88Andrei Popescu // data than it is actually available. In such a case, showing 24186943777260a399df282517241085d0c5e10ee88Andrei Popescu // the notification would not help at all since there is nothing 24286943777260a399df282517241085d0c5e10ee88Andrei Popescu // the user can do. 24386943777260a399df282517241085d0c5e10ee88Andrei Popescu scheduleOutOfSpaceNotification(); 24486943777260a399df282517241085d0c5e10ee88Andrei Popescu } 24579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu quotaUpdater.updateQuota(currentQuota); 24679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu if(LOGV_ENABLED) { 24779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu Log.v(LOGTAG, "onExceededDatabaseQuota: out of space."); 24879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 24979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu return; 25079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 25179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // We have enough space inside mGlobalLimit. 25279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu long newOriginQuota = currentQuota; 25379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu if (newOriginQuota == 0) { 25425a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch // This is a new origin, give it the size it asked for if possible. 25525a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch // If we cannot satisfy the estimatedSize, we should return 0 as 25625a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch // returning a value less that what the site requested will lead 25725a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch // to webcore not creating the database. 25825a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch if (totalUnusedQuota >= estimatedSize) { 25925a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch newOriginQuota = estimatedSize; 26025a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch } else { 26125a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch if (LOGV_ENABLED) { 26225a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch Log.v(LOGTAG, 26325a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch "onExceededDatabaseQuota: Unable to satisfy" + 26425a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch " estimatedSize for the new database " + 26525a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch " (estimatedSize: " + estimatedSize + 26625a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch ", unused quota: " + totalUnusedQuota); 26725a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch } 26825a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch newOriginQuota = 0; 26925a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch } 27079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } else { 27179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // This is an origin we have seen before. It wants a quota 27279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // increase. 27379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu newOriginQuota += 27479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu Math.min(QUOTA_INCREASE_STEP, totalUnusedQuota); 27579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 27679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu quotaUpdater.updateQuota(newOriginQuota); 27779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 27879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu if(LOGV_ENABLED) { 27979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu Log.v(LOGTAG, "onExceededDatabaseQuota set new quota to " 28079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu + newOriginQuota); 28179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 28279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 28379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 28479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu /** 28579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * The Application Cache has exceeded its max size. 28679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * @param spaceNeeded is the amount of disk space that would be needed 28779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * in order for the last appcache operation to succeed. 28879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * @param totalUsedQuota is the sum of all origins' quota. 28979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * @param quotaUpdater A callback to inform the WebCore thread that a new 29079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * app cache size is available. This callback must always be executed at 29179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * some point to ensure that the sleeping WebCore thread is woken up. 29279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu */ 29379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota, 29479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu WebStorage.QuotaUpdater quotaUpdater) { 29579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu if(LOGV_ENABLED) { 29679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu Log.v(LOGTAG, "Received onReachedMaxAppCacheSize with spaceNeeded " 29779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu + spaceNeeded + " bytes."); 29879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 29979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 30079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu long totalUnusedQuota = mGlobalLimit - totalUsedQuota - mAppCacheMaxSize; 30179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 30286943777260a399df282517241085d0c5e10ee88Andrei Popescu if (totalUnusedQuota < spaceNeeded + APPCACHE_MAXSIZE_PADDING) { 30379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // There definitely isn't any more space. Fire notifications 30486943777260a399df282517241085d0c5e10ee88Andrei Popescu // if needed and exit. 30586943777260a399df282517241085d0c5e10ee88Andrei Popescu if (totalUsedQuota > 0) { 30686943777260a399df282517241085d0c5e10ee88Andrei Popescu // We only fire the notification if there are some other websites 30786943777260a399df282517241085d0c5e10ee88Andrei Popescu // using some of the quota. This avoids the degenerate case where 30886943777260a399df282517241085d0c5e10ee88Andrei Popescu // the first ever website to use Web storage tries to use more 30986943777260a399df282517241085d0c5e10ee88Andrei Popescu // data than it is actually available. In such a case, showing 31086943777260a399df282517241085d0c5e10ee88Andrei Popescu // the notification would not help at all since there is nothing 31186943777260a399df282517241085d0c5e10ee88Andrei Popescu // the user can do. 31286943777260a399df282517241085d0c5e10ee88Andrei Popescu scheduleOutOfSpaceNotification(); 31386943777260a399df282517241085d0c5e10ee88Andrei Popescu } 31479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu quotaUpdater.updateQuota(0); 31579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu if(LOGV_ENABLED) { 31679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu Log.v(LOGTAG, "onReachedMaxAppCacheSize: out of space."); 31779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 31879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu return; 31979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 32079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // There is enough space to accommodate spaceNeeded bytes. 32186943777260a399df282517241085d0c5e10ee88Andrei Popescu mAppCacheMaxSize += spaceNeeded + APPCACHE_MAXSIZE_PADDING; 32279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu quotaUpdater.updateQuota(mAppCacheMaxSize); 32379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 32479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu if(LOGV_ENABLED) { 32579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu Log.v(LOGTAG, "onReachedMaxAppCacheSize set new max size to " 32679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu + mAppCacheMaxSize); 32779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 32879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 32979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 330eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard // Reset the notification time; we use this iff the user 331eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard // use clear all; we reset it to some time in the future instead 332eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard // of just setting it to -1, as the clear all method is asynchronous 333eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard static void resetLastOutOfSpaceNotificationTime() { 334eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard mLastOutOfSpaceNotificationTime = System.currentTimeMillis() - 335eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard NOTIFICATION_INTERVAL + RESET_NOTIFICATION_INTERVAL; 336eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard } 337eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard 33879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // Computes the global limit as a function of the size of the data 33979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // partition and the amount of free space on that partition. 340907c5763bb700c705108948328c8892500e59185Andrei Popescu private long getGlobalLimit() { 341907c5763bb700c705108948328c8892500e59185Andrei Popescu long freeSpace = mDiskInfo.getFreeSpaceSizeBytes(); 342907c5763bb700c705108948328c8892500e59185Andrei Popescu long fileSystemSize = mDiskInfo.getTotalSizeBytes(); 34379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu return calculateGlobalLimit(fileSystemSize, freeSpace); 34479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 34579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 34679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu /*package*/ static long calculateGlobalLimit(long fileSystemSizeBytes, 34779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu long freeSpaceBytes) { 34879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu if (fileSystemSizeBytes <= 0 34979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu || freeSpaceBytes <= 0 35079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu || freeSpaceBytes > fileSystemSizeBytes) { 35179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu return 0; 35279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 35379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 35479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu long fileSystemSizeRatio = 35579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 2 << ((int) Math.floor(Math.log10( 35679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu fileSystemSizeBytes / (1024 * 1024)))); 35779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu long maxSizeBytes = (long) Math.min(Math.floor( 35879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu fileSystemSizeBytes / fileSystemSizeRatio), 35979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu Math.floor(freeSpaceBytes / 2)); 36079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // Round maxSizeBytes up to a multiple of 1024KB (but only if 36179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // maxSizeBytes > 1MB). 36279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu long maxSizeStepBytes = 1024 * 1024; 36379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu if (maxSizeBytes < maxSizeStepBytes) { 36479e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu return 0; 36579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 36679e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu long roundingExtra = maxSizeBytes % maxSizeStepBytes == 0 ? 0 : 1; 36779e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu return (maxSizeStepBytes 36879e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu * ((maxSizeBytes / maxSizeStepBytes) + roundingExtra)); 36979e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 37079e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu 37179e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // Schedules a system notification that takes the user to the WebSettings 37279e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu // activity when clicked. 37379e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu private void scheduleOutOfSpaceNotification() { 37486943777260a399df282517241085d0c5e10ee88Andrei Popescu if(LOGV_ENABLED) { 37586943777260a399df282517241085d0c5e10ee88Andrei Popescu Log.v(LOGTAG, "scheduleOutOfSpaceNotification called."); 37686943777260a399df282517241085d0c5e10ee88Andrei Popescu } 37786943777260a399df282517241085d0c5e10ee88Andrei Popescu if (mContext == null) { 37886943777260a399df282517241085d0c5e10ee88Andrei Popescu // mContext can be null if we're running unit tests. 37986943777260a399df282517241085d0c5e10ee88Andrei Popescu return; 38086943777260a399df282517241085d0c5e10ee88Andrei Popescu } 381eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard if ((mLastOutOfSpaceNotificationTime == -1) || 382eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard (System.currentTimeMillis() - mLastOutOfSpaceNotificationTime > NOTIFICATION_INTERVAL)) { 383eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard // setup the notification boilerplate. 384eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard int icon = android.R.drawable.stat_sys_warning; 385eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard CharSequence title = mContext.getString( 386eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard R.string.webstorage_outofspace_notification_title); 387eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard CharSequence text = mContext.getString( 388eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard R.string.webstorage_outofspace_notification_text); 389eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard long when = System.currentTimeMillis(); 390eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard Intent intent = new Intent(mContext, WebsiteSettingsActivity.class); 391eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard PendingIntent contentIntent = 392eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard PendingIntent.getActivity(mContext, 0, intent, 0); 393eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard Notification notification = new Notification(icon, title, when); 394eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard notification.setLatestEventInfo(mContext, title, text, contentIntent); 395eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard notification.flags |= Notification.FLAG_AUTO_CANCEL; 396eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard // Fire away. 397eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard String ns = Context.NOTIFICATION_SERVICE; 398eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard NotificationManager mgr = 399eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard (NotificationManager) mContext.getSystemService(ns); 400eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard if (mgr != null) { 401eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard mLastOutOfSpaceNotificationTime = System.currentTimeMillis(); 402eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard mgr.notify(OUT_OF_SPACE_ID, notification); 403eb9032c67077aa5aa6ddf928bf14a45534f24776Nicolas Roard } 40486943777260a399df282517241085d0c5e10ee88Andrei Popescu } 40579e82b7ba72a7278911edf0dd7b03c65c4ec0e9dAndrei Popescu } 40625a1523642bead2f7e7f929ba9d6d1143dce06a0Ben Murdoch} 407