Merge "Performance improvements for db schema update."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4a7367c..ba475c6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -15,6 +15,16 @@
<provider android:name="MediaProvider" android:authorities="media"
android:multiprocess="false" />
+ <!-- Handles database upgrades after OTAs, then disables itself -->
+ <receiver android:name="MediaUpgradeReceiver">
+ <!-- This broadcast is sent after the core system has finished
+ booting, before the home app is launched or BOOT_COMPLETED
+ is sent. -->
+ <intent-filter>
+ <action android:name="android.intent.action.PRE_BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
+
<receiver android:name="MediaScannerReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 634d21b..fc70b73 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -261,21 +261,27 @@
* external card, or with internal storage). Can open the actual database
* on demand, create and upgrade the schema, etc.
*/
- private final class DatabaseHelper extends SQLiteOpenHelper {
+ static final class DatabaseHelper extends SQLiteOpenHelper {
final Context mContext;
final String mName;
final boolean mInternal; // True if this is the internal database
+ final boolean mEarlyUpgrade;
+ final SQLiteDatabase.CustomFunction mObjectRemovedCallback;
boolean mUpgradeAttempted; // Used for upgrade error handling
// In memory caches of artist and album data.
HashMap<String, Long> mArtistCache = new HashMap<String, Long>();
HashMap<String, Long> mAlbumCache = new HashMap<String, Long>();
- public DatabaseHelper(Context context, String name, boolean internal) {
+ public DatabaseHelper(Context context, String name, boolean internal,
+ boolean earlyUpgrade,
+ SQLiteDatabase.CustomFunction objectRemovedCallback) {
super(context, name, null, DATABASE_VERSION);
mContext = context;
mName = name;
mInternal = internal;
+ mEarlyUpgrade = earlyUpgrade;
+ mObjectRemovedCallback = objectRemovedCallback;
}
/**
@@ -334,7 +340,11 @@
if (mInternal) return; // The internal database is kept separately.
- db.addCustomFunction("_OBJECT_REMOVED", 1, mObjectRemovedCallback);
+ if (mEarlyUpgrade) return; // Doing early upgrade.
+
+ if (mObjectRemovedCallback != null) {
+ db.addCustomFunction("_OBJECT_REMOVED", 1, mObjectRemovedCallback);
+ }
// the code below is only needed on devices with removable storage
if (!Environment.isExternalStorageRemovable()) return;
@@ -4224,6 +4234,26 @@
return null;
}
+ static boolean isMediaDatabaseName(String name) {
+ if (INTERNAL_DATABASE_NAME.equals(name)) {
+ return true;
+ }
+ if (EXTERNAL_DATABASE_NAME.equals(name)) {
+ return true;
+ }
+ if (name.startsWith("external-")) {
+ return true;
+ }
+ return false;
+ }
+
+ static boolean isInternalMediaDatabaseName(String name) {
+ if (INTERNAL_DATABASE_NAME.equals(name)) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Attach the database for a volume (internal or external).
* Does nothing if the volume is already attached, otherwise
@@ -4246,7 +4276,8 @@
Context context = getContext();
DatabaseHelper db;
if (INTERNAL_VOLUME.equals(volume)) {
- db = new DatabaseHelper(context, INTERNAL_DATABASE_NAME, true);
+ db = new DatabaseHelper(context, INTERNAL_DATABASE_NAME, true,
+ false, mObjectRemovedCallback);
} else if (EXTERNAL_VOLUME.equals(volume)) {
if (Environment.isExternalStorageRemovable()) {
String path = mExternalStoragePaths[0];
@@ -4255,7 +4286,8 @@
// generate database name based on volume ID
String dbName = "external-" + Integer.toHexString(volumeID) + ".db";
- db = new DatabaseHelper(context, dbName, false);
+ db = new DatabaseHelper(context, dbName, false,
+ false, mObjectRemovedCallback);
mVolumeId = volumeID;
} else {
// external database name should be EXTERNAL_DATABASE_NAME
@@ -4295,7 +4327,8 @@
}
// else DatabaseHelper will create one named EXTERNAL_DATABASE_NAME
}
- db = new DatabaseHelper(context, dbFile.getName(), false);
+ db = new DatabaseHelper(context, dbFile.getName(), false,
+ false, mObjectRemovedCallback);
}
} else {
throw new IllegalArgumentException("There is no volume named " + volume);
diff --git a/src/com/android/providers/media/MediaUpgradeReceiver.java b/src/com/android/providers/media/MediaUpgradeReceiver.java
new file mode 100644
index 0000000..0e5a45c
--- /dev/null
+++ b/src/com/android/providers/media/MediaUpgradeReceiver.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2011 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.providers.media;
+
+import java.io.File;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * This will be launched during system boot, after the core system has
+ * been brought up but before any non-persistent processes have been
+ * started. It is launched in a special state, with no content provider
+ * or custom application class associated with the process running.
+ *
+ * It's job is to prime the contacts database. Either create it
+ * if it doesn't exist, or open it and force any necessary upgrades.
+ * All of this heavy lifting happens before the boot animation ends.
+ */
+public class MediaUpgradeReceiver extends BroadcastReceiver {
+ static final String TAG = "MediaUpgradeReceiver";
+ static final String PREF_DB_VERSION = "db_version";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // We are now running with the system up, but no apps started,
+ // so can do whatever cleanup after an upgrade that we want.
+
+ try {
+ File dbDir = context.getDatabasePath("foo").getParentFile();
+ String[] files = dbDir.list();
+ for (int i=0; i<files.length; i++) {
+ String file = files[i];
+ if (MediaProvider.isMediaDatabaseName(file)) {
+ long startTime = System.currentTimeMillis();
+ Slog.i(TAG, "---> Start upgrade of media database " + file);
+ SQLiteDatabase db = null;
+ try {
+ MediaProvider.DatabaseHelper helper = new MediaProvider.DatabaseHelper(
+ context, file, MediaProvider.isInternalMediaDatabaseName(file),
+ false, null);
+ db = helper.getWritableDatabase();
+ } catch (Throwable t) {
+ Log.wtf(TAG, "Error during upgrade of media db " + file, t);
+ } finally {
+ if (db != null) {
+ db.close();
+ }
+ }
+ Slog.i(TAG, "<--- Finished upgrade of media database " + file
+ + " in " + (System.currentTimeMillis()-startTime) + "ms");
+ }
+ }
+ } catch (Throwable t) {
+ // Something has gone terribly wrong.
+ Log.wtf(TAG, "Error during upgrade attempt.", t);
+ }
+ }
+}