1 /* 2 * Copyright (C) 2018 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 static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME; 19 import static com.android.launcher3.provider.LauncherDbUtils.dropTable; 20 import static com.android.launcher3.provider.LauncherDbUtils.tableExists; 21 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.database.Cursor; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.os.Process; 27 import android.util.Log; 28 29 import androidx.annotation.IntDef; 30 31 import com.android.launcher3.LauncherSettings.Favorites; 32 import com.android.launcher3.pm.UserCache; 33 34 /** 35 * Helper class to backup and restore Favorites table into a separate table 36 * within the same data base. 37 */ 38 public class GridBackupTable { 39 private static final String TAG = "GridBackupTable"; 40 41 private static final int ID_PROPERTY = -1; 42 43 private static final String KEY_HOTSEAT_SIZE = Favorites.SCREEN; 44 private static final String KEY_GRID_X_SIZE = Favorites.SPANX; 45 private static final String KEY_GRID_Y_SIZE = Favorites.SPANY; 46 private static final String KEY_DB_VERSION = Favorites.RANK; 47 48 public static final int OPTION_REQUIRES_SANITIZATION = 1; 49 50 /** STATE_NOT_FOUND indicates backup doesn't exist in the db. */ 51 private static final int STATE_NOT_FOUND = 0; 52 /** 53 * STATE_RAW indicates the backup has not yet been sanitized. This implies it might still 54 * posses app info that doesn't exist in the workspace and needed to be sanitized before 55 * put into use. 56 */ 57 private static final int STATE_RAW = 1; 58 /** STATE_SANITIZED indicates the backup has already been sanitized, thus can be used as-is. */ 59 private static final int STATE_SANITIZED = 2; 60 61 private final Context mContext; 62 private final SQLiteDatabase mDb; 63 64 private final int mOldHotseatSize; 65 private final int mOldGridX; 66 private final int mOldGridY; 67 68 private int mRestoredHotseatSize; 69 private int mRestoredGridX; 70 private int mRestoredGridY; 71 72 @IntDef({STATE_NOT_FOUND, STATE_RAW, STATE_SANITIZED}) 73 private @interface BackupState { } 74 GridBackupTable(Context context, SQLiteDatabase db, int hotseatSize, int gridX, int gridY)75 public GridBackupTable(Context context, SQLiteDatabase db, int hotseatSize, int gridX, 76 int gridY) { 77 mContext = context; 78 mDb = db; 79 80 mOldHotseatSize = hotseatSize; 81 mOldGridX = gridX; 82 mOldGridY = gridY; 83 } 84 85 /** 86 * Creates a new table and populates with copy of Favorites.TABLE_NAME 87 */ createCustomBackupTable(String tableName)88 public void createCustomBackupTable(String tableName) { 89 long profileId = UserCache.INSTANCE.get(mContext).getSerialNumberForUser( 90 Process.myUserHandle()); 91 copyTable(mDb, Favorites.TABLE_NAME, tableName, profileId); 92 encodeDBProperties(0); 93 } 94 95 /** 96 * 97 * Restores the contents of a custom table to Favorites.TABLE_NAME 98 */ 99 restoreFromCustomBackupTable(String tableName, boolean dropAfterUse)100 public void restoreFromCustomBackupTable(String tableName, boolean dropAfterUse) { 101 if (!tableExists(mDb, tableName)) { 102 return; 103 } 104 long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser( 105 Process.myUserHandle()); 106 copyTable(mDb, tableName, Favorites.TABLE_NAME, userSerial); 107 if (dropAfterUse) { 108 dropTable(mDb, tableName); 109 } 110 } 111 /** 112 * Copy valid grid entries from one table to another. 113 */ copyTable(SQLiteDatabase db, String from, String to, long userSerial)114 private static void copyTable(SQLiteDatabase db, String from, String to, long userSerial) { 115 dropTable(db, to); 116 Favorites.addTableToDb(db, userSerial, false, to); 117 db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from + " where _id > " + ID_PROPERTY); 118 } 119 encodeDBProperties(int options)120 private void encodeDBProperties(int options) { 121 ContentValues values = new ContentValues(); 122 values.put(Favorites._ID, ID_PROPERTY); 123 values.put(KEY_DB_VERSION, mDb.getVersion()); 124 values.put(KEY_GRID_X_SIZE, mOldGridX); 125 values.put(KEY_GRID_Y_SIZE, mOldGridY); 126 values.put(KEY_HOTSEAT_SIZE, mOldHotseatSize); 127 values.put(Favorites.OPTIONS, options); 128 mDb.insert(BACKUP_TABLE_NAME, null, values); 129 } 130 131 /** 132 * Load DB properties from grid backup table. 133 */ loadDBProperties()134 public @BackupState int loadDBProperties() { 135 try (Cursor c = mDb.query(BACKUP_TABLE_NAME, new String[] { 136 KEY_DB_VERSION, // 0 137 KEY_GRID_X_SIZE, // 1 138 KEY_GRID_Y_SIZE, // 2 139 KEY_HOTSEAT_SIZE, // 3 140 Favorites.OPTIONS}, // 4 141 "_id=" + ID_PROPERTY, null, null, null, null)) { 142 if (!c.moveToNext()) { 143 Log.e(TAG, "Meta data not found in backup table"); 144 return STATE_NOT_FOUND; 145 } 146 if (!validateDBVersion(mDb.getVersion(), c.getInt(0))) { 147 return STATE_NOT_FOUND; 148 } 149 150 mRestoredGridX = c.getInt(1); 151 mRestoredGridY = c.getInt(2); 152 mRestoredHotseatSize = c.getInt(3); 153 boolean isSanitized = (c.getInt(4) & OPTION_REQUIRES_SANITIZATION) == 0; 154 return isSanitized ? STATE_SANITIZED : STATE_RAW; 155 } 156 } 157 158 /** 159 * Restore workspace from raw backup if available. 160 */ restoreFromRawBackupIfAvailable(long oldProfileId)161 public boolean restoreFromRawBackupIfAvailable(long oldProfileId) { 162 if (!tableExists(mDb, Favorites.BACKUP_TABLE_NAME) 163 || loadDBProperties() != STATE_RAW 164 || mOldHotseatSize != mRestoredHotseatSize 165 || mOldGridX != mRestoredGridX 166 || mOldGridY != mRestoredGridY) { 167 // skip restore if dimensions in backup table differs from current setup. 168 return false; 169 } 170 copyTable(mDb, Favorites.BACKUP_TABLE_NAME, Favorites.TABLE_NAME, oldProfileId); 171 Log.d(TAG, "Backup restored"); 172 return true; 173 } 174 175 /** 176 * Performs a backup on the workspace layout. 177 */ doBackup(long profileId, int options)178 public void doBackup(long profileId, int options) { 179 copyTable(mDb, Favorites.TABLE_NAME, Favorites.BACKUP_TABLE_NAME, profileId); 180 encodeDBProperties(options); 181 } 182 validateDBVersion(int expected, int actual)183 private static boolean validateDBVersion(int expected, int actual) { 184 if (expected != actual) { 185 Log.e(TAG, String.format("Launcher.db version mismatch, expecting %d but %d was found", 186 expected, actual)); 187 return false; 188 } 189 return true; 190 } 191 } 192