1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.model;
17 
18 import android.content.Context;
19 import android.database.sqlite.SQLiteDatabase;
20 import android.database.sqlite.SQLiteException;
21 import android.util.Log;
22 import android.util.SparseArray;
23 
24 import com.android.launcher3.R;
25 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
26 import com.android.launcher3.util.IOUtils;
27 
28 import org.json.JSONArray;
29 import org.json.JSONException;
30 import org.json.JSONObject;
31 
32 import java.io.File;
33 import java.io.FileOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 
39 /**
40  * Utility class to handle DB downgrade
41  */
42 public class DbDowngradeHelper {
43 
44     private static final String TAG = "DbDowngradeHelper";
45 
46     private static final String KEY_VERSION = "version";
47     private static final String KEY_DOWNGRADE_TO = "downgrade_to_";
48 
49     private final SparseArray<String[]> mStatements = new SparseArray<>();
50     public final int version;
51 
DbDowngradeHelper(int version)52     private DbDowngradeHelper(int version) {
53         this.version = version;
54     }
55 
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)56     public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
57         ArrayList<String> allCommands = new ArrayList<>();
58 
59         for (int i = oldVersion - 1; i >= newVersion; i--) {
60             String[] commands = mStatements.get(i);
61             if (commands == null) {
62                 throw new SQLiteException("Downgrade path not supported to version " + i);
63             }
64             Collections.addAll(allCommands, commands);
65         }
66 
67         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
68             for (String sql : allCommands) {
69                 db.execSQL(sql);
70             }
71             t.commit();
72         }
73     }
74 
75     /**
76      * Creates a helper from the provided file
77      */
parse(File file)78     public static DbDowngradeHelper parse(File file) throws JSONException, IOException {
79         return parse(IOUtils.toByteArray(file));
80     }
81 
82     /**
83      * Creates a helper from the provided bytes
84      */
parse(byte[] fileData)85     public static DbDowngradeHelper parse(byte[] fileData) throws JSONException {
86         JSONObject obj = new JSONObject(new String(fileData));
87         DbDowngradeHelper helper = new DbDowngradeHelper(obj.getInt(KEY_VERSION));
88         for (int version = helper.version - 1; version > 0; version--) {
89             if (obj.has(KEY_DOWNGRADE_TO + version)) {
90                 JSONArray statements = obj.getJSONArray(KEY_DOWNGRADE_TO + version);
91                 String[] parsed = new String[statements.length()];
92                 for (int i = 0; i < parsed.length; i++) {
93                     parsed[i] = statements.getString(i);
94                 }
95                 helper.mStatements.put(version, parsed);
96             }
97         }
98         return helper;
99     }
100 
updateSchemaFile(File schemaFile, int expectedVersion, Context context)101     public static void updateSchemaFile(File schemaFile, int expectedVersion, Context context) {
102         try {
103             if (DbDowngradeHelper.parse(schemaFile).version >= expectedVersion) {
104                 return;
105             }
106         } catch (Exception e) {
107             // Schema error
108         }
109 
110         // Write the updated schema
111         try (FileOutputStream fos = new FileOutputStream(schemaFile);
112             InputStream in = context.getResources().openRawResource(R.raw.downgrade_schema)) {
113             IOUtils.copy(in, fos);
114         } catch (IOException e) {
115             Log.e(TAG, "Error writing schema file", e);
116         }
117     }
118 }
119