Truncate(*1) it if WAL file is too big when opening DB

*1 -- not truncate(2) but "PRAGMA wal_checkpoint(TRUNCATE)"

Otherwise, depending on how an app operate on a DB, SQLite may not
have a chance to "shrink" the WAL file.

Fixes: 112777941
Bug: 111939259
Test: atest /android/master/frameworks/base/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
Test: Manual test with google dailer:
1. With normalized_spam.db-wal > 100MB and receive a phone call
-> WAL file gets truncated to 0 bytes.

2. Restart the dialer process and receive a phone call again
-> WAL file is already 0 bytes; won't be truncated.

3. Restart with the WAL file deleted
Same as #2. WAL file will be created before the added logic, but is 0 bytes,
so it won't be truncated.

4. Test with settings put global sqlite_compatibility_wal_flags truncate_size=1024
-> make sure the threshold is overridden

Merged-in: I2b193603e5dfa493ccccb8123db592f0e9c0e7ae
Change-Id: I2b193603e5dfa493ccccb8123db592f0e9c0e7ae
(cherry picked from commit 96e06002edc374511c545d96ae9b2b7c1e8d78ec)
diff --git a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java
index 5bf3a7c..06c069c 100644
--- a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java
+++ b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java
@@ -41,6 +41,7 @@
     private static volatile boolean sFlagsSet;
     private static volatile boolean sCompatibilityWalSupported;
     private static volatile String sWALSyncMode;
+    private static volatile long sTruncateSize = -1;
     // This flag is used to avoid recursive initialization due to circular dependency on Settings
     private static volatile boolean sCallingGlobalSettings;
 
@@ -71,6 +72,19 @@
         return sWALSyncMode;
     }
 
+    /**
+     * Override {@link com.android.internal.R.integer#db_wal_truncate_size}.
+     *
+     * @return the value set in the global setting, or -1 if a value is not set.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static long getTruncateSize() {
+        initIfNeeded();
+        return sTruncateSize;
+    }
+
     private static void initIfNeeded() {
         if (sInitialized || sCallingGlobalSettings) {
             return;
@@ -115,6 +129,7 @@
         sCompatibilityWalSupported = parser.getBoolean("compatibility_wal_supported",
                 SQLiteGlobal.isCompatibilityWalSupported());
         sWALSyncMode = parser.getString("wal_syncmode", SQLiteGlobal.getWALSyncMode());
+        sTruncateSize = parser.getInt("truncate_size", -1);
         Log.i(TAG, "Read compatibility WAL flags: compatibility_wal_supported="
                 + sCompatibilityWalSupported + ", wal_syncmode=" + sWALSyncMode);
         sFlagsSet = true;
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 101fb82..9c639a5 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -32,6 +32,7 @@
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
 
+import java.io.File;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -303,6 +304,7 @@
                 } else {
                     setSyncMode(SQLiteGlobal.getWALSyncMode());
                 }
+                maybeTruncateWalFile();
             } else {
                 setJournalMode(mConfiguration.journalMode == null
                         ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
@@ -312,6 +314,40 @@
         }
     }
 
+    /**
+     * If the WAL file exists and larger than a threshold, truncate it by executing
+     * PRAGMA wal_checkpoint.
+     */
+    private void maybeTruncateWalFile() {
+        final long threshold = SQLiteGlobal.getWALTruncateSize();
+        if (DEBUG) {
+            Log.d(TAG, "Truncate threshold=" + threshold);
+        }
+        if (threshold == 0) {
+            return;
+        }
+
+        final File walFile = new File(mConfiguration.path + "-wal");
+        if (!walFile.isFile()) {
+            return;
+        }
+        final long size = walFile.length();
+        if (size < threshold) {
+            if (DEBUG) {
+                Log.d(TAG, walFile.getAbsolutePath() + " " + size + " bytes: No need to truncate");
+            }
+            return;
+        }
+
+        Log.i(TAG, walFile.getAbsolutePath() + " " + size + " bytes: Bigger than "
+                + threshold + "; truncating");
+        try {
+            executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
+        } catch (SQLiteException e) {
+            Log.w(TAG, "Failed to truncate the -wal file", e);
+        }
+    }
+
     private void setSyncMode(String newValue) {
         String value = executeForString("PRAGMA synchronous", null, null);
         if (!canonicalizeSyncMode(value).equalsIgnoreCase(
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index e6b6acf..67e5f65 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -164,4 +164,21 @@
                         com.android.internal.R.integer.db_default_idle_connection_timeout));
     }
 
