mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 15:26:58 +00:00
Updating backup restore logic
> Adding DeviceProfile information in the backup > Removing SharedPreference backup > Adding helper methods to abort backup in the middle > Comparing keys against the backup journal during restore to avoid restoring corrupt/lost entries > Old backups are still compatible, but lost keys verification will be ignored in that case. Bug: 17937935 Bug: 17951775 Bug: 17260941 Change-Id: Iad48646cfdd69abaff5c163b2055f3b8a9b39b19
This commit is contained in:
@@ -37,12 +37,36 @@ message CheckedMessage {
|
||||
required int64 checksum = 2;
|
||||
}
|
||||
|
||||
message DeviceProfieData {
|
||||
required float desktop_rows = 1;
|
||||
required float desktop_cols = 2;
|
||||
required float hotseat_count = 3;
|
||||
required int32 allapps_rank = 4;
|
||||
}
|
||||
|
||||
message Journal {
|
||||
required int32 app_version = 1;
|
||||
|
||||
// Time when the backup was created
|
||||
required int64 t = 2;
|
||||
|
||||
// Total bytes written during the last backup
|
||||
// OBSOLETE: A state may contain entries which are already present in the backup
|
||||
// and were not written in the last backup
|
||||
optional int64 bytes = 3;
|
||||
|
||||
// Total entries written during the last backup
|
||||
// OBSOLETE: A state may contain entries which are already present in the backup
|
||||
// and were not written in the last backup
|
||||
optional int32 rows = 4;
|
||||
|
||||
// Valid keys for this state
|
||||
repeated Key key = 5;
|
||||
|
||||
// Backup format version.
|
||||
optional int32 backup_version = 6 [default = 1];
|
||||
|
||||
optional DeviceProfieData profile = 7;
|
||||
}
|
||||
|
||||
message Favorite {
|
||||
|
||||
@@ -53,7 +53,6 @@ import android.database.ContentObserver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -71,7 +70,6 @@ import android.text.Selection;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.TextKeyListener;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
@@ -418,22 +416,9 @@ public class Launcher extends Activity
|
||||
LauncherAppState.setApplicationContext(getApplicationContext());
|
||||
LauncherAppState app = LauncherAppState.getInstance();
|
||||
LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
|
||||
// Determine the dynamic grid properties
|
||||
Point smallestSize = new Point();
|
||||
Point largestSize = new Point();
|
||||
Point realSize = new Point();
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
display.getCurrentSizeRange(smallestSize, largestSize);
|
||||
display.getRealSize(realSize);
|
||||
DisplayMetrics dm = new DisplayMetrics();
|
||||
display.getMetrics(dm);
|
||||
|
||||
// Lazy-initialize the dynamic grid
|
||||
DeviceProfile grid = app.initDynamicGrid(this,
|
||||
Math.min(smallestSize.x, smallestSize.y),
|
||||
Math.min(largestSize.x, largestSize.y),
|
||||
realSize.x, realSize.y,
|
||||
dm.widthPixels, dm.heightPixels);
|
||||
DeviceProfile grid = app.initDynamicGrid(this);
|
||||
|
||||
// the LauncherApplication should call this, but in case of Instrumentation it might not be present yet
|
||||
mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
|
||||
|
||||
@@ -25,8 +25,12 @@ import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.database.ContentObserver;
|
||||
import android.graphics.Point;
|
||||
import android.os.Handler;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.launcher3.compat.LauncherAppsCompat;
|
||||
import com.android.launcher3.compat.PackageInstallerCompat;
|
||||
@@ -190,21 +194,35 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
|
||||
return LauncherFiles.SHARED_PREFERENCES_KEY;
|
||||
}
|
||||
|
||||
DeviceProfile initDynamicGrid(Context context, int minWidth, int minHeight,
|
||||
int width, int height,
|
||||
int availableWidth, int availableHeight) {
|
||||
DeviceProfile initDynamicGrid(Context context) {
|
||||
// Determine the dynamic grid properties
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = wm.getDefaultDisplay();
|
||||
|
||||
Point realSize = new Point();
|
||||
display.getRealSize(realSize);
|
||||
DisplayMetrics dm = new DisplayMetrics();
|
||||
display.getMetrics(dm);
|
||||
|
||||
if (mDynamicGrid == null) {
|
||||
Point smallestSize = new Point();
|
||||
Point largestSize = new Point();
|
||||
display.getCurrentSizeRange(smallestSize, largestSize);
|
||||
|
||||
mDynamicGrid = new DynamicGrid(context,
|
||||
context.getResources(),
|
||||
minWidth, minHeight, width, height,
|
||||
availableWidth, availableHeight);
|
||||
Math.min(smallestSize.x, smallestSize.y),
|
||||
Math.min(largestSize.x, largestSize.y),
|
||||
realSize.x, realSize.y,
|
||||
dm.widthPixels, dm.heightPixels);
|
||||
mDynamicGrid.getDeviceProfile().addCallback(this);
|
||||
}
|
||||
|
||||
// Update the icon size
|
||||
DeviceProfile grid = mDynamicGrid.getDeviceProfile();
|
||||
grid.updateFromConfiguration(context, context.getResources(), width, height,
|
||||
availableWidth, availableHeight);
|
||||
grid.updateFromConfiguration(context, context.getResources(),
|
||||
realSize.x, realSize.y,
|
||||
dm.widthPixels, dm.heightPixels);
|
||||
return grid;
|
||||
}
|
||||
public DynamicGrid getDynamicGrid() {
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.app.backup.BackupManager;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -30,13 +29,14 @@ import java.io.IOException;
|
||||
public class LauncherBackupAgentHelper extends BackupAgentHelper {
|
||||
|
||||
private static final String TAG = "LauncherBackupAgentHelper";
|
||||
|
||||
private static final String LAUNCHER_DATA_PREFIX = "L";
|
||||
|
||||
static final boolean VERBOSE = true;
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
private static BackupManager sBackupManager;
|
||||
|
||||
protected static final String SETTING_RESTORE_ENABLED = "launcher_restore_enabled";
|
||||
|
||||
/**
|
||||
* Notify the backup manager that out database is dirty.
|
||||
*
|
||||
@@ -51,28 +51,13 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper {
|
||||
sBackupManager.dataChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
// There is only one process accessing this preference file, but the restore
|
||||
// modifies the file outside the normal codepaths, so it looks like another
|
||||
// process. This forces a reload of the file, in case this process persists.
|
||||
String spKey = LauncherAppState.getSharedPreferencesKey();
|
||||
getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
|
||||
super.onDestroy();
|
||||
}
|
||||
private LauncherBackupHelper mHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
boolean restoreEnabled = 0 != Settings.Secure.getInt(
|
||||
getContentResolver(), SETTING_RESTORE_ENABLED, 1);
|
||||
if (VERBOSE) Log.v(TAG, "restore is " + (restoreEnabled ? "enabled" : "disabled"));
|
||||
|
||||
addHelper(LauncherBackupHelper.LAUNCHER_PREFS_PREFIX,
|
||||
new LauncherPreferencesBackupHelper(this,
|
||||
LauncherAppState.getSharedPreferencesKey(),
|
||||
restoreEnabled));
|
||||
addHelper(LauncherBackupHelper.LAUNCHER_PREFIX,
|
||||
new LauncherBackupHelper(this, restoreEnabled));
|
||||
super.onCreate();
|
||||
mHelper = new LauncherBackupHelper(this);
|
||||
addHelper(LAUNCHER_DATA_PREFIX, mHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,7 +77,10 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper {
|
||||
boolean hasData = c.moveToNext();
|
||||
c.close();
|
||||
|
||||
if (!hasData) {
|
||||
if (hasData && mHelper.restoreSuccessful) {
|
||||
LauncherAppState.getLauncherProvider().clearFlagEmptyDbCreated();
|
||||
LauncherClings.synchonouslyMarkFirstRunClingDismissed(this);
|
||||
} else {
|
||||
if (VERBOSE) Log.v(TAG, "Nothing was restored, clearing DB");
|
||||
LauncherAppState.getLauncherProvider().createEmptyDB();
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.LauncherSettings.WorkspaceScreens;
|
||||
import com.android.launcher3.backup.BackupProtos;
|
||||
import com.android.launcher3.backup.BackupProtos.CheckedMessage;
|
||||
import com.android.launcher3.backup.BackupProtos.DeviceProfieData;
|
||||
import com.android.launcher3.backup.BackupProtos.Favorite;
|
||||
import com.android.launcher3.backup.BackupProtos.Journal;
|
||||
import com.android.launcher3.backup.BackupProtos.Key;
|
||||
@@ -66,24 +67,24 @@ import java.util.zip.CRC32;
|
||||
* Persist the launcher home state across calamities.
|
||||
*/
|
||||
public class LauncherBackupHelper implements BackupHelper {
|
||||
|
||||
private static final String TAG = "LauncherBackupHelper";
|
||||
private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
|
||||
private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
|
||||
|
||||
private static final int BACKUP_VERSION = 2;
|
||||
private static final int MAX_JOURNAL_SIZE = 1000000;
|
||||
|
||||
// Journal key is such that it is always smaller than any dynamically generated
|
||||
// key (any Base64 encoded string).
|
||||
private static final String JOURNAL_KEY = "#";
|
||||
|
||||
/** icons are large, dribble them out */
|
||||
private static final int MAX_ICONS_PER_PASS = 10;
|
||||
|
||||
/** widgets contain previews, which are very large, dribble them out */
|
||||
private static final int MAX_WIDGETS_PER_PASS = 5;
|
||||
|
||||
public static final int IMAGE_COMPRESSION_QUALITY = 75;
|
||||
|
||||
public static final String LAUNCHER_PREFIX = "L";
|
||||
|
||||
public static final String LAUNCHER_PREFS_PREFIX = "LP";
|
||||
private static final int IMAGE_COMPRESSION_QUALITY = 75;
|
||||
|
||||
private static final Bitmap.CompressFormat IMAGE_FORMAT =
|
||||
android.graphics.Bitmap.CompressFormat.PNG;
|
||||
@@ -145,10 +146,13 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
private byte[] mBuffer = new byte[512];
|
||||
private long mLastBackupRestoreTime;
|
||||
|
||||
public LauncherBackupHelper(Context context, boolean restoreEnabled) {
|
||||
boolean restoreSuccessful;
|
||||
|
||||
public LauncherBackupHelper(Context context) {
|
||||
mContext = context;
|
||||
mExistingKeys = new HashSet<String>();
|
||||
mKeys = new ArrayList<Key>();
|
||||
restoreSuccessful = true;
|
||||
}
|
||||
|
||||
private void dataChanged() {
|
||||
@@ -178,7 +182,6 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
* @param oldState notes from the last backup
|
||||
* @param data incremental key/value pairs to persist off-device
|
||||
* @param newState notes for the next backup
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
|
||||
@@ -220,6 +223,12 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
|
||||
mExistingKeys.clear();
|
||||
mLastBackupRestoreTime = newBackupTime;
|
||||
|
||||
// We store the journal at two places.
|
||||
// 1) Storing it in newState allows us to do partial backups by comparing old state
|
||||
// 2) Storing it in backup data allows us to validate keys during restore
|
||||
Journal state = getCurrentStateJournal();
|
||||
writeRowToBackup(JOURNAL_KEY, state, data);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "launcher backup has failed", e);
|
||||
}
|
||||
@@ -227,15 +236,27 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
writeNewStateDescription(newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the backup corresponding to oldstate can be successfully applied
|
||||
* to this device.
|
||||
*/
|
||||
private boolean isBackupCompatible(Journal oldState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore launcher configuration from the restored data stream.
|
||||
*
|
||||
* <P>Keys may arrive in any order.
|
||||
* It assumes that the keys will arrive in lexical order. So if the journal was present in the
|
||||
* backup, it should arrive first.
|
||||
*
|
||||
* @param data the key/value pair from the server
|
||||
*/
|
||||
@Override
|
||||
public void restoreEntity(BackupDataInputStream data) {
|
||||
if (!restoreSuccessful) {
|
||||
return;
|
||||
}
|
||||
|
||||
int dataSize = data.size();
|
||||
if (mBuffer.length < dataSize) {
|
||||
mBuffer = new byte[dataSize];
|
||||
@@ -244,6 +265,27 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
int bytesRead = data.read(mBuffer, 0, dataSize);
|
||||
if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
|
||||
String backupKey = data.getKey();
|
||||
|
||||
if (JOURNAL_KEY.equals(backupKey)) {
|
||||
if (VERBOSE) Log.v(TAG, "Journal entry restored");
|
||||
if (!mKeys.isEmpty()) {
|
||||
// We received the journal key after a restore key.
|
||||
Log.wtf(TAG, keyToBackupKey(mKeys.get(0)) + " received after " + JOURNAL_KEY);
|
||||
restoreSuccessful = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Journal journal = new Journal();
|
||||
MessageNano.mergeFrom(journal, readCheckedBytes(mBuffer, dataSize));
|
||||
applyJournal(journal);
|
||||
restoreSuccessful = isBackupCompatible(journal);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mExistingKeys.isEmpty() && !mExistingKeys.contains(backupKey)) {
|
||||
if (DEBUG) Log.e(TAG, "Ignoring key not present in the backup state " + backupKey);
|
||||
return;
|
||||
}
|
||||
Key key = backupKeyToKey(backupKey);
|
||||
mKeys.add(key);
|
||||
switch (key.type) {
|
||||
@@ -288,6 +330,8 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
journal.t = mLastBackupRestoreTime;
|
||||
journal.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
|
||||
journal.appVersion = getAppVersion();
|
||||
journal.backupVersion = BACKUP_VERSION;
|
||||
journal.profile = getDeviceProfieData();
|
||||
return journal;
|
||||
}
|
||||
|
||||
@@ -300,6 +344,29 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current device profile information.
|
||||
*/
|
||||
private DeviceProfieData getDeviceProfieData() {
|
||||
LauncherAppState.setApplicationContext(mContext.getApplicationContext());
|
||||
LauncherAppState app = LauncherAppState.getInstance();
|
||||
|
||||
DeviceProfile profile;
|
||||
if (app.getDynamicGrid() == null) {
|
||||
// Initialize the grid
|
||||
profile = app.initDynamicGrid(mContext);
|
||||
} else {
|
||||
profile = app.getDynamicGrid().getDeviceProfile();
|
||||
}
|
||||
|
||||
DeviceProfieData data = new DeviceProfieData();
|
||||
data.desktopRows = profile.numRows;
|
||||
data.desktopCols = profile.numColumns;
|
||||
data.hotseatCount = profile.numHotseatIcons;
|
||||
data.allappsRank = profile.hotseatAllAppsRank;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all modified favorites to the data stream.
|
||||
*
|
||||
@@ -902,7 +969,6 @@ public class LauncherBackupHelper implements BackupHelper {
|
||||
return journal;
|
||||
}
|
||||
|
||||
|
||||
private void writeRowToBackup(Key key, MessageNano proto, BackupDataOutput data)
|
||||
throws IOException {
|
||||
writeRowToBackup(keyToBackupKey(key), proto, data);
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import android.app.backup.BackupDataInputStream;
|
||||
import android.app.backup.SharedPreferencesBackupHelper;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
public class LauncherPreferencesBackupHelper extends SharedPreferencesBackupHelper {
|
||||
|
||||
private static final String TAG = "LauncherPreferencesBackupHelper";
|
||||
private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
|
||||
|
||||
private final boolean mRestoreEnabled;
|
||||
|
||||
public LauncherPreferencesBackupHelper(Context context, String sharedPreferencesKey,
|
||||
boolean restoreEnabled) {
|
||||
super(context, sharedPreferencesKey);
|
||||
mRestoreEnabled = restoreEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreEntity(BackupDataInputStream data) {
|
||||
if (mRestoreEnabled) {
|
||||
if (VERBOSE) Log.v(TAG, "restoring preferences");
|
||||
super.restoreEntity(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,6 +293,14 @@ public class LauncherProvider extends ContentProvider {
|
||||
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
|
||||
}
|
||||
|
||||
public void clearFlagEmptyDbCreated() {
|
||||
String spKey = LauncherAppState.getSharedPreferencesKey();
|
||||
getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.remove(EMPTY_DATABASE_CREATED)
|
||||
.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the default workspace based on the following priority scheme:
|
||||
* 1) From a package provided by play store
|
||||
@@ -334,7 +342,7 @@ public class LauncherProvider extends ContentProvider {
|
||||
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
|
||||
getDefaultLayoutParser());
|
||||
}
|
||||
sp.edit().remove(EMPTY_DATABASE_CREATED).commit();
|
||||
clearFlagEmptyDbCreated();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user