105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal/*
205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * Copyright (C) 2017 The Android Open Source Project
305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal *
405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * Licensed under the Apache License, Version 2.0 (the "License");
505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * you may not use this file except in compliance with the License.
605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * You may obtain a copy of the License at
705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal *
805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal *      http://www.apache.org/licenses/LICENSE-2.0
905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal *
1005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * Unless required by applicable law or agreed to in writing, software
1105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * distributed under the License is distributed on an "AS IS" BASIS,
1205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * See the License for the specific language governing permissions and
1405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * limitations under the License.
1505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal */
1605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalpackage com.android.launcher3.model;
1705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
1805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport android.content.Context;
1905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport android.database.sqlite.SQLiteDatabase;
2005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport android.database.sqlite.SQLiteException;
2105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport android.util.Log;
2205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport android.util.SparseArray;
2305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
2405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
2505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport com.android.launcher3.util.IOUtils;
2605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
2705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport org.json.JSONArray;
2805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport org.json.JSONException;
2905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport org.json.JSONObject;
3005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
3105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport java.io.File;
3205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport java.io.FileOutputStream;
3305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport java.io.IOException;
3405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport java.io.InputStream;
3505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport java.util.ArrayList;
3605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalimport java.util.Collections;
3705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
3805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal/**
3905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal * Utility class to handle DB downgrade
4005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal */
4105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyalpublic class DbDowngradeHelper {
4205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
4305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    private static final String TAG = "DbDowngradeHelper";
4405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
4505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    private static final String KEY_VERSION = "version";
4605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    private static final String KEY_DOWNGRADE_TO = "downgrade_to_";
4705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
4805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    private final SparseArray<String[]> mStatements = new SparseArray<>();
4905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    public final int version;
5005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
5105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    private DbDowngradeHelper(int version) {
5205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        this.version = version;
5305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    }
5405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
5505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
5605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        ArrayList<String> allCommands = new ArrayList<>();
5705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
5805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        for (int i = oldVersion - 1; i >= newVersion; i--) {
5905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            String[] commands = mStatements.get(i);
6005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            if (commands == null) {
6105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal                throw new SQLiteException("Downgrade path not supported to version " + i);
6205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            }
6305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            Collections.addAll(allCommands, commands);
6405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        }
6505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
6605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        try (SQLiteTransaction t = new SQLiteTransaction(db)) {
6705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            for (String sql : allCommands) {
6805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal                db.execSQL(sql);
6905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            }
7005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            t.commit();
7105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        }
7205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    }
7305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
7405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    public static DbDowngradeHelper parse(File file) throws JSONException, IOException {
7505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        JSONObject obj = new JSONObject(new String(IOUtils.toByteArray(file)));
7605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        DbDowngradeHelper helper = new DbDowngradeHelper(obj.getInt(KEY_VERSION));
7705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        for (int version = helper.version - 1; version > 0; version--) {
7805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            if (obj.has(KEY_DOWNGRADE_TO + version)) {
7905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal                JSONArray statements = obj.getJSONArray(KEY_DOWNGRADE_TO + version);
8005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal                String[] parsed = new String[statements.length()];
8105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal                for (int i = 0; i < parsed.length; i++) {
8205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal                    parsed[i] = statements.getString(i);
8305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal                }
8405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal                helper.mStatements.put(version, parsed);
8505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            }
8605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        }
8705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        return helper;
8805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    }
8905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
9005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    public static void updateSchemaFile(File schemaFile, int expectedVersion,
9105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            Context context, int schemaResId) {
9205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        try {
9305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            if (DbDowngradeHelper.parse(schemaFile).version >= expectedVersion) {
9405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal                return;
9505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            }
9605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        } catch (Exception e) {
9705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            // Schema error
9805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        }
9905f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal
10005f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        // Write the updated schema
10105f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        try (FileOutputStream fos = new FileOutputStream(schemaFile);
10205f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            InputStream in = context.getResources().openRawResource(schemaResId)) {
10305f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            IOUtils.copy(in, fos);
10405f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        } catch (IOException e) {
10505f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal            Log.e(TAG, "Error writing schema file", e);
10605f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal        }
10705f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal    }
10805f30889d6ba0f803c720813067b28c9c4cf2bfdSunny Goyal}
109