Loading res/raw/downgrade_schema.json 0 → 100644 +20 −0 Original line number Diff line number Diff line { // Note: Comments are not supported in JSON schema, but android parser is lenient. // Maximum DB version supported by this schema "version" : 27, // Downgrade from 27 to 26. Empty array indicates, the DB is compatible "downgrade_to_26" : [], "downgrade_to_25" : [], "downgrade_to_24" : [], "downgrade_to_23" : [], "downgrade_to_22" : [ "ALTER TABLE favorites RENAME TO temp_favorites;", "CREATE TABLE favorites(_id INTEGER PRIMARY KEY, title TEXT, intent TEXT, container INTEGER, screen INTEGER, cellX INTEGER, cellY INTEGER, spanX INTEGER, spanY INTEGER, itemType INTEGER, appWidgetId INTEGER NOT NULL DEFAULT - 1, iconPackage TEXT, iconResource TEXT, icon BLOB, appWidgetProvider TEXT, modified INTEGER NOT NULL DEFAULT 0, restored INTEGER NOT NULL DEFAULT 0, profileId INTEGER DEFAULT 0, rank INTEGER NOT NULL DEFAULT 0);", "INSERT INTO favorites SELECT _id, title, intent, container, screen, cellX, cellY, spanX, spanY, itemType, appWidgetId, iconPackage, iconResource, icon, appWidgetProvider, modified, restored, profileId, rank FROM temp_favorites;", "DROP TABLE temp_favorites;" ] // Missing values indicate the DB is not compatible } No newline at end of file res/xml/backupscheme.xml +1 −0 Original line number Diff line number Diff line Loading @@ -3,5 +3,6 @@ <include domain="database" path="launcher.db" /> <include domain="sharedpref" path="com.android.launcher3.prefs.xml" /> <include domain="file" path="downgrade_schema.json" /> </full-backup-content> No newline at end of file src/com/android/launcher3/LauncherProvider.java +31 −53 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.graphics.IconShapeOverride; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.DbDowngradeHelper; import com.android.launcher3.provider.LauncherDbUtils; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.provider.RestoreDbTask; Loading @@ -64,6 +65,7 @@ import com.android.launcher3.util.NoLocaleSqliteContext; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Thunk; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.reflect.Method; Loading @@ -77,18 +79,12 @@ public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; private static final boolean LOGD = false; private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json"; /** * Represents the schema of the database. Changes in scheme need not be backwards compatible. */ private static final int SCHEMA_VERSION = 27; /** * Represents the actual data. It could include additional validations and normalizations added * overtime. These must be backwards compatible, else we risk breaking old devices during * restore or binary version downgrade. */ private static final int DATA_VERSION = 3; private static final String PREF_KEY_DATA_VERISON = "provider_data_version"; public static final int SCHEMA_VERSION = 27; public static final String AUTHORITY = (BuildConfig.APPLICATION_ID + ".settings").intern(); Loading Loading @@ -703,34 +699,23 @@ public class LauncherProvider extends ContentProvider { @Override public void onOpen(SQLiteDatabase db) { super.onOpen(db); SharedPreferences prefs = mContext .getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0); int oldVersion = prefs.getInt(PREF_KEY_DATA_VERISON, 0); if (oldVersion != DATA_VERSION) { // Only run the data upgrade path for an existing db. if (!Utilities.getPrefs(mContext).getBoolean(EMPTY_DATABASE_CREATED, false)) { try (SQLiteTransaction t = new SQLiteTransaction(db)) { onDataUpgrade(db, oldVersion); t.commit(); } catch (Exception e) { Log.d(TAG, "Error updating data version, ignoring", e); return; } } prefs.edit().putInt(PREF_KEY_DATA_VERISON, DATA_VERSION).apply(); File schemaFile = mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE); if (!schemaFile.exists()) { handleOneTimeDataUpgrade(db); } DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext, R.raw.downgrade_schema); } /** * Called when the data is updated as part of app update. It can be called multiple times * with old version, even though it had been run before. The changes made here must be * backwards compatible, else we risk breaking old devices during restore or binary * version downgrade. * One-time data updated before support of onDowngrade was added. This update is backwards * compatible and can safely be run multiple times. * Note: No new logic should be added here after release, as the new logic might not get * executed on an existing device. * TODO: Move this to db upgrade path, once the downgrade path is released. */ protected void onDataUpgrade(SQLiteDatabase db, int oldVersion) { switch (oldVersion) { case 0: case 1: { protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { // Remove "profile extra" UserManagerCompat um = UserManagerCompat.getInstance(mContext); for (UserHandle user : um.getUserProfiles()) { Loading @@ -740,12 +725,6 @@ public class LauncherProvider extends ContentProvider { db.execSQL(sql); } } case 2: case 3: // data updated return; } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Loading Loading @@ -850,16 +829,15 @@ public class LauncherProvider extends ContentProvider { @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion == 28 && newVersion == 27) { // TODO: remove this check. This is only applicable for internal development/testing // and for any released version of Launcher. return; } // This shouldn't happen -- throw our hands up in the air and start over. Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion + ". Wiping databse."); try { DbDowngradeHelper.parse(mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE)) .onDowngrade(db, oldVersion, newVersion); } catch (Exception e) { Log.d(TAG, "Unable to downgrade from: " + oldVersion + " to " + newVersion + ". Wiping databse.", e); createEmptyDB(db); } } /** * Clears all the data for a fresh start. Loading src/com/android/launcher3/model/DbDowngradeHelper.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.launcher3.model; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.util.Log; import android.util.SparseArray; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.util.IOUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; /** * Utility class to handle DB downgrade */ public class DbDowngradeHelper { private static final String TAG = "DbDowngradeHelper"; private static final String KEY_VERSION = "version"; private static final String KEY_DOWNGRADE_TO = "downgrade_to_"; private final SparseArray<String[]> mStatements = new SparseArray<>(); public final int version; private DbDowngradeHelper(int version) { this.version = version; } public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { ArrayList<String> allCommands = new ArrayList<>(); for (int i = oldVersion - 1; i >= newVersion; i--) { String[] commands = mStatements.get(i); if (commands == null) { throw new SQLiteException("Downgrade path not supported to version " + i); } Collections.addAll(allCommands, commands); } try (SQLiteTransaction t = new SQLiteTransaction(db)) { for (String sql : allCommands) { db.execSQL(sql); } t.commit(); } } public static DbDowngradeHelper parse(File file) throws JSONException, IOException { JSONObject obj = new JSONObject(new String(IOUtils.toByteArray(file))); DbDowngradeHelper helper = new DbDowngradeHelper(obj.getInt(KEY_VERSION)); for (int version = helper.version - 1; version > 0; version--) { if (obj.has(KEY_DOWNGRADE_TO + version)) { JSONArray statements = obj.getJSONArray(KEY_DOWNGRADE_TO + version); String[] parsed = new String[statements.length()]; for (int i = 0; i < parsed.length; i++) { parsed[i] = statements.getString(i); } helper.mStatements.put(version, parsed); } } return helper; } public static void updateSchemaFile(File schemaFile, int expectedVersion, Context context, int schemaResId) { try { if (DbDowngradeHelper.parse(schemaFile).version >= expectedVersion) { return; } } catch (Exception e) { // Schema error } // Write the updated schema try (FileOutputStream fos = new FileOutputStream(schemaFile); InputStream in = context.getResources().openRawResource(schemaResId)) { IOUtils.copy(in, fos); } catch (IOException e) { Log.e(TAG, "Error writing schema file", e); } } } src/com/android/launcher3/provider/LauncherDbUtils.java +2 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ public class LauncherDbUtils { if (screenIds.isEmpty()) { // No update needed t.commit(); return true; } if (screenIds.get(0) != 0) { Loading @@ -71,6 +72,7 @@ public class LauncherDbUtils { if (DatabaseUtils.queryNumEntries(db, Favorites.TABLE_NAME, "container = -100 and screen = 0 and cellY = 0") == 0) { // First row is empty, no need to migrate. t.commit(); return true; } Loading Loading
res/raw/downgrade_schema.json 0 → 100644 +20 −0 Original line number Diff line number Diff line { // Note: Comments are not supported in JSON schema, but android parser is lenient. // Maximum DB version supported by this schema "version" : 27, // Downgrade from 27 to 26. Empty array indicates, the DB is compatible "downgrade_to_26" : [], "downgrade_to_25" : [], "downgrade_to_24" : [], "downgrade_to_23" : [], "downgrade_to_22" : [ "ALTER TABLE favorites RENAME TO temp_favorites;", "CREATE TABLE favorites(_id INTEGER PRIMARY KEY, title TEXT, intent TEXT, container INTEGER, screen INTEGER, cellX INTEGER, cellY INTEGER, spanX INTEGER, spanY INTEGER, itemType INTEGER, appWidgetId INTEGER NOT NULL DEFAULT - 1, iconPackage TEXT, iconResource TEXT, icon BLOB, appWidgetProvider TEXT, modified INTEGER NOT NULL DEFAULT 0, restored INTEGER NOT NULL DEFAULT 0, profileId INTEGER DEFAULT 0, rank INTEGER NOT NULL DEFAULT 0);", "INSERT INTO favorites SELECT _id, title, intent, container, screen, cellX, cellY, spanX, spanY, itemType, appWidgetId, iconPackage, iconResource, icon, appWidgetProvider, modified, restored, profileId, rank FROM temp_favorites;", "DROP TABLE temp_favorites;" ] // Missing values indicate the DB is not compatible } No newline at end of file
res/xml/backupscheme.xml +1 −0 Original line number Diff line number Diff line Loading @@ -3,5 +3,6 @@ <include domain="database" path="launcher.db" /> <include domain="sharedpref" path="com.android.launcher3.prefs.xml" /> <include domain="file" path="downgrade_schema.json" /> </full-backup-content> No newline at end of file
src/com/android/launcher3/LauncherProvider.java +31 −53 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.graphics.IconShapeOverride; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.DbDowngradeHelper; import com.android.launcher3.provider.LauncherDbUtils; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.provider.RestoreDbTask; Loading @@ -64,6 +65,7 @@ import com.android.launcher3.util.NoLocaleSqliteContext; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Thunk; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.reflect.Method; Loading @@ -77,18 +79,12 @@ public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; private static final boolean LOGD = false; private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json"; /** * Represents the schema of the database. Changes in scheme need not be backwards compatible. */ private static final int SCHEMA_VERSION = 27; /** * Represents the actual data. It could include additional validations and normalizations added * overtime. These must be backwards compatible, else we risk breaking old devices during * restore or binary version downgrade. */ private static final int DATA_VERSION = 3; private static final String PREF_KEY_DATA_VERISON = "provider_data_version"; public static final int SCHEMA_VERSION = 27; public static final String AUTHORITY = (BuildConfig.APPLICATION_ID + ".settings").intern(); Loading Loading @@ -703,34 +699,23 @@ public class LauncherProvider extends ContentProvider { @Override public void onOpen(SQLiteDatabase db) { super.onOpen(db); SharedPreferences prefs = mContext .getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0); int oldVersion = prefs.getInt(PREF_KEY_DATA_VERISON, 0); if (oldVersion != DATA_VERSION) { // Only run the data upgrade path for an existing db. if (!Utilities.getPrefs(mContext).getBoolean(EMPTY_DATABASE_CREATED, false)) { try (SQLiteTransaction t = new SQLiteTransaction(db)) { onDataUpgrade(db, oldVersion); t.commit(); } catch (Exception e) { Log.d(TAG, "Error updating data version, ignoring", e); return; } } prefs.edit().putInt(PREF_KEY_DATA_VERISON, DATA_VERSION).apply(); File schemaFile = mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE); if (!schemaFile.exists()) { handleOneTimeDataUpgrade(db); } DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext, R.raw.downgrade_schema); } /** * Called when the data is updated as part of app update. It can be called multiple times * with old version, even though it had been run before. The changes made here must be * backwards compatible, else we risk breaking old devices during restore or binary * version downgrade. * One-time data updated before support of onDowngrade was added. This update is backwards * compatible and can safely be run multiple times. * Note: No new logic should be added here after release, as the new logic might not get * executed on an existing device. * TODO: Move this to db upgrade path, once the downgrade path is released. */ protected void onDataUpgrade(SQLiteDatabase db, int oldVersion) { switch (oldVersion) { case 0: case 1: { protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { // Remove "profile extra" UserManagerCompat um = UserManagerCompat.getInstance(mContext); for (UserHandle user : um.getUserProfiles()) { Loading @@ -740,12 +725,6 @@ public class LauncherProvider extends ContentProvider { db.execSQL(sql); } } case 2: case 3: // data updated return; } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Loading Loading @@ -850,16 +829,15 @@ public class LauncherProvider extends ContentProvider { @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion == 28 && newVersion == 27) { // TODO: remove this check. This is only applicable for internal development/testing // and for any released version of Launcher. return; } // This shouldn't happen -- throw our hands up in the air and start over. Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion + ". Wiping databse."); try { DbDowngradeHelper.parse(mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE)) .onDowngrade(db, oldVersion, newVersion); } catch (Exception e) { Log.d(TAG, "Unable to downgrade from: " + oldVersion + " to " + newVersion + ". Wiping databse.", e); createEmptyDB(db); } } /** * Clears all the data for a fresh start. Loading
src/com/android/launcher3/model/DbDowngradeHelper.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.launcher3.model; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.util.Log; import android.util.SparseArray; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.util.IOUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; /** * Utility class to handle DB downgrade */ public class DbDowngradeHelper { private static final String TAG = "DbDowngradeHelper"; private static final String KEY_VERSION = "version"; private static final String KEY_DOWNGRADE_TO = "downgrade_to_"; private final SparseArray<String[]> mStatements = new SparseArray<>(); public final int version; private DbDowngradeHelper(int version) { this.version = version; } public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { ArrayList<String> allCommands = new ArrayList<>(); for (int i = oldVersion - 1; i >= newVersion; i--) { String[] commands = mStatements.get(i); if (commands == null) { throw new SQLiteException("Downgrade path not supported to version " + i); } Collections.addAll(allCommands, commands); } try (SQLiteTransaction t = new SQLiteTransaction(db)) { for (String sql : allCommands) { db.execSQL(sql); } t.commit(); } } public static DbDowngradeHelper parse(File file) throws JSONException, IOException { JSONObject obj = new JSONObject(new String(IOUtils.toByteArray(file))); DbDowngradeHelper helper = new DbDowngradeHelper(obj.getInt(KEY_VERSION)); for (int version = helper.version - 1; version > 0; version--) { if (obj.has(KEY_DOWNGRADE_TO + version)) { JSONArray statements = obj.getJSONArray(KEY_DOWNGRADE_TO + version); String[] parsed = new String[statements.length()]; for (int i = 0; i < parsed.length; i++) { parsed[i] = statements.getString(i); } helper.mStatements.put(version, parsed); } } return helper; } public static void updateSchemaFile(File schemaFile, int expectedVersion, Context context, int schemaResId) { try { if (DbDowngradeHelper.parse(schemaFile).version >= expectedVersion) { return; } } catch (Exception e) { // Schema error } // Write the updated schema try (FileOutputStream fos = new FileOutputStream(schemaFile); InputStream in = context.getResources().openRawResource(schemaResId)) { IOUtils.copy(in, fos); } catch (IOException e) { Log.e(TAG, "Error writing schema file", e); } } }
src/com/android/launcher3/provider/LauncherDbUtils.java +2 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ public class LauncherDbUtils { if (screenIds.isEmpty()) { // No update needed t.commit(); return true; } if (screenIds.get(0) != 0) { Loading @@ -71,6 +72,7 @@ public class LauncherDbUtils { if (DatabaseUtils.queryNumEntries(db, Favorites.TABLE_NAME, "container = -100 and screen = 0 and cellY = 0") == 0) { // First row is empty, no need to migrate. t.commit(); return true; } Loading