Enforce app cache maximum size
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 2301289..d2371cd 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -25,6 +25,7 @@
 import android.content.SharedPreferences.Editor;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceScreen;
+import android.os.StatFs;
 import android.webkit.CookieManager;
 import android.webkit.WebView;
 import android.webkit.WebViewDatabase;
@@ -77,6 +78,7 @@
     // The Browser always enables Application Caches.
     private boolean appCacheEnabled = true;
     private String appCachePath;  // default value set in loadFromDb().
+    private long appCacheMaxSize = Long.MAX_VALUE;
     private boolean domStorageEnabled = true;
     private String jsFlags = "";
 
@@ -209,6 +211,7 @@
             // Turn on Application Caches.
             s.setAppCachePath(b.appCachePath);
             s.setAppCacheEnabled(b.appCacheEnabled);
+            s.setAppCacheMaxSize(b.appCacheMaxSize);
 
             // Enable/Disable the error console.
             b.mTabControl.getBrowserActivity().setShouldShowErrorConsole(
@@ -234,6 +237,8 @@
         pluginsPath = ctx.getDir("plugins", 0).getPath();
         // Set the default value for the Application Caches path.
         appCachePath = ctx.getDir("appcache", 0).getPath();
+        // Determine the maximum size of the application cache.
+        appCacheMaxSize = getAppCacheMaxSize();
         // Set the default value for the Database path.
         databasePath = ctx.getDir("databases", 0).getPath();
 
@@ -538,6 +543,35 @@
         return url;
     }
 
+    private long getAppCacheMaxSize() {
+        StatFs dataPartition = new StatFs(appCachePath);
+        long freeSpace = dataPartition.getAvailableBlocks()
+            * dataPartition.getBlockSize();
+        long fileSystemSize = dataPartition.getBlockCount()
+            * dataPartition.getBlockSize();
+        return calculateAppCacheMaxSize(fileSystemSize, freeSpace);
+    }
+
+    /*package*/ static long calculateAppCacheMaxSize(long fileSystemSizeBytes,
+            long freeSpaceBytes) {
+        if (fileSystemSizeBytes <= 0
+                || freeSpaceBytes <= 0
+                || freeSpaceBytes > fileSystemSizeBytes) {
+            return 0;
+        }
+
+        long fileSystemSizeRatio =
+            4 << ((int) Math.floor(Math.log10(fileSystemSizeBytes / (1024 * 1024))));
+        long maxSizeBytes = (long) Math.min(Math.floor(fileSystemSizeBytes / fileSystemSizeRatio),
+                Math.floor(freeSpaceBytes / 4));
+        // Round maxSizeBytes up to a multiple of 512KB (except when freeSpaceBytes < 1MB).
+        long maxSizeStepBytes = 512 * 1024;
+        if (freeSpaceBytes < maxSizeStepBytes * 2) {
+            return 0;
+        }
+        return (maxSizeStepBytes * ((maxSizeBytes / maxSizeStepBytes) + 1));
+    }
+
     // Private constructor that does nothing.
     private BrowserSettings() {
     }
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..f86942d
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,37 @@
+# Copyright 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Notice that we don't have to include the src files of Browser because, by
+# running the tests using an instrumentation targeting Browser, we
+# automatically get all of its classes loaded into our environment.
+
+LOCAL_PACKAGE_NAME := BrowserTests
+
+LOCAL_INSTRUMENTATION_FOR := Browser
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..51715a9
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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 name must be unique so suffix with "tests" so package loader doesn't ignore us -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.browser.tests">
+
+    <!-- We add an application tag here just so that we can indicate that
+         this package needs to link against the android.test library,
+         which is needed when building test cases. -->
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!--
+    This declares that this app uses the instrumentation test runner targeting
+    the package of com.android.email.  To run the tests use the command:
+    "adb shell am instrument -w com.android.browser.tests/android.test.InstrumentationTestRunner"
+    -->
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+                     android:targetPackage="com.android.browser"
+                     android:label="Tests for Browser."/>
+
+</manifest>
diff --git a/tests/src/com/android/browser/BrowserSettingsUnitTests.java b/tests/src/com/android/browser/BrowserSettingsUnitTests.java
new file mode 100644
index 0000000..fdaa5c8
--- /dev/null
+++ b/tests/src/com/android/browser/BrowserSettingsUnitTests.java
@@ -0,0 +1,88 @@
+/*
+ * 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.browser;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * This is a series of unit tests for the BrowserSettings class.
+ *
+ */
+@MediumTest
+public class BrowserSettingsUnitTests extends AndroidTestCase {
+
+    /**
+     * Test the application caches max size calculator.
+     */
+    public void testCalculateAppCacheMaxSize() {
+        long fileSystemSize = 78643200;  // 75 MB
+        long freeSpaceSize = 25165824;  // 24 MB
+        long maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(6815744, maxSize);  // 6.5MB
+
+        fileSystemSize = 78643200;  // 75 MB
+        freeSpaceSize = 60 * 1024 * 1024;  // 60MB
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(9961472, maxSize);  // 9.5MB
+
+        fileSystemSize = 8589934592L;  // 8 GB
+        freeSpaceSize = 4294967296L;  // 4 GB
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(268959744L, maxSize);  // 256.5 MB
+
+        fileSystemSize = -14;
+        freeSpaceSize = 21;
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(0, maxSize);
+
+        fileSystemSize = 100;
+        freeSpaceSize = 101;
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(0, maxSize);
+
+        fileSystemSize = 3774873; // ~4.2 MB
+        freeSpaceSize = 2560000;  // ~2.4 MB
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(1048576, maxSize);  // 1 MB
+
+        fileSystemSize = 4404019; // ~4.2 MB
+        freeSpaceSize = 3774873;  // ~3.6 MB
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(1048576, maxSize);  // 1 MB
+
+        fileSystemSize = 4404019; // ~4.2 MB
+        freeSpaceSize = 4404019;  // ~4.2 MB
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(1572864, maxSize);  // 1.5 MB
+
+        fileSystemSize = 1048576; // 1 MB
+        freeSpaceSize = 1048575;  // 1 MB - 1 byte
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(0, maxSize);
+
+        fileSystemSize = 1048576; // 1 MB
+        freeSpaceSize = 1048576;  // 1 MB
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(524288, maxSize);  // 512KB
+
+        fileSystemSize = 3774873; // ~3.6 MB
+        freeSpaceSize = 2097151;  // 2 MB - 1 byte
+        maxSize = BrowserSettings.calculateAppCacheMaxSize(fileSystemSize, freeSpaceSize);
+        assertEquals(524288, maxSize);  // 512KB
+    }
+}