+    /**
+     * When opening a database, if the WAL file is larger than this size, we'll truncate it.
+     *
+     * (If it's 0, we do not truncate.)
+     *
+     * @hide
+     */
+    public static long getWALTruncateSize() {
+        final long setting = SQLiteCompatibilityWalFlags.getTruncateSize();
+        if (setting >= 0) {
+            return setting;
+        }
+        return SystemProperties.getInt("debug.sqlite.wal.truncatesize",
+                Resources.getSystem().getInteger(
+                        com.android.internal.R.integer.db_wal_truncate_size));
+    }
+
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fbe6f3e..e00ff20 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12777,6 +12777,7 @@
          * Supported keys:
          * compatibility_wal_supported      (boolean)
          * wal_syncmode       (String)
+         * truncate_size      (int)
          *
          * @hide
          */
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a9d69c1..a10c197 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1873,6 +1873,10 @@
          truncate it after committing the transaction. -->
     <integer name="db_journal_size_limit">524288</integer>
 
+    <!-- When opening a database with WAL enabled and if the wal file already exists and larger
+         than this size in bytes, we'll truncate it. -->
+    <integer name="db_wal_truncate_size">1048576</integer>
+
     <!-- The database synchronization mode when using the default journal mode.
          FULL is safest and preserves durability at the cost of extra fsyncs.
          NORMAL also preserves durability in non-WAL modes and uses checksums to ensure
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a3ed1b0..95af9a6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3415,4 +3415,6 @@
 
   <java-symbol type="string" name="config_misprovisionedDeviceModel" />
   <java-symbol type="string" name="config_misprovisionedBrandValue" />
+
+  <java-symbol type="integer" name="db_wal_truncate_size" />
 </resources>
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
index 230655d..551a58e 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
@@ -62,18 +62,24 @@
         assertTrue(SQLiteCompatibilityWalFlags.areFlagsSet());
         assertFalse(SQLiteCompatibilityWalFlags.isCompatibilityWalSupported());
         assertEquals("OFF", SQLiteCompatibilityWalFlags.getWALSyncMode());
+        assertEquals(-1, SQLiteCompatibilityWalFlags.getTruncateSize());
 
         SQLiteCompatibilityWalFlags.init("wal_syncmode=VALUE");
         assertTrue(SQLiteCompatibilityWalFlags.areFlagsSet());
         assertEquals(SQLiteGlobal.isCompatibilityWalSupported(),
                 SQLiteCompatibilityWalFlags.isCompatibilityWalSupported());
         assertEquals("VALUE", SQLiteCompatibilityWalFlags.getWALSyncMode());
+        assertEquals(-1, SQLiteCompatibilityWalFlags.getTruncateSize());
 
         SQLiteCompatibilityWalFlags.init("compatibility_wal_supported=true");
         assertTrue(SQLiteCompatibilityWalFlags.areFlagsSet());
         assertEquals(SQLiteGlobal.getWALSyncMode(),
                 SQLiteCompatibilityWalFlags.getWALSyncMode());
         assertTrue(SQLiteCompatibilityWalFlags.isCompatibilityWalSupported());
+        assertEquals(-1, SQLiteCompatibilityWalFlags.getTruncateSize());
+
+        SQLiteCompatibilityWalFlags.init("truncate_size=1024");
+        assertEquals(1024, SQLiteCompatibilityWalFlags.getTruncateSize());
 
         SQLiteCompatibilityWalFlags.reset();
         SQLiteCompatibilityWalFlags.init("Invalid value");