blob: 39603ad1bfe746a3b80bcb394fad722640c8ae1e [file] [log] [blame]
Chris Wren1ada10d2013-09-13 18:01:38 -04001/*
2 * Copyright (C) 2013 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 */
Chris Wren1ada10d2013-09-13 18:01:38 -040016package com.android.launcher3;
17
Chris Wren92aa4232013-10-04 11:29:36 -040018import android.app.backup.BackupDataInputStream;
Chris Wren1ada10d2013-09-13 18:01:38 -040019import android.app.backup.BackupDataOutput;
Chris Wren4d89e2a2013-10-09 17:03:50 -040020import android.app.backup.BackupHelper;
Chris Wren1ada10d2013-09-13 18:01:38 -040021import android.app.backup.BackupManager;
Chris Wren22e130d2013-09-23 18:25:57 -040022import android.content.ComponentName;
Chris Wren1ada10d2013-09-13 18:01:38 -040023import android.content.ContentResolver;
Chris Wren5dee7af2013-12-20 17:22:11 -050024import android.content.ContentValues;
Chris Wren1ada10d2013-09-13 18:01:38 -040025import android.content.Context;
Chris Wren22e130d2013-09-23 18:25:57 -040026import android.content.Intent;
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080027import android.content.pm.ActivityInfo;
28import android.content.pm.PackageManager;
Sunny Goyalef728d42014-10-22 11:28:28 -070029import android.content.pm.PackageManager.NameNotFoundException;
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080030import android.content.pm.ResolveInfo;
31import android.content.res.XmlResourceParser;
Chris Wren1ada10d2013-09-13 18:01:38 -040032import android.database.Cursor;
Chris Wren22e130d2013-09-23 18:25:57 -040033import android.graphics.Bitmap;
34import android.graphics.BitmapFactory;
Chris Wrenfd13c712013-09-27 15:45:19 -040035import android.graphics.drawable.Drawable;
Chris Wren1ada10d2013-09-13 18:01:38 -040036import android.os.ParcelFileDescriptor;
Chris Wren1ada10d2013-09-13 18:01:38 -040037import android.text.TextUtils;
38import android.util.Base64;
39import android.util.Log;
40
Sunny Goyalef728d42014-10-22 11:28:28 -070041import com.android.launcher3.LauncherSettings.Favorites;
42import com.android.launcher3.LauncherSettings.WorkspaceScreens;
43import com.android.launcher3.backup.BackupProtos;
44import com.android.launcher3.backup.BackupProtos.CheckedMessage;
Sunny Goyal33d44382014-10-16 09:24:19 -070045import com.android.launcher3.backup.BackupProtos.DeviceProfieData;
Sunny Goyalef728d42014-10-22 11:28:28 -070046import com.android.launcher3.backup.BackupProtos.Favorite;
47import com.android.launcher3.backup.BackupProtos.Journal;
48import com.android.launcher3.backup.BackupProtos.Key;
49import com.android.launcher3.backup.BackupProtos.Resource;
50import com.android.launcher3.backup.BackupProtos.Screen;
51import com.android.launcher3.backup.BackupProtos.Widget;
52import com.android.launcher3.compat.UserHandleCompat;
53import com.android.launcher3.compat.UserManagerCompat;
Sunny Goyal316490e2015-06-02 09:38:28 -070054import com.android.launcher3.util.Thunk;
Sunny Goyalef728d42014-10-22 11:28:28 -070055import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
56import com.google.protobuf.nano.MessageNano;
57
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080058import org.xmlpull.v1.XmlPullParser;
59import org.xmlpull.v1.XmlPullParserException;
60
Chris Wren1ada10d2013-09-13 18:01:38 -040061import java.io.FileInputStream;
62import java.io.FileOutputStream;
63import java.io.IOException;
Chris Wren22e130d2013-09-23 18:25:57 -040064import java.net.URISyntaxException;
Chris Wren1ada10d2013-09-13 18:01:38 -040065import java.util.ArrayList;
Sunny Goyalc0ee6752015-01-16 14:10:32 -080066import java.util.Arrays;
Chris Wren1ada10d2013-09-13 18:01:38 -040067import java.util.HashSet;
Chris Wren1ada10d2013-09-13 18:01:38 -040068import java.util.zip.CRC32;
69
70/**
71 * Persist the launcher home state across calamities.
72 */
Chris Wren92aa4232013-10-04 11:29:36 -040073public class LauncherBackupHelper implements BackupHelper {
Chris Wren92aa4232013-10-04 11:29:36 -040074 private static final String TAG = "LauncherBackupHelper";
Chris Wren50c8f422014-01-15 16:10:39 -050075 private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
76 private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
Chris Wren1ada10d2013-09-13 18:01:38 -040077
Sunny Goyal107ea632015-07-20 12:59:39 -070078 private static final int BACKUP_VERSION = 4;
Chris Wren1ada10d2013-09-13 18:01:38 -040079 private static final int MAX_JOURNAL_SIZE = 1000000;
80
Sunny Goyal33d44382014-10-16 09:24:19 -070081 // Journal key is such that it is always smaller than any dynamically generated
82 // key (any Base64 encoded string).
83 private static final String JOURNAL_KEY = "#";
84
Chris Wrenfd13c712013-09-27 15:45:19 -040085 /** icons are large, dribble them out */
Chris Wren22e130d2013-09-23 18:25:57 -040086 private static final int MAX_ICONS_PER_PASS = 10;
87
Chris Wrenfd13c712013-09-27 15:45:19 -040088 /** widgets contain previews, which are very large, dribble them out */
89 private static final int MAX_WIDGETS_PER_PASS = 5;
90
Chris Wren1ada10d2013-09-13 18:01:38 -040091 private static final String[] FAVORITE_PROJECTION = {
Sunny Goyalef728d42014-10-22 11:28:28 -070092 Favorites._ID, // 0
93 Favorites.MODIFIED, // 1
94 Favorites.INTENT, // 2
95 Favorites.APPWIDGET_PROVIDER, // 3
96 Favorites.APPWIDGET_ID, // 4
97 Favorites.CELLX, // 5
98 Favorites.CELLY, // 6
99 Favorites.CONTAINER, // 7
100 Favorites.ICON, // 8
101 Favorites.ICON_PACKAGE, // 9
102 Favorites.ICON_RESOURCE, // 10
103 Favorites.ICON_TYPE, // 11
104 Favorites.ITEM_TYPE, // 12
105 Favorites.SCREEN, // 13
106 Favorites.SPANX, // 14
107 Favorites.SPANY, // 15
108 Favorites.TITLE, // 16
109 Favorites.PROFILE_ID, // 17
Sunny Goyal107ea632015-07-20 12:59:39 -0700110 Favorites.RANK, // 18
Chris Wren1ada10d2013-09-13 18:01:38 -0400111 };
112
113 private static final int ID_INDEX = 0;
Chris Wren22e130d2013-09-23 18:25:57 -0400114 private static final int ID_MODIFIED = 1;
115 private static final int INTENT_INDEX = 2;
116 private static final int APPWIDGET_PROVIDER_INDEX = 3;
117 private static final int APPWIDGET_ID_INDEX = 4;
118 private static final int CELLX_INDEX = 5;
119 private static final int CELLY_INDEX = 6;
120 private static final int CONTAINER_INDEX = 7;
121 private static final int ICON_INDEX = 8;
122 private static final int ICON_PACKAGE_INDEX = 9;
123 private static final int ICON_RESOURCE_INDEX = 10;
124 private static final int ICON_TYPE_INDEX = 11;
125 private static final int ITEM_TYPE_INDEX = 12;
126 private static final int SCREEN_INDEX = 13;
127 private static final int SPANX_INDEX = 14;
128 private static final int SPANY_INDEX = 15;
129 private static final int TITLE_INDEX = 16;
Sunny Goyal107ea632015-07-20 12:59:39 -0700130 private static final int RANK_INDEX = 18;
Chris Wren1ada10d2013-09-13 18:01:38 -0400131
132 private static final String[] SCREEN_PROJECTION = {
Sunny Goyalef728d42014-10-22 11:28:28 -0700133 WorkspaceScreens._ID, // 0
134 WorkspaceScreens.MODIFIED, // 1
135 WorkspaceScreens.SCREEN_RANK // 2
Chris Wren1ada10d2013-09-13 18:01:38 -0400136 };
137
Chris Wren22e130d2013-09-23 18:25:57 -0400138 private static final int SCREEN_RANK_INDEX = 2;
Chris Wren1ada10d2013-09-13 18:01:38 -0400139
Sunny Goyal316490e2015-06-02 09:38:28 -0700140 @Thunk final Context mContext;
Sunny Goyalef728d42014-10-22 11:28:28 -0700141 private final HashSet<String> mExistingKeys;
Sunny Goyal42de82f2014-09-26 22:09:29 -0700142 private final ArrayList<Key> mKeys;
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800143 private final ItemTypeMatcher[] mItemTypeMatchers;
Sunny Goyal4fbc3822015-02-18 16:46:50 -0800144 private final long mUserSerial;
Chris Wren1ada10d2013-09-13 18:01:38 -0400145
Sunny Goyalef728d42014-10-22 11:28:28 -0700146 private BackupManager mBackupManager;
Sunny Goyalef728d42014-10-22 11:28:28 -0700147 private byte[] mBuffer = new byte[512];
148 private long mLastBackupRestoreTime;
Sunny Goyalc0ee6752015-01-16 14:10:32 -0800149 private boolean mBackupDataWasUpdated;
Sunny Goyalef728d42014-10-22 11:28:28 -0700150
Adam Cohen2e6da152015-05-06 11:42:25 -0700151 private IconCache mIconCache;
152 private DeviceProfieData mDeviceProfileData;
Sunny Goyal3a30cfe2015-07-16 17:27:43 -0700153 private InvariantDeviceProfile mIdp;
Adam Cohen2e6da152015-05-06 11:42:25 -0700154
Sunny Goyal33d44382014-10-16 09:24:19 -0700155 boolean restoreSuccessful;
Sunny Goyal08f72612015-01-05 13:41:43 -0800156 int restoredBackupVersion = 1;
Sunny Goyal33d44382014-10-16 09:24:19 -0700157
Sunny Goyalc115e642015-07-20 14:23:43 -0700158 // When migrating from a device which different hotseat configuration, the icons are shifted
159 // to center along the new all-apps icon.
160 private int mHotseatShift = 0;
161
Sunny Goyal33d44382014-10-16 09:24:19 -0700162 public LauncherBackupHelper(Context context) {
Chris Wren92aa4232013-10-04 11:29:36 -0400163 mContext = context;
Sunny Goyalef728d42014-10-22 11:28:28 -0700164 mExistingKeys = new HashSet<String>();
Sunny Goyal42de82f2014-09-26 22:09:29 -0700165 mKeys = new ArrayList<Key>();
Sunny Goyal33d44382014-10-16 09:24:19 -0700166 restoreSuccessful = true;
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800167 mItemTypeMatchers = new ItemTypeMatcher[CommonAppTypeParser.SUPPORTED_TYPE_COUNT];
Sunny Goyal4fbc3822015-02-18 16:46:50 -0800168
169 UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
170 mUserSerial = userManager.getSerialNumberForUser(UserHandleCompat.myUserHandle());
Chris Wren92aa4232013-10-04 11:29:36 -0400171 }
172
173 private void dataChanged() {
Sunny Goyalef728d42014-10-22 11:28:28 -0700174 if (mBackupManager == null) {
175 mBackupManager = new BackupManager(mContext);
Chris Wren1ada10d2013-09-13 18:01:38 -0400176 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700177 mBackupManager.dataChanged();
178 }
179
180 private void applyJournal(Journal journal) {
181 mLastBackupRestoreTime = journal.t;
182 mExistingKeys.clear();
183 if (journal.key != null) {
184 for (Key key : journal.key) {
185 mExistingKeys.add(keyToBackupKey(key));
186 }
187 }
Sunny Goyal3a30cfe2015-07-16 17:27:43 -0700188 restoredBackupVersion = journal.backupVersion;
Chris Wren1ada10d2013-09-13 18:01:38 -0400189 }
190
191 /**
192 * Back up launcher data so we can restore the user's state on a new device.
193 *
194 * <P>The journal is a timestamp and a list of keys that were saved as of that time.
195 *
196 * <P>Keys may come back in any order, so each key/value is one complete row of the database.
197 *
198 * @param oldState notes from the last backup
199 * @param data incremental key/value pairs to persist off-device
200 * @param newState notes for the next backup
Chris Wren1ada10d2013-09-13 18:01:38 -0400201 */
202 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400203 public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
204 ParcelFileDescriptor newState) {
Chris Wren50c8f422014-01-15 16:10:39 -0500205 if (VERBOSE) Log.v(TAG, "onBackup");
Chris Wren1ada10d2013-09-13 18:01:38 -0400206
207 Journal in = readJournal(oldState);
Sunny Goyalef728d42014-10-22 11:28:28 -0700208 if (!launcherIsReady()) {
Adam Cohen2e6da152015-05-06 11:42:25 -0700209 dataChanged();
Sunny Goyalef728d42014-10-22 11:28:28 -0700210 // Perform backup later.
211 writeJournal(newState, in);
212 return;
213 }
Adam Cohen2e6da152015-05-06 11:42:25 -0700214
Sunny Goyal96373642015-06-05 11:14:01 -0700215 if (mDeviceProfileData == null) {
216 LauncherAppState app = LauncherAppState.getInstance();
Sunny Goyal3a30cfe2015-07-16 17:27:43 -0700217 mIdp = app.getInvariantDeviceProfile();
218 mDeviceProfileData = initDeviceProfileData(mIdp);
Sunny Goyal96373642015-06-05 11:14:01 -0700219 mIconCache = app.getIconCache();
220 }
Adam Cohen2e6da152015-05-06 11:42:25 -0700221
Sunny Goyalef728d42014-10-22 11:28:28 -0700222 Log.v(TAG, "lastBackupTime = " + in.t);
223 mKeys.clear();
224 applyJournal(in);
Chris Wren1ada10d2013-09-13 18:01:38 -0400225
Sunny Goyalef728d42014-10-22 11:28:28 -0700226 // Record the time before performing backup so that entries edited while the backup
227 // was going on, do not get missed in next backup.
228 long newBackupTime = System.currentTimeMillis();
Sunny Goyalc0ee6752015-01-16 14:10:32 -0800229 mBackupDataWasUpdated = false;
Sunny Goyalef728d42014-10-22 11:28:28 -0700230 try {
231 backupFavorites(data);
232 backupScreens(data);
233 backupIcons(data);
234 backupWidgets(data);
Chris Wren1ada10d2013-09-13 18:01:38 -0400235
Sunny Goyalef728d42014-10-22 11:28:28 -0700236 // Delete any key which still exist in the old backup, but is not valid anymore.
237 HashSet<String> validKeys = new HashSet<String>();
238 for (Key key : mKeys) {
239 validKeys.add(keyToBackupKey(key));
Chris Wren71144262014-02-27 15:49:39 -0500240 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700241 mExistingKeys.removeAll(validKeys);
242
243 // Delete anything left in the existing keys.
244 for (String deleted: mExistingKeys) {
245 if (VERBOSE) Log.v(TAG, "dropping deleted item " + deleted);
246 data.writeEntityHeader(deleted, -1);
Sunny Goyalc0ee6752015-01-16 14:10:32 -0800247 mBackupDataWasUpdated = true;
Sunny Goyalef728d42014-10-22 11:28:28 -0700248 }
249
250 mExistingKeys.clear();
Sunny Goyalc0ee6752015-01-16 14:10:32 -0800251 if (!mBackupDataWasUpdated) {
252 // Check if any metadata has changed
253 mBackupDataWasUpdated = (in.profile == null)
254 || !Arrays.equals(DeviceProfieData.toByteArray(in.profile),
Sunny Goyal96373642015-06-05 11:14:01 -0700255 DeviceProfieData.toByteArray(mDeviceProfileData))
Sunny Goyalc0ee6752015-01-16 14:10:32 -0800256 || (in.backupVersion != BACKUP_VERSION)
257 || (in.appVersion != getAppVersion());
258 }
Sunny Goyal33d44382014-10-16 09:24:19 -0700259
Sunny Goyalc0ee6752015-01-16 14:10:32 -0800260 if (mBackupDataWasUpdated) {
261 mLastBackupRestoreTime = newBackupTime;
262
263 // We store the journal at two places.
264 // 1) Storing it in newState allows us to do partial backups by comparing old state
265 // 2) Storing it in backup data allows us to validate keys during restore
266 Journal state = getCurrentStateJournal();
267 writeRowToBackup(JOURNAL_KEY, state, data);
268 } else {
269 if (DEBUG) Log.d(TAG, "Nothing was written during backup");
270 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700271 } catch (IOException e) {
272 Log.e(TAG, "launcher backup has failed", e);
Chris Wren92aa4232013-10-04 11:29:36 -0400273 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400274
Sunny Goyalef728d42014-10-22 11:28:28 -0700275 writeNewStateDescription(newState);
Chris Wren1ada10d2013-09-13 18:01:38 -0400276 }
277
278 /**
Sunny Goyal33d44382014-10-16 09:24:19 -0700279 * @return true if the backup corresponding to oldstate can be successfully applied
280 * to this device.
281 */
282 private boolean isBackupCompatible(Journal oldState) {
Sunny Goyal96373642015-06-05 11:14:01 -0700283 DeviceProfieData currentProfile = mDeviceProfileData;
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700284 DeviceProfieData oldProfile = oldState.profile;
285
286 if (oldProfile == null || oldProfile.desktopCols == 0) {
287 // Profile info is not valid, ignore the check.
288 return true;
289 }
290
291 boolean isHotsetCompatible = false;
292 if (currentProfile.allappsRank >= oldProfile.hotseatCount) {
293 isHotsetCompatible = true;
Sunny Goyalc115e642015-07-20 14:23:43 -0700294 mHotseatShift = 0;
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700295 }
Sunny Goyalc115e642015-07-20 14:23:43 -0700296
297 if ((currentProfile.allappsRank >= oldProfile.allappsRank)
298 && ((currentProfile.hotseatCount - currentProfile.allappsRank) >=
299 (oldProfile.hotseatCount - oldProfile.allappsRank))) {
300 // There is enough space on both sides of the hotseat.
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700301 isHotsetCompatible = true;
Sunny Goyalc115e642015-07-20 14:23:43 -0700302 mHotseatShift = currentProfile.allappsRank - oldProfile.allappsRank;
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700303 }
304
305 return isHotsetCompatible && (currentProfile.desktopCols >= oldProfile.desktopCols)
306 && (currentProfile.desktopRows >= oldProfile.desktopRows);
Sunny Goyal33d44382014-10-16 09:24:19 -0700307 }
308
309 /**
Chris Wren92aa4232013-10-04 11:29:36 -0400310 * Restore launcher configuration from the restored data stream.
Sunny Goyal33d44382014-10-16 09:24:19 -0700311 * It assumes that the keys will arrive in lexical order. So if the journal was present in the
312 * backup, it should arrive first.
Chris Wren1ada10d2013-09-13 18:01:38 -0400313 *
Chris Wren92aa4232013-10-04 11:29:36 -0400314 * @param data the key/value pair from the server
Chris Wren1ada10d2013-09-13 18:01:38 -0400315 */
316 @Override
Chris Wren92aa4232013-10-04 11:29:36 -0400317 public void restoreEntity(BackupDataInputStream data) {
Sunny Goyal33d44382014-10-16 09:24:19 -0700318 if (!restoreSuccessful) {
319 return;
320 }
Sunny Goyal96373642015-06-05 11:14:01 -0700321
322 if (mDeviceProfileData == null) {
323 // This call does not happen on a looper thread. So LauncherAppState
324 // can't be created . Instead initialize required dependencies directly.
Sunny Goyal3a30cfe2015-07-16 17:27:43 -0700325 mIdp = new InvariantDeviceProfile(mContext);
326 mDeviceProfileData = initDeviceProfileData(mIdp);
327 mIconCache = new IconCache(mContext, mIdp);
Sunny Goyal96373642015-06-05 11:14:01 -0700328 }
Sunny Goyal33d44382014-10-16 09:24:19 -0700329
Sunny Goyalef728d42014-10-22 11:28:28 -0700330 int dataSize = data.size();
331 if (mBuffer.length < dataSize) {
332 mBuffer = new byte[dataSize];
Chris Wren92aa4232013-10-04 11:29:36 -0400333 }
334 try {
Sunny Goyalef728d42014-10-22 11:28:28 -0700335 int bytesRead = data.read(mBuffer, 0, dataSize);
336 if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
337 String backupKey = data.getKey();
Sunny Goyal33d44382014-10-16 09:24:19 -0700338
339 if (JOURNAL_KEY.equals(backupKey)) {
340 if (VERBOSE) Log.v(TAG, "Journal entry restored");
341 if (!mKeys.isEmpty()) {
342 // We received the journal key after a restore key.
343 Log.wtf(TAG, keyToBackupKey(mKeys.get(0)) + " received after " + JOURNAL_KEY);
344 restoreSuccessful = false;
345 return;
346 }
347
348 Journal journal = new Journal();
349 MessageNano.mergeFrom(journal, readCheckedBytes(mBuffer, dataSize));
350 applyJournal(journal);
351 restoreSuccessful = isBackupCompatible(journal);
352 return;
353 }
354
355 if (!mExistingKeys.isEmpty() && !mExistingKeys.contains(backupKey)) {
356 if (DEBUG) Log.e(TAG, "Ignoring key not present in the backup state " + backupKey);
357 return;
358 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700359 Key key = backupKeyToKey(backupKey);
Chris Wren50c8f422014-01-15 16:10:39 -0500360 mKeys.add(key);
Chris Wren92aa4232013-10-04 11:29:36 -0400361 switch (key.type) {
362 case Key.FAVORITE:
Sunny Goyalef728d42014-10-22 11:28:28 -0700363 restoreFavorite(key, mBuffer, dataSize);
Chris Wren92aa4232013-10-04 11:29:36 -0400364 break;
365
366 case Key.SCREEN:
Sunny Goyalef728d42014-10-22 11:28:28 -0700367 restoreScreen(key, mBuffer, dataSize);
Chris Wren92aa4232013-10-04 11:29:36 -0400368 break;
369
370 case Key.ICON:
Sunny Goyalef728d42014-10-22 11:28:28 -0700371 restoreIcon(key, mBuffer, dataSize);
Chris Wren92aa4232013-10-04 11:29:36 -0400372 break;
373
374 case Key.WIDGET:
Sunny Goyalef728d42014-10-22 11:28:28 -0700375 restoreWidget(key, mBuffer, dataSize);
Chris Wren92aa4232013-10-04 11:29:36 -0400376 break;
377
378 default:
379 Log.w(TAG, "unknown restore entity type: " + key.type);
Sunny Goyalef728d42014-10-22 11:28:28 -0700380 mKeys.remove(key);
Chris Wren92aa4232013-10-04 11:29:36 -0400381 break;
Chris Wren1ada10d2013-09-13 18:01:38 -0400382 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700383 } catch (IOException e) {
384 Log.w(TAG, "ignoring unparsable backup entry", e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400385 }
Chris Wren92aa4232013-10-04 11:29:36 -0400386 }
387
388 /**
389 * Record the restore state for the next backup.
390 *
391 * @param newState notes about the backup state after restore.
392 */
393 @Override
394 public void writeNewStateDescription(ParcelFileDescriptor newState) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700395 writeJournal(newState, getCurrentStateJournal());
396 }
397
398 private Journal getCurrentStateJournal() {
399 Journal journal = new Journal();
400 journal.t = mLastBackupRestoreTime;
401 journal.key = mKeys.toArray(new BackupProtos.Key[mKeys.size()]);
402 journal.appVersion = getAppVersion();
Sunny Goyal33d44382014-10-16 09:24:19 -0700403 journal.backupVersion = BACKUP_VERSION;
Sunny Goyal96373642015-06-05 11:14:01 -0700404 journal.profile = mDeviceProfileData;
Sunny Goyalef728d42014-10-22 11:28:28 -0700405 return journal;
406 }
407
408 private int getAppVersion() {
409 try {
410 return mContext.getPackageManager()
411 .getPackageInfo(mContext.getPackageName(), 0).versionCode;
412 } catch (NameNotFoundException e) {
413 return 0;
414 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400415 }
416
Adam Cohen2e6da152015-05-06 11:42:25 -0700417 private DeviceProfieData initDeviceProfileData(InvariantDeviceProfile profile) {
418 DeviceProfieData data = new DeviceProfieData();
419 data.desktopRows = profile.numRows;
420 data.desktopCols = profile.numColumns;
421 data.hotseatCount = profile.numHotseatIcons;
422 data.allappsRank = profile.hotseatAllAppsRank;
423 return data;
Sunny Goyal33d44382014-10-16 09:24:19 -0700424 }
425
426 /**
Chris Wren1ada10d2013-09-13 18:01:38 -0400427 * Write all modified favorites to the data stream.
428 *
Chris Wren1ada10d2013-09-13 18:01:38 -0400429 * @param data output stream for key/value pairs
Chris Wren1ada10d2013-09-13 18:01:38 -0400430 * @throws IOException
431 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700432 private void backupFavorites(BackupDataOutput data) throws IOException {
Chris Wren1ada10d2013-09-13 18:01:38 -0400433 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400434 ContentResolver cr = mContext.getContentResolver();
Sunny Goyalffe83f12014-08-14 17:39:34 -0700435 // Don't backup apps in other profiles for now.
Chris Wren22e130d2013-09-23 18:25:57 -0400436 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
Sunny Goyalffe83f12014-08-14 17:39:34 -0700437 getUserSelectionArg(), null, null);
Chris Wren1ada10d2013-09-13 18:01:38 -0400438 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400439 cursor.moveToPosition(-1);
440 while(cursor.moveToNext()) {
441 final long id = cursor.getLong(ID_INDEX);
Sunny Goyalffe83f12014-08-14 17:39:34 -0700442 final long updateTime = cursor.getLong(ID_MODIFIED);
443 Key key = getKey(Key.FAVORITE, id);
Sunny Goyalef728d42014-10-22 11:28:28 -0700444 mKeys.add(key);
Sunny Goyalffe83f12014-08-14 17:39:34 -0700445 final String backupKey = keyToBackupKey(key);
Sunny Goyal107ea632015-07-20 12:59:39 -0700446
447 // Favorite proto changed in v4. Backup again if the version is old.
448 if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime
449 || restoredBackupVersion < 4) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700450 writeRowToBackup(key, packFavorite(cursor), data);
Chris Wren50c8f422014-01-15 16:10:39 -0500451 } else {
Sunny Goyalef728d42014-10-22 11:28:28 -0700452 if (DEBUG) Log.d(TAG, "favorite already backup up: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400453 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400454 }
455 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400456 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400457 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400458 }
459
460 /**
461 * Read a favorite from the stream.
462 *
463 * <P>Keys arrive in any order, so screens and containers may not exist yet.
464 *
465 * @param key identifier for the row
466 * @param buffer the serialized proto from the stream, may be larger than dataSize
467 * @param dataSize the size of the proto from the stream
Chris Wren1ada10d2013-09-13 18:01:38 -0400468 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700469 private void restoreFavorite(Key key, byte[] buffer, int dataSize) throws IOException {
Chris Wren50c8f422014-01-15 16:10:39 -0500470 if (VERBOSE) Log.v(TAG, "unpacking favorite " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400471 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
472 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
473
Sunny Goyalef728d42014-10-22 11:28:28 -0700474 ContentResolver cr = mContext.getContentResolver();
475 ContentValues values = unpackFavorite(buffer, dataSize);
Sunny Goyal1d4a2df2015-03-30 11:11:46 -0700476 cr.insert(Favorites.CONTENT_URI, values);
Chris Wren1ada10d2013-09-13 18:01:38 -0400477 }
478
479 /**
480 * Write all modified screens to the data stream.
481 *
Chris Wren1ada10d2013-09-13 18:01:38 -0400482 * @param data output stream for key/value pairs
Chris Wren22e130d2013-09-23 18:25:57 -0400483 * @throws IOException
Chris Wren1ada10d2013-09-13 18:01:38 -0400484 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700485 private void backupScreens(BackupDataOutput data) throws IOException {
Chris Wren1ada10d2013-09-13 18:01:38 -0400486 // persist things that have changed since the last backup
Chris Wren92aa4232013-10-04 11:29:36 -0400487 ContentResolver cr = mContext.getContentResolver();
Chris Wren22e130d2013-09-23 18:25:57 -0400488 Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
489 null, null, null);
Chris Wren1ada10d2013-09-13 18:01:38 -0400490 try {
Chris Wren22e130d2013-09-23 18:25:57 -0400491 cursor.moveToPosition(-1);
Sunny Goyalef728d42014-10-22 11:28:28 -0700492 if (DEBUG) Log.d(TAG, "dumping screens after: " + mLastBackupRestoreTime);
Chris Wren22e130d2013-09-23 18:25:57 -0400493 while(cursor.moveToNext()) {
494 final long id = cursor.getLong(ID_INDEX);
495 final long updateTime = cursor.getLong(ID_MODIFIED);
Chris Wren1ada10d2013-09-13 18:01:38 -0400496 Key key = getKey(Key.SCREEN, id);
Sunny Goyalef728d42014-10-22 11:28:28 -0700497 mKeys.add(key);
Chris Wren5743aa92014-01-10 18:02:06 -0500498 final String backupKey = keyToBackupKey(key);
Sunny Goyalef728d42014-10-22 11:28:28 -0700499 if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime) {
500 writeRowToBackup(key, packScreen(cursor), data);
Chris Wren5dee7af2013-12-20 17:22:11 -0500501 } else {
Sunny Goyalef728d42014-10-22 11:28:28 -0700502 if (VERBOSE) Log.v(TAG, "screen already backup up " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400503 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400504 }
505 } finally {
Chris Wren22e130d2013-09-23 18:25:57 -0400506 cursor.close();
Chris Wren1ada10d2013-09-13 18:01:38 -0400507 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400508 }
509
510 /**
511 * Read a screen from the stream.
512 *
513 * <P>Keys arrive in any order, so children of this screen may already exist.
514 *
515 * @param key identifier for the row
516 * @param buffer the serialized proto from the stream, may be larger than dataSize
517 * @param dataSize the size of the proto from the stream
Chris Wren1ada10d2013-09-13 18:01:38 -0400518 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700519 private void restoreScreen(Key key, byte[] buffer, int dataSize) throws IOException {
Chris Wren50c8f422014-01-15 16:10:39 -0500520 if (VERBOSE) Log.v(TAG, "unpacking screen " + key.id);
Chris Wren1ada10d2013-09-13 18:01:38 -0400521 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
522 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Chris Wren5dee7af2013-12-20 17:22:11 -0500523
Sunny Goyalef728d42014-10-22 11:28:28 -0700524 ContentResolver cr = mContext.getContentResolver();
525 ContentValues values = unpackScreen(buffer, dataSize);
526 cr.insert(WorkspaceScreens.CONTENT_URI, values);
Chris Wren1ada10d2013-09-13 18:01:38 -0400527 }
528
Chris Wren22e130d2013-09-23 18:25:57 -0400529 /**
530 * Write all the static icon resources we need to render placeholders
531 * for a package that is not installed.
532 *
Chris Wren22e130d2013-09-23 18:25:57 -0400533 * @param data output stream for key/value pairs
Chris Wren22e130d2013-09-23 18:25:57 -0400534 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700535 private void backupIcons(BackupDataOutput data) throws IOException {
Chris Wrenfd13c712013-09-27 15:45:19 -0400536 // persist icons that haven't been persisted yet
Chris Wren92aa4232013-10-04 11:29:36 -0400537 final ContentResolver cr = mContext.getContentResolver();
Chris Wren92aa4232013-10-04 11:29:36 -0400538 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Sunny Goyalffe83f12014-08-14 17:39:34 -0700539 final UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
Sunny Goyalef728d42014-10-22 11:28:28 -0700540 int backupUpIconCount = 0;
Chris Wren22e130d2013-09-23 18:25:57 -0400541
Kenny Guyed131872014-04-30 03:02:21 +0100542 // Don't backup apps in other profiles for now.
Kenny Guyed131872014-04-30 03:02:21 +0100543 String where = "(" + Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
Kenny Guy43ea7ac2014-05-09 16:44:18 +0100544 Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + ") AND " +
Sunny Goyalffe83f12014-08-14 17:39:34 -0700545 getUserSelectionArg();
Chris Wren22e130d2013-09-23 18:25:57 -0400546 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
547 where, null, null);
Chris Wren22e130d2013-09-23 18:25:57 -0400548 try {
549 cursor.moveToPosition(-1);
550 while(cursor.moveToNext()) {
551 final long id = cursor.getLong(ID_INDEX);
552 final String intentDescription = cursor.getString(INTENT_INDEX);
553 try {
554 Intent intent = Intent.parseUri(intentDescription, 0);
555 ComponentName cn = intent.getComponent();
556 Key key = null;
557 String backupKey = null;
558 if (cn != null) {
559 key = getKey(Key.ICON, cn.flattenToShortString());
560 backupKey = keyToBackupKey(key);
Chris Wren22e130d2013-09-23 18:25:57 -0400561 } else {
562 Log.w(TAG, "empty intent on application favorite: " + id);
563 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700564 if (mExistingKeys.contains(backupKey)) {
565 if (DEBUG) Log.d(TAG, "already saved icon " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400566
567 // remember that we already backed this up previously
Sunny Goyalef728d42014-10-22 11:28:28 -0700568 mKeys.add(key);
Chris Wren22e130d2013-09-23 18:25:57 -0400569 } else if (backupKey != null) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700570 if (DEBUG) Log.d(TAG, "I can count this high: " + backupUpIconCount);
571 if (backupUpIconCount < MAX_ICONS_PER_PASS) {
572 if (DEBUG) Log.d(TAG, "saving icon " + backupKey);
Kenny Guyed131872014-04-30 03:02:21 +0100573 Bitmap icon = mIconCache.getIcon(intent, myUserHandle);
Kenny Guyed131872014-04-30 03:02:21 +0100574 if (icon != null && !mIconCache.isDefaultIcon(icon, myUserHandle)) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700575 writeRowToBackup(key, packIcon(dpi, icon), data);
576 mKeys.add(key);
577 backupUpIconCount ++;
Chris Wren22e130d2013-09-23 18:25:57 -0400578 }
579 } else {
Sunny Goyalef728d42014-10-22 11:28:28 -0700580 if (VERBOSE) Log.v(TAG, "deferring icon backup " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -0400581 // too many icons for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400582 dataChanged();
Chris Wren22e130d2013-09-23 18:25:57 -0400583 }
584 }
585 } catch (URISyntaxException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500586 Log.e(TAG, "invalid URI on application favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400587 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -0500588 Log.e(TAG, "unable to save application icon for favorite: " + id);
Chris Wren22e130d2013-09-23 18:25:57 -0400589 }
590
591 }
592 } finally {
593 cursor.close();
594 }
Chris Wren22e130d2013-09-23 18:25:57 -0400595 }
596
597 /**
598 * Read an icon from the stream.
599 *
Chris Wrenfd13c712013-09-27 15:45:19 -0400600 * <P>Keys arrive in any order, so shortcuts that use this icon may already exist.
Chris Wren22e130d2013-09-23 18:25:57 -0400601 *
602 * @param key identifier for the row
603 * @param buffer the serialized proto from the stream, may be larger than dataSize
604 * @param dataSize the size of the proto from the stream
Chris Wren22e130d2013-09-23 18:25:57 -0400605 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700606 private void restoreIcon(Key key, byte[] buffer, int dataSize) throws IOException {
Chris Wren50c8f422014-01-15 16:10:39 -0500607 if (VERBOSE) Log.v(TAG, "unpacking icon " + key.id);
Chris Wren22e130d2013-09-23 18:25:57 -0400608 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
609 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Chris Wren6d0dde02014-02-10 12:16:54 -0500610
Sunny Goyalef728d42014-10-22 11:28:28 -0700611 Resource res = unpackProto(new Resource(), buffer, dataSize);
612 if (DEBUG) {
613 Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
Chris Wren22e130d2013-09-23 18:25:57 -0400614 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700615 Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
616 if (icon == null) {
617 Log.w(TAG, "failed to unpack icon for " + key.name);
Sunny Goyaldf6ccf82015-07-20 14:32:48 -0700618 } else {
619 if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
620 mIconCache.preloadIcon(ComponentName.unflattenFromString(key.name), icon, res.dpi,
621 "" /* label */, mUserSerial, mIdp);
Sunny Goyalef728d42014-10-22 11:28:28 -0700622 }
Chris Wren22e130d2013-09-23 18:25:57 -0400623 }
624
Chris Wrenfd13c712013-09-27 15:45:19 -0400625 /**
626 * Write all the static widget resources we need to render placeholders
627 * for a package that is not installed.
628 *
Chris Wrenfd13c712013-09-27 15:45:19 -0400629 * @param data output stream for key/value pairs
Chris Wrenfd13c712013-09-27 15:45:19 -0400630 * @throws IOException
631 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700632 private void backupWidgets(BackupDataOutput data) throws IOException {
Chris Wrenfd13c712013-09-27 15:45:19 -0400633 // persist static widget info that hasn't been persisted yet
Chris Wren92aa4232013-10-04 11:29:36 -0400634 final ContentResolver cr = mContext.getContentResolver();
Chris Wren92aa4232013-10-04 11:29:36 -0400635 final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
Sunny Goyalef728d42014-10-22 11:28:28 -0700636 int backupWidgetCount = 0;
Chris Wrenfd13c712013-09-27 15:45:19 -0400637
Sunny Goyalffe83f12014-08-14 17:39:34 -0700638 String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET + " AND "
639 + getUserSelectionArg();
Chris Wrenfd13c712013-09-27 15:45:19 -0400640 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
641 where, null, null);
Chris Wrenfd13c712013-09-27 15:45:19 -0400642 try {
643 cursor.moveToPosition(-1);
644 while(cursor.moveToNext()) {
645 final long id = cursor.getLong(ID_INDEX);
646 final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
Chris Wrenfd13c712013-09-27 15:45:19 -0400647 final ComponentName provider = ComponentName.unflattenFromString(providerName);
648 Key key = null;
649 String backupKey = null;
650 if (provider != null) {
651 key = getKey(Key.WIDGET, providerName);
652 backupKey = keyToBackupKey(key);
Chris Wrenfd13c712013-09-27 15:45:19 -0400653 } else {
654 Log.w(TAG, "empty intent on appwidget: " + id);
655 }
Sunny Goyal107ea632015-07-20 12:59:39 -0700656
657 // Widget backup proto changed in v3. So add it again if the original backup is old.
658 if (mExistingKeys.contains(backupKey) && restoredBackupVersion >= 3) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700659 if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400660
661 // remember that we already backed this up previously
Sunny Goyalef728d42014-10-22 11:28:28 -0700662 mKeys.add(key);
Chris Wrenfd13c712013-09-27 15:45:19 -0400663 } else if (backupKey != null) {
Sunny Goyalef728d42014-10-22 11:28:28 -0700664 if (DEBUG) Log.d(TAG, "I can count this high: " + backupWidgetCount);
665 if (backupWidgetCount < MAX_WIDGETS_PER_PASS) {
666 if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
Robin Lee26ace122015-03-16 19:41:43 +0000667 UserHandleCompat user = UserHandleCompat.myUserHandle();
Sunny Goyal96373642015-06-05 11:14:01 -0700668 writeRowToBackup(key, packWidget(dpi, provider, user), data);
Sunny Goyalef728d42014-10-22 11:28:28 -0700669 mKeys.add(key);
670 backupWidgetCount ++;
Chris Wrenfd13c712013-09-27 15:45:19 -0400671 } else {
Sunny Goyalef728d42014-10-22 11:28:28 -0700672 if (VERBOSE) Log.v(TAG, "deferring widget backup " + backupKey);
Chris Wrenfd13c712013-09-27 15:45:19 -0400673 // too many widgets for this pass, request another.
Chris Wren92aa4232013-10-04 11:29:36 -0400674 dataChanged();
Chris Wrenfd13c712013-09-27 15:45:19 -0400675 }
676 }
677 }
678 } finally {
679 cursor.close();
680 }
Chris Wrenfd13c712013-09-27 15:45:19 -0400681 }
682
683 /**
684 * Read a widget from the stream.
685 *
686 * <P>Keys arrive in any order, so widgets that use this data may already exist.
687 *
688 * @param key identifier for the row
689 * @param buffer the serialized proto from the stream, may be larger than dataSize
690 * @param dataSize the size of the proto from the stream
Chris Wrenfd13c712013-09-27 15:45:19 -0400691 */
Sunny Goyalef728d42014-10-22 11:28:28 -0700692 private void restoreWidget(Key key, byte[] buffer, int dataSize) throws IOException {
Chris Wren50c8f422014-01-15 16:10:39 -0500693 if (VERBOSE) Log.v(TAG, "unpacking widget " + key.id);
Chris Wrenfd13c712013-09-27 15:45:19 -0400694 if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
695 Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
Sunny Goyalef728d42014-10-22 11:28:28 -0700696 Widget widget = unpackProto(new Widget(), buffer, dataSize);
697 if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
698 if (widget.icon.data != null) {
699 Bitmap icon = BitmapFactory
700 .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
701 if (icon == null) {
702 Log.w(TAG, "failed to unpack widget icon for " + key.name);
Chris Wren5dee7af2013-12-20 17:22:11 -0500703 } else {
Sunny Goyal4fbc3822015-02-18 16:46:50 -0800704 mIconCache.preloadIcon(ComponentName.unflattenFromString(widget.provider),
Sunny Goyaldf6ccf82015-07-20 14:32:48 -0700705 icon, widget.icon.dpi, widget.label, mUserSerial, mIdp);
Chris Wren5dee7af2013-12-20 17:22:11 -0500706 }
Chris Wrenfd13c712013-09-27 15:45:19 -0400707 }
Sunny Goyalef728d42014-10-22 11:28:28 -0700708
709 // future site of widget table mutation
Chris Wrenfd13c712013-09-27 15:45:19 -0400710 }
711
Chris Wren22e130d2013-09-23 18:25:57 -0400712 /** create a new key, with an integer ID.
Chris Wren1ada10d2013-09-13 18:01:38 -0400713 *
714 * <P> Keys contain their own checksum instead of using
715 * the heavy-weight CheckedMessage wrapper.
716 */
717 private Key getKey(int type, long id) {
718 Key key = new Key();
719 key.type = type;
720 key.id = id;
721 key.checksum = checkKey(key);
722 return key;
723 }
724
Chris Wren22e130d2013-09-23 18:25:57 -0400725 /** create a new key for a named object.
726 *
727 * <P> Keys contain their own checksum instead of using
728 * the heavy-weight CheckedMessage wrapper.
729 */
730 private Key getKey(int type, String name) {
731 Key key = new Key();
732 key.type = type;
733 key.name = name;
734 key.checksum = checkKey(key);
735 return key;
736 }
737
Chris Wren1ada10d2013-09-13 18:01:38 -0400738 /** keys need to be strings, serialize and encode. */
739 private String keyToBackupKey(Key key) {
Chris Wren978194c2013-10-03 17:47:22 -0400740 return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP);
Chris Wren1ada10d2013-09-13 18:01:38 -0400741 }
742
743 /** keys need to be strings, decode and parse. */
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700744 private Key backupKeyToKey(String backupKey) throws InvalidBackupException {
Chris Wren1ada10d2013-09-13 18:01:38 -0400745 try {
746 Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
747 if (key.checksum != checkKey(key)) {
748 key = null;
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700749 throw new InvalidBackupException("invalid key read from stream" + backupKey);
Chris Wren1ada10d2013-09-13 18:01:38 -0400750 }
751 return key;
752 } catch (InvalidProtocolBufferNanoException e) {
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700753 throw new InvalidBackupException(e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400754 } catch (IllegalArgumentException e) {
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700755 throw new InvalidBackupException(e);
Chris Wren1ada10d2013-09-13 18:01:38 -0400756 }
757 }
758
759 /** Compute the checksum over the important bits of a key. */
760 private long checkKey(Key key) {
761 CRC32 checksum = new CRC32();
762 checksum.update(key.type);
763 checksum.update((int) (key.id & 0xffff));
764 checksum.update((int) ((key.id >> 32) & 0xffff));
765 if (!TextUtils.isEmpty(key.name)) {
766 checksum.update(key.name.getBytes());
767 }
768 return checksum.getValue();
769 }
770
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800771 /**
772 * @return true if its an hotseat item, that can be replaced during restore.
773 * TODO: Extend check for folders in hotseat.
774 */
775 private boolean isReplaceableHotseatItem(Favorite favorite) {
776 return favorite.container == Favorites.CONTAINER_HOTSEAT
777 && favorite.intent != null
778 && (favorite.itemType == Favorites.ITEM_TYPE_APPLICATION
779 || favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT);
780 }
781
Chris Wren1ada10d2013-09-13 18:01:38 -0400782 /** Serialize a Favorite for persistence, including a checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700783 private Favorite packFavorite(Cursor c) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400784 Favorite favorite = new Favorite();
785 favorite.id = c.getLong(ID_INDEX);
786 favorite.screen = c.getInt(SCREEN_INDEX);
787 favorite.container = c.getInt(CONTAINER_INDEX);
788 favorite.cellX = c.getInt(CELLX_INDEX);
789 favorite.cellY = c.getInt(CELLY_INDEX);
790 favorite.spanX = c.getInt(SPANX_INDEX);
791 favorite.spanY = c.getInt(SPANY_INDEX);
792 favorite.iconType = c.getInt(ICON_TYPE_INDEX);
Sunny Goyal107ea632015-07-20 12:59:39 -0700793 favorite.rank = c.getInt(RANK_INDEX);
Sunny Goyal4e5cc642015-06-25 16:37:44 -0700794
Chris Wren1ada10d2013-09-13 18:01:38 -0400795 String title = c.getString(TITLE_INDEX);
796 if (!TextUtils.isEmpty(title)) {
797 favorite.title = title;
798 }
Kenny Guyf8b1dfd2014-05-13 12:59:34 +0100799 String intentDescription = c.getString(INTENT_INDEX);
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800800 Intent intent = null;
Kenny Guyf8b1dfd2014-05-13 12:59:34 +0100801 if (!TextUtils.isEmpty(intentDescription)) {
802 try {
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800803 intent = Intent.parseUri(intentDescription, 0);
Kenny Guyf8b1dfd2014-05-13 12:59:34 +0100804 intent.removeExtra(ItemInfo.EXTRA_PROFILE);
805 favorite.intent = intent.toUri(0);
806 } catch (URISyntaxException e) {
807 Log.e(TAG, "Invalid intent", e);
Sunny Goyalef728d42014-10-22 11:28:28 -0700808 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400809 }
810 favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
811 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
812 favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
813 String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
814 if (!TextUtils.isEmpty(appWidgetProvider)) {
815 favorite.appWidgetProvider = appWidgetProvider;
816 }
Sunny Goyal4e5cc642015-06-25 16:37:44 -0700817 } else if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
818 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
819 String iconPackage = c.getString(ICON_PACKAGE_INDEX);
820 if (!TextUtils.isEmpty(iconPackage)) {
821 favorite.iconPackage = iconPackage;
822 }
823 String iconResource = c.getString(ICON_RESOURCE_INDEX);
824 if (!TextUtils.isEmpty(iconResource)) {
825 favorite.iconResource = iconResource;
826 }
827 }
828
829 byte[] blob = c.getBlob(ICON_INDEX);
830 if (blob != null && blob.length > 0) {
831 favorite.icon = blob;
832 }
Chris Wren1ada10d2013-09-13 18:01:38 -0400833 }
834
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800835 if (isReplaceableHotseatItem(favorite)) {
836 if (intent != null && intent.getComponent() != null) {
837 PackageManager pm = mContext.getPackageManager();
838 ActivityInfo activity = null;;
839 try {
840 activity = pm.getActivityInfo(intent.getComponent(), 0);
841 } catch (NameNotFoundException e) {
842 Log.e(TAG, "Target not found", e);
843 }
844 if (activity == null) {
845 return favorite;
846 }
847 for (int i = 0; i < mItemTypeMatchers.length; i++) {
848 if (mItemTypeMatchers[i] == null) {
849 mItemTypeMatchers[i] = new ItemTypeMatcher(
850 CommonAppTypeParser.getResourceForItemType(i));
851 }
852 if (mItemTypeMatchers[i].matches(activity, pm)) {
853 favorite.targetType = i;
854 break;
855 }
856 }
857 }
858 }
859
Sunny Goyalef728d42014-10-22 11:28:28 -0700860 return favorite;
Chris Wren1ada10d2013-09-13 18:01:38 -0400861 }
862
863 /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700864 private ContentValues unpackFavorite(byte[] buffer, int dataSize)
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700865 throws IOException {
Sunny Goyalef728d42014-10-22 11:28:28 -0700866 Favorite favorite = unpackProto(new Favorite(), buffer, dataSize);
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800867
Sunny Goyalc115e642015-07-20 14:23:43 -0700868 // If it is a hotseat item, move it accordingly.
869 if (favorite.container == Favorites.CONTAINER_HOTSEAT) {
870 favorite.screen += mHotseatShift;
871 }
872
Chris Wren5dee7af2013-12-20 17:22:11 -0500873 ContentValues values = new ContentValues();
874 values.put(Favorites._ID, favorite.id);
875 values.put(Favorites.SCREEN, favorite.screen);
876 values.put(Favorites.CONTAINER, favorite.container);
877 values.put(Favorites.CELLX, favorite.cellX);
878 values.put(Favorites.CELLY, favorite.cellY);
879 values.put(Favorites.SPANX, favorite.spanX);
880 values.put(Favorites.SPANY, favorite.spanY);
Sunny Goyal107ea632015-07-20 12:59:39 -0700881 values.put(Favorites.RANK, favorite.rank);
Sunny Goyal4e5cc642015-06-25 16:37:44 -0700882
883 if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
884 values.put(Favorites.ICON_TYPE, favorite.iconType);
885 if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
886 values.put(Favorites.ICON_PACKAGE, favorite.iconPackage);
887 values.put(Favorites.ICON_RESOURCE, favorite.iconResource);
888 }
Chris Wren5dee7af2013-12-20 17:22:11 -0500889 values.put(Favorites.ICON, favorite.icon);
890 }
Sunny Goyal4e5cc642015-06-25 16:37:44 -0700891
Chris Wren5dee7af2013-12-20 17:22:11 -0500892 if (!TextUtils.isEmpty(favorite.title)) {
893 values.put(Favorites.TITLE, favorite.title);
894 } else {
895 values.put(Favorites.TITLE, "");
896 }
897 if (!TextUtils.isEmpty(favorite.intent)) {
898 values.put(Favorites.INTENT, favorite.intent);
899 }
900 values.put(Favorites.ITEM_TYPE, favorite.itemType);
Chris Wrenf4d08112014-01-16 18:13:56 -0500901
Kenny Guyf8b1dfd2014-05-13 12:59:34 +0100902 UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
903 long userSerialNumber =
904 UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
905 values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
906
Sunny Goyal96373642015-06-05 11:14:01 -0700907 DeviceProfieData currentProfile = mDeviceProfileData;
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700908
Sunny Goyalff572272014-07-23 13:58:07 -0700909 if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
910 if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
911 values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider);
912 }
913 values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId);
914 values.put(LauncherSettings.Favorites.RESTORED,
915 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
916 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
917 LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700918
919 // Verify placement
920 if (((favorite.cellX + favorite.spanX) > currentProfile.desktopCols)
921 || ((favorite.cellY + favorite.spanY) > currentProfile.desktopRows)) {
922 restoreSuccessful = false;
923 throw new InvalidBackupException("Widget not in screen bounds, aborting restore");
924 }
Sunny Goyalff572272014-07-23 13:58:07 -0700925 } else {
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800926 // Check if it is an hotseat item, that can be replaced.
927 if (isReplaceableHotseatItem(favorite)
928 && favorite.targetType != Favorite.TARGET_NONE
929 && favorite.targetType < CommonAppTypeParser.SUPPORTED_TYPE_COUNT) {
930 Log.e(TAG, "Added item type flag");
931 values.put(LauncherSettings.Favorites.RESTORED,
932 1 | CommonAppTypeParser.encodeItemTypeToFlag(favorite.targetType));
933 } else {
934 // Let LauncherModel know we've been here.
935 values.put(LauncherSettings.Favorites.RESTORED, 1);
936 }
Sunny Goyal5fd733d2014-10-29 10:51:54 -0700937
938 // Verify placement
939 if (favorite.container == Favorites.CONTAINER_HOTSEAT) {
940 if ((favorite.screen >= currentProfile.hotseatCount)
941 || (favorite.screen == currentProfile.allappsRank)) {
942 restoreSuccessful = false;
943 throw new InvalidBackupException("Item not in hotseat bounds, aborting restore");
944 }
945 } else {
946 if ((favorite.cellX >= currentProfile.desktopCols)
947 || (favorite.cellY >= currentProfile.desktopRows)) {
948 restoreSuccessful = false;
949 throw new InvalidBackupException("Item not in desktop bounds, aborting restore");
950 }
951 }
Sunny Goyalff572272014-07-23 13:58:07 -0700952 }
Chris Wrenf4d08112014-01-16 18:13:56 -0500953
Chris Wren5dee7af2013-12-20 17:22:11 -0500954 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400955 }
956
957 /** Serialize a Screen for persistence, including a checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700958 private Screen packScreen(Cursor c) {
Chris Wren1ada10d2013-09-13 18:01:38 -0400959 Screen screen = new Screen();
960 screen.id = c.getLong(ID_INDEX);
961 screen.rank = c.getInt(SCREEN_RANK_INDEX);
Sunny Goyalef728d42014-10-22 11:28:28 -0700962 return screen;
Chris Wren1ada10d2013-09-13 18:01:38 -0400963 }
964
965 /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700966 private ContentValues unpackScreen(byte[] buffer, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -0400967 throws InvalidProtocolBufferNanoException {
Sunny Goyalef728d42014-10-22 11:28:28 -0700968 Screen screen = unpackProto(new Screen(), buffer, dataSize);
Chris Wren5dee7af2013-12-20 17:22:11 -0500969 ContentValues values = new ContentValues();
970 values.put(WorkspaceScreens._ID, screen.id);
971 values.put(WorkspaceScreens.SCREEN_RANK, screen.rank);
972 return values;
Chris Wren1ada10d2013-09-13 18:01:38 -0400973 }
974
Chris Wren22e130d2013-09-23 18:25:57 -0400975 /** Serialize an icon Resource for persistence, including a checksum wrapper. */
Sunny Goyalef728d42014-10-22 11:28:28 -0700976 private Resource packIcon(int dpi, Bitmap icon) {
Chris Wren22e130d2013-09-23 18:25:57 -0400977 Resource res = new Resource();
978 res.dpi = dpi;
Sunny Goyal73a22a52015-04-28 16:51:09 -0700979 res.data = Utilities.flattenBitmap(icon);
Chris Wren22e130d2013-09-23 18:25:57 -0400980 return res;
981 }
982
Chris Wrenfd13c712013-09-27 15:45:19 -0400983 /** Serialize a widget for persistence, including a checksum wrapper. */
Sunny Goyal96373642015-06-05 11:14:01 -0700984 private Widget packWidget(int dpi, ComponentName provider, UserHandleCompat user) {
Adam Cohen59400422014-03-05 18:07:04 -0800985 final LauncherAppWidgetProviderInfo info =
Robin Lee26ace122015-03-16 19:41:43 +0000986 LauncherModel.getProviderInfo(mContext, provider, user);
Chris Wrenfd13c712013-09-27 15:45:19 -0400987 Widget widget = new Widget();
988 widget.provider = provider.flattenToShortString();
989 widget.label = info.label;
990 widget.configure = info.configure != null;
991 if (info.icon != 0) {
992 widget.icon = new Resource();
Sunny Goyal96373642015-06-05 11:14:01 -0700993 Drawable fullResIcon = mIconCache.getFullResIcon(provider.getPackageName(), info.icon);
Chris Wren92aa4232013-10-04 11:29:36 -0400994 Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
Sunny Goyal73a22a52015-04-28 16:51:09 -0700995 widget.icon.data = Utilities.flattenBitmap(icon);
996 widget.icon.dpi = dpi;
Chris Wrenfd13c712013-09-27 15:45:19 -0400997 }
Sunny Goyal3a30cfe2015-07-16 17:27:43 -0700998
999 // Calculate the spans corresponding to any one of the orientations as it should not change
1000 // based on orientation.
1001 int[] minSpans = CellLayout.rectToCell(
1002 mIdp.portraitProfile, mContext, info.minResizeWidth, info.minResizeHeight, null);
1003 widget.minSpanX = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0
1004 ? minSpans[0] : -1;
1005 widget.minSpanY = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_VERTICAL) != 0
1006 ? minSpans[1] : -1;
1007
Sunny Goyalef728d42014-10-22 11:28:28 -07001008 return widget;
Chris Wrenfd13c712013-09-27 15:45:19 -04001009 }
1010
Sunny Goyalef728d42014-10-22 11:28:28 -07001011 /**
1012 * Deserialize a proto after verifying checksum wrapper.
1013 */
1014 private <T extends MessageNano> T unpackProto(T proto, byte[] buffer, int dataSize)
Chris Wrenfd13c712013-09-27 15:45:19 -04001015 throws InvalidProtocolBufferNanoException {
Sunny Goyalef728d42014-10-22 11:28:28 -07001016 MessageNano.mergeFrom(proto, readCheckedBytes(buffer, dataSize));
1017 if (DEBUG) Log.d(TAG, "unpacked proto " + proto);
1018 return proto;
Chris Wrenfd13c712013-09-27 15:45:19 -04001019 }
1020
Chris Wren1ada10d2013-09-13 18:01:38 -04001021 /**
1022 * Read the old journal from the input file.
1023 *
1024 * In the event of any error, just pretend we didn't have a journal,
1025 * in that case, do a full backup.
1026 *
1027 * @param oldState the read-0only file descriptor pointing to the old journal
Chris Wren65b6a602014-01-10 14:11:25 -05001028 * @return a Journal protocol buffer
Chris Wren1ada10d2013-09-13 18:01:38 -04001029 */
1030 private Journal readJournal(ParcelFileDescriptor oldState) {
Chris Wren1ada10d2013-09-13 18:01:38 -04001031 Journal journal = new Journal();
Chris Wren92aa4232013-10-04 11:29:36 -04001032 if (oldState == null) {
1033 return journal;
1034 }
1035 FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
1036 try {
Chris Wren65b6a602014-01-10 14:11:25 -05001037 int availableBytes = inStream.available();
1038 if (DEBUG) Log.d(TAG, "available " + availableBytes);
1039 if (availableBytes < MAX_JOURNAL_SIZE) {
1040 byte[] buffer = new byte[availableBytes];
Chris Wren1ada10d2013-09-13 18:01:38 -04001041 int bytesRead = 0;
Chris Wren65b6a602014-01-10 14:11:25 -05001042 boolean valid = false;
Chris Wren50c8f422014-01-15 16:10:39 -05001043 InvalidProtocolBufferNanoException lastProtoException = null;
Chris Wren65b6a602014-01-10 14:11:25 -05001044 while (availableBytes > 0) {
Chris Wren92aa4232013-10-04 11:29:36 -04001045 try {
Chris Wren65b6a602014-01-10 14:11:25 -05001046 // OMG what are you doing? This is crazy inefficient!
1047 // If we read a byte that is not ours, we will cause trouble: b/12491813
1048 // However, we don't know how many bytes to expect (oops).
1049 // So we have to step through *slowly*, watching for the end.
1050 int result = inStream.read(buffer, bytesRead, 1);
Chris Wren92aa4232013-10-04 11:29:36 -04001051 if (result > 0) {
Chris Wren65b6a602014-01-10 14:11:25 -05001052 availableBytes -= result;
Chris Wren92aa4232013-10-04 11:29:36 -04001053 bytesRead += result;
1054 } else {
Chris Wren65b6a602014-01-10 14:11:25 -05001055 Log.w(TAG, "unexpected end of file while reading journal.");
1056 // stop reading and see what there is to parse
1057 availableBytes = 0;
Chris Wren92aa4232013-10-04 11:29:36 -04001058 }
1059 } catch (IOException e) {
Chris Wren92aa4232013-10-04 11:29:36 -04001060 buffer = null;
Chris Wren65b6a602014-01-10 14:11:25 -05001061 availableBytes = 0;
1062 }
1063
1064 // check the buffer to see if we have a valid journal
1065 try {
Sunny Goyalef728d42014-10-22 11:28:28 -07001066 MessageNano.mergeFrom(journal, readCheckedBytes(buffer, bytesRead));
Chris Wren65b6a602014-01-10 14:11:25 -05001067 // if we are here, then we have read a valid, checksum-verified journal
1068 valid = true;
1069 availableBytes = 0;
Chris Wren50c8f422014-01-15 16:10:39 -05001070 if (VERBOSE) Log.v(TAG, "read " + bytesRead + " bytes of journal");
Chris Wren65b6a602014-01-10 14:11:25 -05001071 } catch (InvalidProtocolBufferNanoException e) {
1072 // if we don't have the whole journal yet, mergeFrom will throw. keep going.
Chris Wren50c8f422014-01-15 16:10:39 -05001073 lastProtoException = e;
Chris Wren65b6a602014-01-10 14:11:25 -05001074 journal.clear();
Chris Wren92aa4232013-10-04 11:29:36 -04001075 }
Chris Wren1ada10d2013-09-13 18:01:38 -04001076 }
Chris Wren92aa4232013-10-04 11:29:36 -04001077 if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
Chris Wren65b6a602014-01-10 14:11:25 -05001078 if (!valid) {
Chris Wren50c8f422014-01-15 16:10:39 -05001079 Log.w(TAG, "could not find a valid journal", lastProtoException);
Chris Wren1ada10d2013-09-13 18:01:38 -04001080 }
1081 }
Chris Wren92aa4232013-10-04 11:29:36 -04001082 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001083 Log.w(TAG, "failed to close the journal", e);
Chris Wren92aa4232013-10-04 11:29:36 -04001084 } finally {
Chris Wren1ada10d2013-09-13 18:01:38 -04001085 try {
1086 inStream.close();
1087 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001088 Log.w(TAG, "failed to close the journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -04001089 }
1090 }
1091 return journal;
1092 }
1093
Sunny Goyalef728d42014-10-22 11:28:28 -07001094 private void writeRowToBackup(Key key, MessageNano proto, BackupDataOutput data)
1095 throws IOException {
1096 writeRowToBackup(keyToBackupKey(key), proto, data);
1097 }
1098
1099 private void writeRowToBackup(String backupKey, MessageNano proto,
Chris Wren22e130d2013-09-23 18:25:57 -04001100 BackupDataOutput data) throws IOException {
Sunny Goyalef728d42014-10-22 11:28:28 -07001101 byte[] blob = writeCheckedBytes(proto);
Chris Wren22e130d2013-09-23 18:25:57 -04001102 data.writeEntityHeader(backupKey, blob.length);
1103 data.writeEntityData(blob, blob.length);
Sunny Goyalc0ee6752015-01-16 14:10:32 -08001104 mBackupDataWasUpdated = true;
Sunny Goyalef728d42014-10-22 11:28:28 -07001105 if (VERBOSE) Log.v(TAG, "Writing New entry " + backupKey);
Chris Wren22e130d2013-09-23 18:25:57 -04001106 }
1107
Chris Wren1ada10d2013-09-13 18:01:38 -04001108 /**
1109 * Write the new journal to the output file.
1110 *
1111 * In the event of any error, just pretend we didn't have a journal,
1112 * in that case, do a full backup.
1113
1114 * @param newState the write-only file descriptor pointing to the new journal
1115 * @param journal a Journal protocol buffer
1116 */
1117 private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
1118 FileOutputStream outStream = null;
1119 try {
1120 outStream = new FileOutputStream(newState.getFileDescriptor());
Chris Wren65b6a602014-01-10 14:11:25 -05001121 final byte[] journalBytes = writeCheckedBytes(journal);
Chris Wren65b6a602014-01-10 14:11:25 -05001122 outStream.write(journalBytes);
Chris Wren1ada10d2013-09-13 18:01:38 -04001123 outStream.close();
Chris Wren50c8f422014-01-15 16:10:39 -05001124 if (VERBOSE) Log.v(TAG, "wrote " + journalBytes.length + " bytes of journal");
Chris Wren1ada10d2013-09-13 18:01:38 -04001125 } catch (IOException e) {
Chris Wren50c8f422014-01-15 16:10:39 -05001126 Log.w(TAG, "failed to write backup journal", e);
Chris Wren1ada10d2013-09-13 18:01:38 -04001127 }
1128 }
1129
1130 /** Wrap a proto in a CheckedMessage and compute the checksum. */
1131 private byte[] writeCheckedBytes(MessageNano proto) {
1132 CheckedMessage wrapper = new CheckedMessage();
1133 wrapper.payload = MessageNano.toByteArray(proto);
1134 CRC32 checksum = new CRC32();
1135 checksum.update(wrapper.payload);
1136 wrapper.checksum = checksum.getValue();
1137 return MessageNano.toByteArray(wrapper);
1138 }
1139
1140 /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
Sunny Goyalef728d42014-10-22 11:28:28 -07001141 private static byte[] readCheckedBytes(byte[] buffer, int dataSize)
Chris Wren1ada10d2013-09-13 18:01:38 -04001142 throws InvalidProtocolBufferNanoException {
1143 CheckedMessage wrapper = new CheckedMessage();
Sunny Goyalef728d42014-10-22 11:28:28 -07001144 MessageNano.mergeFrom(wrapper, buffer, 0, dataSize);
Chris Wren1ada10d2013-09-13 18:01:38 -04001145 CRC32 checksum = new CRC32();
1146 checksum.update(wrapper.payload);
1147 if (wrapper.checksum != checksum.getValue()) {
1148 throw new InvalidProtocolBufferNanoException("checksum does not match");
1149 }
1150 return wrapper.payload;
1151 }
1152
Sunny Goyalef728d42014-10-22 11:28:28 -07001153 /**
1154 * @return true if the launcher is in a state to support backup
1155 */
Chris Wren71144262014-02-27 15:49:39 -05001156 private boolean launcherIsReady() {
1157 ContentResolver cr = mContext.getContentResolver();
1158 Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION, null, null, null);
1159 if (cursor == null) {
1160 // launcher data has been wiped, do nothing
1161 return false;
1162 }
1163 cursor.close();
1164
Adam Cohen2e6da152015-05-06 11:42:25 -07001165 if (LauncherAppState.getInstanceNoCreate() == null) {
Chris Wren71144262014-02-27 15:49:39 -05001166 // launcher services are unavailable, try again later
Chris Wren71144262014-02-27 15:49:39 -05001167 return false;
1168 }
1169
1170 return true;
1171 }
1172
Sunny Goyalffe83f12014-08-14 17:39:34 -07001173 private String getUserSelectionArg() {
1174 return Favorites.PROFILE_ID + '=' + UserManagerCompat.getInstance(mContext)
1175 .getSerialNumberForUser(UserHandleCompat.myUserHandle());
1176 }
1177
Sunny Goyal316490e2015-06-02 09:38:28 -07001178 @Thunk class InvalidBackupException extends IOException {
Sunny Goyalbb3b02f2015-01-15 12:00:14 -08001179
1180 private static final long serialVersionUID = 8931456637211665082L;
1181
Sunny Goyal316490e2015-06-02 09:38:28 -07001182 @Thunk InvalidBackupException(Throwable cause) {
Chris Wren1ada10d2013-09-13 18:01:38 -04001183 super(cause);
1184 }
1185
Sunny Goyal316490e2015-06-02 09:38:28 -07001186 @Thunk InvalidBackupException(String reason) {
Chris Wren1ada10d2013-09-13 18:01:38 -04001187 super(reason);
1188 }
1189 }
Sunny Goyalbb3b02f2015-01-15 12:00:14 -08001190
1191 /**
1192 * A class to check if an activity can handle one of the intents from a list of
1193 * predefined intents.
1194 */
1195 private class ItemTypeMatcher {
1196
1197 private final ArrayList<Intent> mIntents;
1198
1199 ItemTypeMatcher(int xml_res) {
1200 mIntents = xml_res == 0 ? new ArrayList<Intent>() : parseIntents(xml_res);
1201 }
1202
1203 private ArrayList<Intent> parseIntents(int xml_res) {
1204 ArrayList<Intent> intents = new ArrayList<Intent>();
1205 XmlResourceParser parser = mContext.getResources().getXml(xml_res);
1206 try {
1207 DefaultLayoutParser.beginDocument(parser, DefaultLayoutParser.TAG_RESOLVE);
1208 final int depth = parser.getDepth();
1209 int type;
1210 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1211 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1212 if (type != XmlPullParser.START_TAG) {
1213 continue;
1214 } else if (DefaultLayoutParser.TAG_FAVORITE.equals(parser.getName())) {
1215 final String uri = DefaultLayoutParser.getAttributeValue(
1216 parser, DefaultLayoutParser.ATTR_URI);
1217 intents.add(Intent.parseUri(uri, 0));
1218 }
1219 }
1220 } catch (URISyntaxException | XmlPullParserException | IOException e) {
1221 Log.e(TAG, "Unable to parse " + xml_res, e);
1222 } finally {
1223 parser.close();
1224 }
1225 return intents;
1226 }
1227
1228 public boolean matches(ActivityInfo activity, PackageManager pm) {
1229 for (Intent intent : mIntents) {
1230 intent.setPackage(activity.packageName);
1231 ResolveInfo info = pm.resolveActivity(intent, 0);
1232 if (info != null && (info.activityInfo.name.equals(activity.name)
1233 || info.activityInfo.name.equals(activity.targetActivity))) {
1234 return true;
1235 }
1236 }
1237 return false;
1238 }
1239 }
Chris Wren1ada10d2013-09-13 18:01:38 -04001240}