Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2009 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 | |
Christopher Tate | 4528186 | 2010-03-05 15:46:30 -0800 | [diff] [blame] | 17 | package android.app.backup; |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 18 | |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 19 | import android.app.IBackupAgent; |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 20 | import android.app.QueuedWork; |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 21 | import android.content.Context; |
| 22 | import android.content.ContextWrapper; |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 23 | import android.content.pm.ApplicationInfo; |
Christopher Tate | 1902492 | 2010-01-22 16:39:53 -0800 | [diff] [blame] | 24 | import android.os.Binder; |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 25 | import android.os.Handler; |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 26 | import android.os.IBinder; |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 27 | import android.os.Looper; |
Christopher Tate | 22b8787 | 2009-05-04 16:41:53 -0700 | [diff] [blame] | 28 | import android.os.ParcelFileDescriptor; |
Christopher Tate | 5cb5c33 | 2013-02-21 14:32:12 -0800 | [diff] [blame] | 29 | import android.os.Process; |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 30 | import android.os.RemoteException; |
Elliott Hughes | 34385d3 | 2014-04-28 11:11:32 -0700 | [diff] [blame] | 31 | import android.system.ErrnoException; |
| 32 | import android.system.Os; |
| 33 | import android.system.OsConstants; |
| 34 | import android.system.StructStat; |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 35 | import android.util.ArraySet; |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 36 | import android.util.Log; |
| 37 | |
Christopher Tate | 91bb0e5 | 2016-09-30 17:52:19 -0700 | [diff] [blame] | 38 | import libcore.io.IoUtils; |
| 39 | |
Jeff Sharkey | 8a372a0 | 2016-03-16 16:25:45 -0600 | [diff] [blame] | 40 | import org.xmlpull.v1.XmlPullParserException; |
| 41 | |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 42 | import java.io.File; |
Christopher Tate | 7926a69 | 2011-07-11 11:31:57 -0700 | [diff] [blame] | 43 | import java.io.FileOutputStream; |
Joe Onorato | 83248c4 | 2009-06-17 17:55:20 -0700 | [diff] [blame] | 44 | import java.io.IOException; |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 45 | import java.util.Collection; |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 46 | import java.util.LinkedList; |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 47 | import java.util.Map; |
| 48 | import java.util.Set; |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 49 | import java.util.concurrent.CountDownLatch; |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 50 | |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 51 | /** |
Scott Main | d17da43 | 2010-04-29 21:42:58 -0700 | [diff] [blame] | 52 | * Provides the central interface between an |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 53 | * application and Android's data backup infrastructure. An application that wishes |
| 54 | * to participate in the backup and restore mechanism will declare a subclass of |
| 55 | * {@link android.app.backup.BackupAgent}, implement the |
Scott Main | d17da43 | 2010-04-29 21:42:58 -0700 | [diff] [blame] | 56 | * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} |
| 57 | * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods, |
| 58 | * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via |
Joe Fernandez | 61fd1e8 | 2011-10-26 13:39:11 -0700 | [diff] [blame] | 59 | * the <code> |
| 60 | * <a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> |
Scott Main | d17da43 | 2010-04-29 21:42:58 -0700 | [diff] [blame] | 61 | * tag's {@code android:backupAgent} attribute. |
Joe Fernandez | 61fd1e8 | 2011-10-26 13:39:11 -0700 | [diff] [blame] | 62 | * |
| 63 | * <div class="special reference"> |
| 64 | * <h3>Developer Guides</h3> |
| 65 | * <p>For more information about using BackupAgent, read the |
| 66 | * <a href="{@docRoot}guide/topics/data/backup.html">Data Backup</a> developer guide.</p></div> |
| 67 | * |
Scott Main | d17da43 | 2010-04-29 21:42:58 -0700 | [diff] [blame] | 68 | * <h3>Basic Operation</h3> |
Kenny Root | 5a20ea1 | 2010-02-23 18:49:11 -0800 | [diff] [blame] | 69 | * <p> |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 70 | * When the application makes changes to data that it wishes to keep backed up, |
| 71 | * it should call the |
| 72 | * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method. |
Scott Main | d17da43 | 2010-04-29 21:42:58 -0700 | [diff] [blame] | 73 | * This notifies the Android Backup Manager that the application needs an opportunity |
| 74 | * to update its backup image. The Backup Manager, in turn, schedules a |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 75 | * backup pass to be performed at an opportune time. |
| 76 | * <p> |
Scott Main | d17da43 | 2010-04-29 21:42:58 -0700 | [diff] [blame] | 77 | * Restore operations are typically performed only when applications are first |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 78 | * installed on a device. At that time, the operating system checks to see whether |
Scott Main | d17da43 | 2010-04-29 21:42:58 -0700 | [diff] [blame] | 79 | * there is a previously-saved data set available for the application being installed, and if so, |
| 80 | * begins an immediate restore pass to deliver the backup data as part of the installation |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 81 | * process. |
| 82 | * <p> |
Scott Main | d17da43 | 2010-04-29 21:42:58 -0700 | [diff] [blame] | 83 | * When a backup or restore pass is run, the application's process is launched |
| 84 | * (if not already running), the manifest-declared backup agent class (in the {@code |
| 85 | * android:backupAgent} attribute) is instantiated within |
| 86 | * that process, and the agent's {@link #onCreate()} method is invoked. This prepares the |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 87 | * agent instance to run the actual backup or restore logic. At this point the |
| 88 | * agent's |
| 89 | * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or |
| 90 | * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be |
| 91 | * invoked as appropriate for the operation being performed. |
| 92 | * <p> |
Scott Main | d17da43 | 2010-04-29 21:42:58 -0700 | [diff] [blame] | 93 | * A backup data set consists of one or more "entities," flattened binary data |
| 94 | * records that are each identified with a key string unique within the data set. Adding a |
| 95 | * record to the active data set or updating an existing record is done by simply |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 96 | * writing new entity data under the desired key. Deleting an entity from the data set |
| 97 | * is done by writing an entity under that key with header specifying a negative data |
| 98 | * size, and no actual entity data. |
| 99 | * <p> |
| 100 | * <b>Helper Classes</b> |
| 101 | * <p> |
| 102 | * An extensible agent based on convenient helper classes is available in |
| 103 | * {@link android.app.backup.BackupAgentHelper}. That class is particularly |
| 104 | * suited to handling of simple file or {@link android.content.SharedPreferences} |
| 105 | * backup and restore. |
| 106 | * |
| 107 | * @see android.app.backup.BackupManager |
| 108 | * @see android.app.backup.BackupAgentHelper |
| 109 | * @see android.app.backup.BackupDataInput |
| 110 | * @see android.app.backup.BackupDataOutput |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 111 | */ |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 112 | public abstract class BackupAgent extends ContextWrapper { |
Joe Onorato | 83248c4 | 2009-06-17 17:55:20 -0700 | [diff] [blame] | 113 | private static final String TAG = "BackupAgent"; |
Alan Jeon | f0e32ee | 2014-12-13 22:44:53 +0900 | [diff] [blame] | 114 | private static final boolean DEBUG = false; |
Joe Onorato | 83248c4 | 2009-06-17 17:55:20 -0700 | [diff] [blame] | 115 | |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 116 | /** @hide */ |
| 117 | public static final int TYPE_EOF = 0; |
| 118 | |
| 119 | /** |
| 120 | * During a full restore, indicates that the file system object being restored |
| 121 | * is an ordinary file. |
| 122 | */ |
| 123 | public static final int TYPE_FILE = 1; |
| 124 | |
| 125 | /** |
| 126 | * During a full restore, indicates that the file system object being restored |
| 127 | * is a directory. |
| 128 | */ |
| 129 | public static final int TYPE_DIRECTORY = 2; |
| 130 | |
| 131 | /** @hide */ |
| 132 | public static final int TYPE_SYMLINK = 3; |
| 133 | |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 134 | Handler mHandler = null; |
| 135 | |
Christopher Tate | cba5941 | 2014-04-01 10:38:29 -0700 | [diff] [blame] | 136 | Handler getHandler() { |
| 137 | if (mHandler == null) { |
| 138 | mHandler = new Handler(Looper.getMainLooper()); |
| 139 | } |
| 140 | return mHandler; |
| 141 | } |
| 142 | |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 143 | class SharedPrefsSynchronizer implements Runnable { |
| 144 | public final CountDownLatch mLatch = new CountDownLatch(1); |
| 145 | |
| 146 | @Override |
| 147 | public void run() { |
| 148 | QueuedWork.waitToFinish(); |
| 149 | mLatch.countDown(); |
| 150 | } |
| 151 | }; |
| 152 | |
| 153 | // Syncing shared preferences deferred writes needs to happen on the main looper thread |
| 154 | private void waitForSharedPrefs() { |
Christopher Tate | cba5941 | 2014-04-01 10:38:29 -0700 | [diff] [blame] | 155 | Handler h = getHandler(); |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 156 | final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer(); |
Christopher Tate | cba5941 | 2014-04-01 10:38:29 -0700 | [diff] [blame] | 157 | h.postAtFrontOfQueue(s); |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 158 | try { |
| 159 | s.mLatch.await(); |
| 160 | } catch (InterruptedException e) { /* ignored */ } |
| 161 | } |
| 162 | |
| 163 | |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 164 | public BackupAgent() { |
| 165 | super(null); |
| 166 | } |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 167 | |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 168 | /** |
| 169 | * Provided as a convenience for agent implementations that need an opportunity |
| 170 | * to do one-time initialization before the actual backup or restore operation |
| 171 | * is begun. |
| 172 | * <p> |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 173 | */ |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 174 | public void onCreate() { |
| 175 | } |
| 176 | |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 177 | /** |
| 178 | * Provided as a convenience for agent implementations that need to do some |
| 179 | * sort of shutdown process after backup or restore is completed. |
| 180 | * <p> |
| 181 | * Agents do not need to override this method. |
| 182 | */ |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 183 | public void onDestroy() { |
| 184 | } |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 185 | |
| 186 | /** |
Kenny Root | 5a20ea1 | 2010-02-23 18:49:11 -0800 | [diff] [blame] | 187 | * The application is being asked to write any data changed since the last |
| 188 | * time it performed a backup operation. The state data recorded during the |
| 189 | * last backup pass is provided in the <code>oldState</code> file |
| 190 | * descriptor. If <code>oldState</code> is <code>null</code>, no old state |
| 191 | * is available and the application should perform a full backup. In both |
| 192 | * cases, a representation of the final backup state after this pass should |
| 193 | * be written to the file pointed to by the file descriptor wrapped in |
| 194 | * <code>newState</code>. |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 195 | * <p> |
| 196 | * Each entity written to the {@link android.app.backup.BackupDataOutput} |
| 197 | * <code>data</code> stream will be transmitted |
| 198 | * over the current backup transport and stored in the remote data set under |
| 199 | * the key supplied as part of the entity. Writing an entity with a negative |
| 200 | * data size instructs the transport to delete whatever entity currently exists |
| 201 | * under that key from the remote data set. |
Elliott Hughes | 34385d3 | 2014-04-28 11:11:32 -0700 | [diff] [blame] | 202 | * |
Kenny Root | 5a20ea1 | 2010-02-23 18:49:11 -0800 | [diff] [blame] | 203 | * @param oldState An open, read-only ParcelFileDescriptor pointing to the |
| 204 | * last backup state provided by the application. May be |
| 205 | * <code>null</code>, in which case no prior state is being |
| 206 | * provided and the application should perform a full backup. |
| 207 | * @param data A structured wrapper around an open, read/write |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 208 | * file descriptor pointing to the backup data destination. |
Kenny Root | 5a20ea1 | 2010-02-23 18:49:11 -0800 | [diff] [blame] | 209 | * Typically the application will use backup helper classes to |
| 210 | * write to this file. |
| 211 | * @param newState An open, read/write ParcelFileDescriptor pointing to an |
| 212 | * empty file. The application should record the final backup |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 213 | * state here after writing the requested data to the <code>data</code> |
| 214 | * output stream. |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 215 | */ |
Joe Onorato | 290bb01 | 2009-05-13 18:57:29 -0400 | [diff] [blame] | 216 | public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, |
Christopher Tate | 2e40d11 | 2014-07-15 12:37:38 -0700 | [diff] [blame] | 217 | ParcelFileDescriptor newState) throws IOException; |
Kenny Root | 5a20ea1 | 2010-02-23 18:49:11 -0800 | [diff] [blame] | 218 | |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 219 | /** |
Kenny Root | 5a20ea1 | 2010-02-23 18:49:11 -0800 | [diff] [blame] | 220 | * The application is being restored from backup and should replace any |
| 221 | * existing data with the contents of the backup. The backup data is |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 222 | * provided through the <code>data</code> parameter. Once |
Kenny Root | 5a20ea1 | 2010-02-23 18:49:11 -0800 | [diff] [blame] | 223 | * the restore is finished, the application should write a representation of |
| 224 | * the final state to the <code>newState</code> file descriptor. |
| 225 | * <p> |
| 226 | * The application is responsible for properly erasing its old data and |
| 227 | * replacing it with the data supplied to this method. No "clear user data" |
| 228 | * operation will be performed automatically by the operating system. The |
| 229 | * exception to this is in the case of a failed restore attempt: if |
| 230 | * onRestore() throws an exception, the OS will assume that the |
| 231 | * application's data may now be in an incoherent state, and will clear it |
| 232 | * before proceeding. |
Elliott Hughes | 34385d3 | 2014-04-28 11:11:32 -0700 | [diff] [blame] | 233 | * |
Kenny Root | 5a20ea1 | 2010-02-23 18:49:11 -0800 | [diff] [blame] | 234 | * @param data A structured wrapper around an open, read-only |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 235 | * file descriptor pointing to a full snapshot of the |
| 236 | * application's data. The application should consume every |
| 237 | * entity represented in this data stream. |
Scott Main | b83a283 | 2010-04-29 13:26:53 -0700 | [diff] [blame] | 238 | * @param appVersionCode The value of the <a |
| 239 | * href="{@docRoot}guide/topics/manifest/manifest-element.html#vcode">{@code |
| 240 | * android:versionCode}</a> manifest attribute, |
| 241 | * from the application that backed up this particular data set. This |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 242 | * makes it possible for an application's agent to distinguish among any |
Kenny Root | 5a20ea1 | 2010-02-23 18:49:11 -0800 | [diff] [blame] | 243 | * possible older data versions when asked to perform the restore |
| 244 | * operation. |
| 245 | * @param newState An open, read/write ParcelFileDescriptor pointing to an |
| 246 | * empty file. The application should record the final backup |
Christopher Tate | 4e14a82 | 2010-04-08 12:54:23 -0700 | [diff] [blame] | 247 | * state here after restoring its data from the <code>data</code> stream. |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 248 | * When a full-backup dataset is being restored, this will be <code>null</code>. |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 249 | */ |
Christopher Tate | 5cbbf56 | 2009-06-22 16:44:51 -0700 | [diff] [blame] | 250 | public abstract void onRestore(BackupDataInput data, int appVersionCode, |
Christopher Tate | 2e40d11 | 2014-07-15 12:37:38 -0700 | [diff] [blame] | 251 | ParcelFileDescriptor newState) throws IOException; |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 252 | |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 253 | /** |
Christopher Tate | a7835b6 | 2014-07-11 17:25:57 -0700 | [diff] [blame] | 254 | * The application is having its entire file system contents backed up. {@code data} |
| 255 | * points to the backup destination, and the app has the opportunity to choose which |
| 256 | * files are to be stored. To commit a file as part of the backup, call the |
| 257 | * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file |
| 258 | * data is written to the output, the agent returns from this method and the backup |
| 259 | * operation concludes. |
| 260 | * |
| 261 | * <p>Certain parts of the app's data are never backed up even if the app explicitly |
| 262 | * sends them to the output: |
| 263 | * |
| 264 | * <ul> |
| 265 | * <li>The contents of the {@link #getCacheDir()} directory</li> |
Christopher Tate | a8a739f | 2015-03-05 18:31:38 -0800 | [diff] [blame] | 266 | * <li>The contents of the {@link #getCodeCacheDir()} directory</li> |
Christopher Tate | a7835b6 | 2014-07-11 17:25:57 -0700 | [diff] [blame] | 267 | * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li> |
| 268 | * <li>The contents of the app's shared library directory</li> |
| 269 | * </ul> |
| 270 | * |
| 271 | * <p>The default implementation of this method backs up the entirety of the |
| 272 | * application's "owned" file system trees to the output other than the few exceptions |
| 273 | * listed above. Apps only need to override this method if they need to impose special |
| 274 | * limitations on which files are being stored beyond the control that |
| 275 | * {@link #getNoBackupFilesDir()} offers. |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 276 | * Alternatively they can provide an xml resource to specify what data to include or exclude. |
| 277 | * |
Christopher Tate | a7835b6 | 2014-07-11 17:25:57 -0700 | [diff] [blame] | 278 | * |
| 279 | * @param data A structured wrapper pointing to the backup destination. |
| 280 | * @throws IOException |
| 281 | * |
| 282 | * @see Context#getNoBackupFilesDir() |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 283 | * @see ApplicationInfo#fullBackupContent |
Christopher Tate | a7835b6 | 2014-07-11 17:25:57 -0700 | [diff] [blame] | 284 | * @see #fullBackupFile(File, FullBackupDataOutput) |
| 285 | * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 286 | */ |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 287 | public void onFullBackup(FullBackupDataOutput data) throws IOException { |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 288 | FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this); |
| 289 | if (!backupScheme.isFullBackupContentEnabled()) { |
| 290 | return; |
| 291 | } |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 292 | |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 293 | Map<String, Set<String>> manifestIncludeMap; |
| 294 | ArraySet<String> manifestExcludeSet; |
| 295 | try { |
| 296 | manifestIncludeMap = |
| 297 | backupScheme.maybeParseAndGetCanonicalIncludePaths(); |
| 298 | manifestExcludeSet = backupScheme.maybeParseAndGetCanonicalExcludePaths(); |
| 299 | } catch (IOException | XmlPullParserException e) { |
| 300 | if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { |
| 301 | Log.v(FullBackup.TAG_XML_PARSER, |
| 302 | "Exception trying to parse fullBackupContent xml file!" |
| 303 | + " Aborting full backup.", e); |
| 304 | } |
| 305 | return; |
| 306 | } |
| 307 | |
| 308 | final String packageName = getPackageName(); |
| 309 | final ApplicationInfo appInfo = getApplicationInfo(); |
| 310 | |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 311 | // System apps have control over where their default storage context |
| 312 | // is pointed, so we're always explicit when building paths. |
Jeff Sharkey | 8a372a0 | 2016-03-16 16:25:45 -0600 | [diff] [blame] | 313 | final Context ceContext = createCredentialProtectedStorageContext(); |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 314 | final String rootDir = ceContext.getDataDir().getCanonicalPath(); |
| 315 | final String filesDir = ceContext.getFilesDir().getCanonicalPath(); |
| 316 | final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath(); |
| 317 | final String databaseDir = ceContext.getDatabasePath("foo").getParentFile() |
| 318 | .getCanonicalPath(); |
| 319 | final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile() |
| 320 | .getCanonicalPath(); |
| 321 | final String cacheDir = ceContext.getCacheDir().getCanonicalPath(); |
| 322 | final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath(); |
| 323 | |
Jeff Sharkey | 8a372a0 | 2016-03-16 16:25:45 -0600 | [diff] [blame] | 324 | final Context deContext = createDeviceProtectedStorageContext(); |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 325 | final String deviceRootDir = deContext.getDataDir().getCanonicalPath(); |
| 326 | final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath(); |
| 327 | final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath(); |
| 328 | final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile() |
| 329 | .getCanonicalPath(); |
| 330 | final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo") |
| 331 | .getParentFile().getCanonicalPath(); |
| 332 | final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath(); |
| 333 | final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath(); |
| 334 | |
| 335 | final String libDir = (appInfo.nativeLibraryDir != null) |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 336 | ? new File(appInfo.nativeLibraryDir).getCanonicalPath() |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 337 | : null; |
| 338 | |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 339 | // Maintain a set of excluded directories so that as we traverse the tree we know we're not |
| 340 | // going places we don't expect, and so the manifest includes can't take precedence over |
| 341 | // what the framework decides is not to be included. |
| 342 | final ArraySet<String> traversalExcludeSet = new ArraySet<String>(); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 343 | |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 344 | // Add the directories we always exclude. |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 345 | traversalExcludeSet.add(filesDir); |
| 346 | traversalExcludeSet.add(noBackupDir); |
| 347 | traversalExcludeSet.add(databaseDir); |
| 348 | traversalExcludeSet.add(sharedPrefsDir); |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 349 | traversalExcludeSet.add(cacheDir); |
| 350 | traversalExcludeSet.add(codeCacheDir); |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 351 | |
| 352 | traversalExcludeSet.add(deviceFilesDir); |
| 353 | traversalExcludeSet.add(deviceNoBackupDir); |
| 354 | traversalExcludeSet.add(deviceDatabaseDir); |
| 355 | traversalExcludeSet.add(deviceSharedPrefsDir); |
| 356 | traversalExcludeSet.add(deviceCacheDir); |
| 357 | traversalExcludeSet.add(deviceCodeCacheDir); |
| 358 | |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 359 | if (libDir != null) { |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 360 | traversalExcludeSet.add(libDir); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 361 | } |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 362 | |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 363 | // Root dir first. |
| 364 | applyXmlFiltersAndDoFullBackupForDomain( |
| 365 | packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap, |
| 366 | manifestExcludeSet, traversalExcludeSet, data); |
| 367 | traversalExcludeSet.add(rootDir); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 368 | |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 369 | applyXmlFiltersAndDoFullBackupForDomain( |
| 370 | packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap, |
| 371 | manifestExcludeSet, traversalExcludeSet, data); |
| 372 | traversalExcludeSet.add(deviceRootDir); |
| 373 | |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 374 | // Data dir next. |
| 375 | traversalExcludeSet.remove(filesDir); |
| 376 | applyXmlFiltersAndDoFullBackupForDomain( |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 377 | packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap, |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 378 | manifestExcludeSet, traversalExcludeSet, data); |
| 379 | traversalExcludeSet.add(filesDir); |
| 380 | |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 381 | traversalExcludeSet.remove(deviceFilesDir); |
| 382 | applyXmlFiltersAndDoFullBackupForDomain( |
| 383 | packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap, |
| 384 | manifestExcludeSet, traversalExcludeSet, data); |
| 385 | traversalExcludeSet.add(deviceFilesDir); |
| 386 | |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 387 | // Database directory. |
| 388 | traversalExcludeSet.remove(databaseDir); |
| 389 | applyXmlFiltersAndDoFullBackupForDomain( |
| 390 | packageName, FullBackup.DATABASE_TREE_TOKEN, manifestIncludeMap, |
| 391 | manifestExcludeSet, traversalExcludeSet, data); |
| 392 | traversalExcludeSet.add(databaseDir); |
| 393 | |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 394 | traversalExcludeSet.remove(deviceDatabaseDir); |
| 395 | applyXmlFiltersAndDoFullBackupForDomain( |
| 396 | packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap, |
| 397 | manifestExcludeSet, traversalExcludeSet, data); |
| 398 | traversalExcludeSet.add(deviceDatabaseDir); |
| 399 | |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 400 | // SharedPrefs. |
| 401 | traversalExcludeSet.remove(sharedPrefsDir); |
| 402 | applyXmlFiltersAndDoFullBackupForDomain( |
| 403 | packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, manifestIncludeMap, |
| 404 | manifestExcludeSet, traversalExcludeSet, data); |
| 405 | traversalExcludeSet.add(sharedPrefsDir); |
Christopher Tate | 416c39e | 2013-02-14 16:55:46 -0800 | [diff] [blame] | 406 | |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 407 | traversalExcludeSet.remove(deviceSharedPrefsDir); |
| 408 | applyXmlFiltersAndDoFullBackupForDomain( |
| 409 | packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap, |
| 410 | manifestExcludeSet, traversalExcludeSet, data); |
| 411 | traversalExcludeSet.add(deviceSharedPrefsDir); |
| 412 | |
Christopher Tate | 416c39e | 2013-02-14 16:55:46 -0800 | [diff] [blame] | 413 | // getExternalFilesDir() location associated with this app. Technically there should |
| 414 | // not be any files here if the app does not properly have permission to access |
| 415 | // external storage, but edge cases happen. fullBackupFileTree() catches |
Christopher Tate | 5cb5c33 | 2013-02-21 14:32:12 -0800 | [diff] [blame] | 416 | // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and |
| 417 | // we know a priori that processes running as the system UID are not permitted to |
| 418 | // access external storage, so we check for that as well to avoid nastygrams in |
| 419 | // the log. |
| 420 | if (Process.myUid() != Process.SYSTEM_UID) { |
| 421 | File efLocation = getExternalFilesDir(null); |
| 422 | if (efLocation != null) { |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 423 | applyXmlFiltersAndDoFullBackupForDomain( |
| 424 | packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN, manifestIncludeMap, |
| 425 | manifestExcludeSet, traversalExcludeSet, data); |
| 426 | } |
| 427 | |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | /** |
Christopher Tate | d43879c | 2016-04-04 12:54:43 -0700 | [diff] [blame] | 432 | * Notification that the application's current backup operation causes it to exceed |
| 433 | * the maximum size permitted by the transport. The ongoing backup operation is |
| 434 | * halted and rolled back: any data that had been stored by a previous backup operation |
| 435 | * is still intact. Typically the quota-exceeded state will be detected before any data |
| 436 | * is actually transmitted over the network. |
Sergey Poromov | 872d3b6 | 2016-01-12 15:48:08 +0100 | [diff] [blame] | 437 | * |
Christopher Tate | d43879c | 2016-04-04 12:54:43 -0700 | [diff] [blame] | 438 | * <p>The {@code quotaBytes} value is the total data size currently permitted for this |
| 439 | * application. If desired, the application can use this as a hint for determining |
| 440 | * how much data to store. For example, a messaging application might choose to |
| 441 | * store only the newest messages, dropping enough older content to stay under |
| 442 | * the quota. |
| 443 | * |
| 444 | * <p class="note">Note that the maximum quota for the application can change over |
| 445 | * time. In particular, in the future the quota may grow. Applications that adapt |
| 446 | * to the quota when deciding what data to store should be aware of this and implement |
| 447 | * their data storage mechanisms in a way that can take advantage of additional |
| 448 | * quota. |
| 449 | * |
| 450 | * @param backupDataBytes The amount of data measured while initializing the backup |
| 451 | * operation, if the total exceeds the app's alloted quota. If initial measurement |
| 452 | * suggested that the data would fit but then too much data was actually submitted |
| 453 | * as part of the operation, then this value is the amount of data that had been |
| 454 | * streamed into the transport at the time the quota was reached. |
| 455 | * @param quotaBytes The maximum data size that the transport currently permits |
| 456 | * this application to store as a backup. |
Sergey Poromov | 872d3b6 | 2016-01-12 15:48:08 +0100 | [diff] [blame] | 457 | */ |
| 458 | public void onQuotaExceeded(long backupDataBytes, long quotaBytes) { |
| 459 | } |
| 460 | |
| 461 | /** |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 462 | * Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>. |
| 463 | * If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path |
| 464 | * is a directory. |
| 465 | */ |
| 466 | private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken, |
| 467 | Map<String, Set<String>> includeMap, |
| 468 | ArraySet<String> filterSet, |
| 469 | ArraySet<String> traversalExcludeSet, |
| 470 | FullBackupDataOutput data) |
| 471 | throws IOException { |
| 472 | if (includeMap == null || includeMap.size() == 0) { |
| 473 | // Do entire sub-tree for the provided token. |
| 474 | fullBackupFileTree(packageName, domainToken, |
| 475 | FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken), |
| 476 | filterSet, traversalExcludeSet, data); |
| 477 | } else if (includeMap.get(domainToken) != null) { |
| 478 | // This will be null if the xml parsing didn't yield any rules for |
| 479 | // this domain (there may still be rules for other domains). |
| 480 | for (String includeFile : includeMap.get(domainToken)) { |
| 481 | fullBackupFileTree(packageName, domainToken, includeFile, filterSet, |
| 482 | traversalExcludeSet, data); |
Christopher Tate | 5cb5c33 | 2013-02-21 14:32:12 -0800 | [diff] [blame] | 483 | } |
| 484 | } |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 485 | } |
| 486 | |
| 487 | /** |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 488 | * Write an entire file as part of a full-backup operation. The file's contents |
| 489 | * will be delivered to the backup destination along with the metadata necessary |
| 490 | * to place it with the proper location and permissions on the device where the |
| 491 | * data is restored. |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 492 | * |
Christopher Tate | c5be8f8 | 2016-04-25 14:41:50 -0700 | [diff] [blame] | 493 | * <p class="note">Attempting to back up files in directories that are ignored by |
| 494 | * the backup system will have no effect. For example, if the app calls this method |
| 495 | * with a file inside the {@link #getNoBackupFilesDir()} directory, it will be ignored. |
Elliot Waite | 54de7747 | 2017-01-11 15:30:35 -0800 | [diff] [blame] | 496 | * See {@link #onFullBackup(FullBackupDataOutput)} for details on what directories |
Christopher Tate | c5be8f8 | 2016-04-25 14:41:50 -0700 | [diff] [blame] | 497 | * are excluded from backups. |
Christopher Tate | a7835b6 | 2014-07-11 17:25:57 -0700 | [diff] [blame] | 498 | * |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 499 | * @param file The file to be backed up. The file must exist and be readable by |
| 500 | * the caller. |
| 501 | * @param output The destination to which the backed-up file data will be sent. |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 502 | */ |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 503 | public final void fullBackupFile(File file, FullBackupDataOutput output) { |
| 504 | // Look up where all of our various well-defined dir trees live on this device |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 505 | final String rootDir; |
| 506 | final String filesDir; |
| 507 | final String nbFilesDir; |
| 508 | final String dbDir; |
| 509 | final String spDir; |
| 510 | final String cacheDir; |
| 511 | final String codeCacheDir; |
| 512 | final String deviceRootDir; |
| 513 | final String deviceFilesDir; |
| 514 | final String deviceNbFilesDir; |
| 515 | final String deviceDbDir; |
| 516 | final String deviceSpDir; |
| 517 | final String deviceCacheDir; |
| 518 | final String deviceCodeCacheDir; |
| 519 | final String libDir; |
| 520 | |
Christopher Tate | 5cb5c33 | 2013-02-21 14:32:12 -0800 | [diff] [blame] | 521 | String efDir = null; |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 522 | String filePath; |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 523 | |
| 524 | ApplicationInfo appInfo = getApplicationInfo(); |
| 525 | |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 526 | try { |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 527 | // System apps have control over where their default storage context |
| 528 | // is pointed, so we're always explicit when building paths. |
Jeff Sharkey | 8a372a0 | 2016-03-16 16:25:45 -0600 | [diff] [blame] | 529 | final Context ceContext = createCredentialProtectedStorageContext(); |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 530 | rootDir = ceContext.getDataDir().getCanonicalPath(); |
| 531 | filesDir = ceContext.getFilesDir().getCanonicalPath(); |
| 532 | nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath(); |
| 533 | dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath(); |
| 534 | spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath(); |
| 535 | cacheDir = ceContext.getCacheDir().getCanonicalPath(); |
| 536 | codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath(); |
| 537 | |
Jeff Sharkey | 8a372a0 | 2016-03-16 16:25:45 -0600 | [diff] [blame] | 538 | final Context deContext = createDeviceProtectedStorageContext(); |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 539 | deviceRootDir = deContext.getDataDir().getCanonicalPath(); |
| 540 | deviceFilesDir = deContext.getFilesDir().getCanonicalPath(); |
| 541 | deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath(); |
| 542 | deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath(); |
| 543 | deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile() |
| 544 | .getCanonicalPath(); |
| 545 | deviceCacheDir = deContext.getCacheDir().getCanonicalPath(); |
| 546 | deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath(); |
| 547 | |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 548 | libDir = (appInfo.nativeLibraryDir == null) |
| 549 | ? null |
| 550 | : new File(appInfo.nativeLibraryDir).getCanonicalPath(); |
Christopher Tate | 5cb5c33 | 2013-02-21 14:32:12 -0800 | [diff] [blame] | 551 | |
| 552 | // may or may not have external files access to attempt backup/restore there |
| 553 | if (Process.myUid() != Process.SYSTEM_UID) { |
| 554 | File efLocation = getExternalFilesDir(null); |
| 555 | if (efLocation != null) { |
| 556 | efDir = efLocation.getCanonicalPath(); |
| 557 | } |
| 558 | } |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 559 | |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 560 | // Now figure out which well-defined tree the file is placed in, working from |
Christopher Tate | a8a739f | 2015-03-05 18:31:38 -0800 | [diff] [blame] | 561 | // most to least specific. We also specifically exclude the lib, cache, |
| 562 | // and code_cache dirs. |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 563 | filePath = file.getCanonicalPath(); |
| 564 | } catch (IOException e) { |
| 565 | Log.w(TAG, "Unable to obtain canonical paths"); |
| 566 | return; |
| 567 | } |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 568 | |
Christopher Tate | a7835b6 | 2014-07-11 17:25:57 -0700 | [diff] [blame] | 569 | if (filePath.startsWith(cacheDir) |
Christopher Tate | a8a739f | 2015-03-05 18:31:38 -0800 | [diff] [blame] | 570 | || filePath.startsWith(codeCacheDir) |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 571 | || filePath.startsWith(nbFilesDir) |
| 572 | || filePath.startsWith(deviceCacheDir) |
| 573 | || filePath.startsWith(deviceCodeCacheDir) |
| 574 | || filePath.startsWith(deviceNbFilesDir) |
| 575 | || filePath.startsWith(libDir)) { |
Christopher Tate | a8a739f | 2015-03-05 18:31:38 -0800 | [diff] [blame] | 576 | Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up"); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 577 | return; |
| 578 | } |
| 579 | |
| 580 | final String domain; |
| 581 | String rootpath = null; |
| 582 | if (filePath.startsWith(dbDir)) { |
| 583 | domain = FullBackup.DATABASE_TREE_TOKEN; |
| 584 | rootpath = dbDir; |
| 585 | } else if (filePath.startsWith(spDir)) { |
| 586 | domain = FullBackup.SHAREDPREFS_TREE_TOKEN; |
| 587 | rootpath = spDir; |
| 588 | } else if (filePath.startsWith(filesDir)) { |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 589 | domain = FullBackup.FILES_TREE_TOKEN; |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 590 | rootpath = filesDir; |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 591 | } else if (filePath.startsWith(rootDir)) { |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 592 | domain = FullBackup.ROOT_TREE_TOKEN; |
Jeff Sharkey | 2c1ba9a | 2016-02-17 15:29:38 -0700 | [diff] [blame] | 593 | rootpath = rootDir; |
| 594 | } else if (filePath.startsWith(deviceDbDir)) { |
| 595 | domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN; |
| 596 | rootpath = deviceDbDir; |
| 597 | } else if (filePath.startsWith(deviceSpDir)) { |
| 598 | domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN; |
| 599 | rootpath = deviceSpDir; |
| 600 | } else if (filePath.startsWith(deviceFilesDir)) { |
| 601 | domain = FullBackup.DEVICE_FILES_TREE_TOKEN; |
| 602 | rootpath = deviceFilesDir; |
| 603 | } else if (filePath.startsWith(deviceRootDir)) { |
| 604 | domain = FullBackup.DEVICE_ROOT_TREE_TOKEN; |
| 605 | rootpath = deviceRootDir; |
Christopher Tate | 5cb5c33 | 2013-02-21 14:32:12 -0800 | [diff] [blame] | 606 | } else if ((efDir != null) && filePath.startsWith(efDir)) { |
Christopher Tate | 416c39e | 2013-02-14 16:55:46 -0800 | [diff] [blame] | 607 | domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN; |
| 608 | rootpath = efDir; |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 609 | } else { |
| 610 | Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping"); |
| 611 | return; |
| 612 | } |
| 613 | |
| 614 | // And now that we know where it lives, semantically, back it up appropriately |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 615 | // In the measurement case, backupToTar() updates the size in output and returns |
| 616 | // without transmitting any file data. |
| 617 | if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 618 | + " rootpath=" + rootpath); |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 619 | |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 620 | FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 621 | } |
| 622 | |
| 623 | /** |
| 624 | * Scan the dir tree (if it actually exists) and process each entry we find. If the |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 625 | * 'excludes' parameters are non-null, they are consulted each time a new file system entity |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 626 | * is visited to see whether that entity (and its subtree, if appropriate) should be |
| 627 | * omitted from the backup process. |
| 628 | * |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 629 | * @param systemExcludes An optional list of excludes. |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 630 | * @hide |
| 631 | */ |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 632 | protected final void fullBackupFileTree(String packageName, String domain, String startingPath, |
| 633 | ArraySet<String> manifestExcludes, |
| 634 | ArraySet<String> systemExcludes, |
| 635 | FullBackupDataOutput output) { |
| 636 | // Pull out the domain and set it aside to use when making the tarball. |
| 637 | String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain); |
| 638 | if (domainPath == null) { |
| 639 | // Should never happen. |
| 640 | return; |
| 641 | } |
| 642 | |
| 643 | File rootFile = new File(startingPath); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 644 | if (rootFile.exists()) { |
| 645 | LinkedList<File> scanQueue = new LinkedList<File>(); |
| 646 | scanQueue.add(rootFile); |
| 647 | |
| 648 | while (scanQueue.size() > 0) { |
| 649 | File file = scanQueue.remove(0); |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 650 | String filePath; |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 651 | try { |
Christopher Tate | da2018e | 2016-10-13 12:12:29 -0700 | [diff] [blame] | 652 | // Ignore things that aren't "real" files or dirs |
Christopher Tate | 85192a1 | 2015-09-24 10:21:54 -0700 | [diff] [blame] | 653 | StructStat stat = Os.lstat(file.getPath()); |
Christopher Tate | da2018e | 2016-10-13 12:12:29 -0700 | [diff] [blame] | 654 | if (!OsConstants.S_ISREG(stat.st_mode) |
| 655 | && !OsConstants.S_ISDIR(stat.st_mode)) { |
| 656 | if (DEBUG) Log.i(TAG, "Not a file/dir (skipping)!: " + file); |
Christopher Tate | 85192a1 | 2015-09-24 10:21:54 -0700 | [diff] [blame] | 657 | continue; |
| 658 | } |
| 659 | |
| 660 | // For all other verification, look at the canonicalized path |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 661 | filePath = file.getCanonicalPath(); |
| 662 | |
| 663 | // prune this subtree? |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 664 | if (manifestExcludes != null && manifestExcludes.contains(filePath)) { |
| 665 | continue; |
| 666 | } |
| 667 | if (systemExcludes != null && systemExcludes.contains(filePath)) { |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 668 | continue; |
| 669 | } |
| 670 | |
| 671 | // If it's a directory, enqueue its contents for scanning. |
Christopher Tate | 85192a1 | 2015-09-24 10:21:54 -0700 | [diff] [blame] | 672 | if (OsConstants.S_ISDIR(stat.st_mode)) { |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 673 | File[] contents = file.listFiles(); |
| 674 | if (contents != null) { |
| 675 | for (File entry : contents) { |
| 676 | scanQueue.add(0, entry); |
| 677 | } |
| 678 | } |
| 679 | } |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 680 | } catch (IOException e) { |
| 681 | if (DEBUG) Log.w(TAG, "Error canonicalizing path of " + file); |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 682 | if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { |
| 683 | Log.v(FullBackup.TAG_XML_PARSER, "Error canonicalizing path of " + file); |
| 684 | } |
Christopher Tate | 2efd2db | 2011-07-19 16:32:49 -0700 | [diff] [blame] | 685 | continue; |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 686 | } catch (ErrnoException e) { |
| 687 | if (DEBUG) Log.w(TAG, "Error scanning file " + file + " : " + e); |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 688 | if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { |
| 689 | Log.v(FullBackup.TAG_XML_PARSER, "Error scanning file " + file + " : " + e); |
| 690 | } |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 691 | continue; |
| 692 | } |
| 693 | |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 694 | // Finally, back this file up (or measure it) before proceeding |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 695 | FullBackup.backupToTar(packageName, domain, null, domainPath, filePath, output); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 696 | } |
| 697 | } |
| 698 | } |
| 699 | |
| 700 | /** |
| 701 | * Handle the data delivered via the given file descriptor during a full restore |
| 702 | * operation. The agent is given the path to the file's original location as well |
| 703 | * as its size and metadata. |
| 704 | * <p> |
| 705 | * The file descriptor can only be read for {@code size} bytes; attempting to read |
| 706 | * more data has undefined behavior. |
| 707 | * <p> |
| 708 | * The default implementation creates the destination file/directory and populates it |
| 709 | * with the data from the file descriptor, then sets the file's access mode and |
| 710 | * modification time to match the restore arguments. |
| 711 | * |
| 712 | * @param data A read-only file descriptor from which the agent can read {@code size} |
| 713 | * bytes of file data. |
| 714 | * @param size The number of bytes of file content to be restored to the given |
| 715 | * destination. If the file system object being restored is a directory, {@code size} |
| 716 | * will be zero. |
| 717 | * @param destination The File on disk to be restored with the given data. |
| 718 | * @param type The kind of file system object being restored. This will be either |
| 719 | * {@link BackupAgent#TYPE_FILE} or {@link BackupAgent#TYPE_DIRECTORY}. |
| 720 | * @param mode The access mode to be assigned to the destination after its data is |
| 721 | * written. This is in the standard format used by {@code chmod()}. |
| 722 | * @param mtime The modification time of the file when it was backed up, suitable to |
| 723 | * be assigned to the file after its data is written. |
| 724 | * @throws IOException |
| 725 | */ |
| 726 | public void onRestoreFile(ParcelFileDescriptor data, long size, |
| 727 | File destination, int type, long mode, long mtime) |
| 728 | throws IOException { |
Matthew Williams | b9ebed5 | 2015-08-05 18:27:44 -0700 | [diff] [blame] | 729 | |
| 730 | final boolean accept = isFileEligibleForRestore(destination); |
| 731 | // If we don't accept the file, consume the bytes from the pipe anyway. |
| 732 | FullBackup.restoreFile(data, size, type, mode, mtime, accept ? destination : null); |
| 733 | } |
| 734 | |
| 735 | private boolean isFileEligibleForRestore(File destination) throws IOException { |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 736 | FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this); |
| 737 | if (!bs.isFullBackupContentEnabled()) { |
| 738 | if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { |
| 739 | Log.v(FullBackup.TAG_XML_PARSER, |
| 740 | "onRestoreFile \"" + destination.getCanonicalPath() |
| 741 | + "\" : fullBackupContent not enabled for " + getPackageName()); |
| 742 | } |
Matthew Williams | b9ebed5 | 2015-08-05 18:27:44 -0700 | [diff] [blame] | 743 | return false; |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 744 | } |
Matthew Williams | b9ebed5 | 2015-08-05 18:27:44 -0700 | [diff] [blame] | 745 | |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 746 | Map<String, Set<String>> includes = null; |
| 747 | ArraySet<String> excludes = null; |
| 748 | final String destinationCanonicalPath = destination.getCanonicalPath(); |
| 749 | try { |
| 750 | includes = bs.maybeParseAndGetCanonicalIncludePaths(); |
| 751 | excludes = bs.maybeParseAndGetCanonicalExcludePaths(); |
| 752 | } catch (XmlPullParserException e) { |
| 753 | if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { |
| 754 | Log.v(FullBackup.TAG_XML_PARSER, |
| 755 | "onRestoreFile \"" + destinationCanonicalPath |
| 756 | + "\" : Exception trying to parse fullBackupContent xml file!" |
| 757 | + " Aborting onRestoreFile.", e); |
| 758 | } |
Matthew Williams | b9ebed5 | 2015-08-05 18:27:44 -0700 | [diff] [blame] | 759 | return false; |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 760 | } |
| 761 | |
| 762 | if (excludes != null && |
| 763 | isFileSpecifiedInPathList(destination, excludes)) { |
| 764 | if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { |
| 765 | Log.v(FullBackup.TAG_XML_PARSER, |
| 766 | "onRestoreFile: \"" + destinationCanonicalPath + "\": listed in" |
| 767 | + " excludes; skipping."); |
| 768 | } |
Matthew Williams | b9ebed5 | 2015-08-05 18:27:44 -0700 | [diff] [blame] | 769 | return false; |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 770 | } |
| 771 | |
| 772 | if (includes != null && !includes.isEmpty()) { |
| 773 | // Rather than figure out the <include/> domain based on the path (a lot of code, and |
| 774 | // it's a small list), we'll go through and look for it. |
| 775 | boolean explicitlyIncluded = false; |
| 776 | for (Set<String> domainIncludes : includes.values()) { |
| 777 | explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes); |
| 778 | if (explicitlyIncluded) { |
| 779 | break; |
| 780 | } |
| 781 | } |
| 782 | if (!explicitlyIncluded) { |
| 783 | if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) { |
| 784 | Log.v(FullBackup.TAG_XML_PARSER, |
| 785 | "onRestoreFile: Trying to restore \"" |
| 786 | + destinationCanonicalPath + "\" but it isn't specified" |
| 787 | + " in the included files; skipping."); |
| 788 | } |
Matthew Williams | b9ebed5 | 2015-08-05 18:27:44 -0700 | [diff] [blame] | 789 | return false; |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 790 | } |
| 791 | } |
Matthew Williams | b9ebed5 | 2015-08-05 18:27:44 -0700 | [diff] [blame] | 792 | return true; |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 793 | } |
| 794 | |
| 795 | /** |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 796 | * @return True if the provided file is either directly in the provided list, or the provided |
| 797 | * file is within a directory in the list. |
| 798 | */ |
| 799 | private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList) |
| 800 | throws IOException { |
| 801 | for (String canonicalPath : canonicalPathList) { |
| 802 | File fileFromList = new File(canonicalPath); |
| 803 | if (fileFromList.isDirectory()) { |
| 804 | if (file.isDirectory()) { |
| 805 | // If they are both directories check exact equals. |
| 806 | return file.equals(fileFromList); |
| 807 | } else { |
| 808 | // O/w we have to check if the file is within the directory from the list. |
| 809 | return file.getCanonicalPath().startsWith(canonicalPath); |
| 810 | } |
| 811 | } else { |
| 812 | if (file.equals(fileFromList)) { |
| 813 | // Need to check the explicit "equals" so we don't end up with substrings. |
| 814 | return true; |
| 815 | } |
| 816 | } |
| 817 | } |
| 818 | return false; |
| 819 | } |
| 820 | |
| 821 | /** |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 822 | * Only specialized platform agents should overload this entry point to support |
| 823 | * restores to crazy non-app locations. |
| 824 | * @hide |
| 825 | */ |
| 826 | protected void onRestoreFile(ParcelFileDescriptor data, long size, |
| 827 | int type, String domain, String path, long mode, long mtime) |
| 828 | throws IOException { |
| 829 | String basePath = null; |
| 830 | |
| 831 | if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type |
| 832 | + " domain=" + domain + " relpath=" + path + " mode=" + mode |
| 833 | + " mtime=" + mtime); |
| 834 | |
Matthew Williams | 303650c | 2015-04-17 18:22:51 -0700 | [diff] [blame] | 835 | basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain); |
| 836 | if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) { |
| 837 | mode = -1; // < 0 is a token to skip attempting a chmod() |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 838 | } |
| 839 | |
| 840 | // Now that we've figured out where the data goes, send it on its way |
| 841 | if (basePath != null) { |
Christopher Tate | 7323765 | 2013-03-25 10:06:34 -0700 | [diff] [blame] | 842 | // Canonicalize the nominal path and verify that it lies within the stated domain |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 843 | File outFile = new File(basePath, path); |
Christopher Tate | 7323765 | 2013-03-25 10:06:34 -0700 | [diff] [blame] | 844 | String outPath = outFile.getCanonicalPath(); |
| 845 | if (outPath.startsWith(basePath + File.separatorChar)) { |
| 846 | if (DEBUG) Log.i(TAG, "[" + domain + " : " + path + "] mapped to " + outPath); |
| 847 | onRestoreFile(data, size, outFile, type, mode, mtime); |
| 848 | return; |
| 849 | } else { |
| 850 | // Attempt to restore to a path outside the file's nominal domain. |
| 851 | if (DEBUG) { |
| 852 | Log.e(TAG, "Cross-domain restore attempt: " + outPath); |
| 853 | } |
| 854 | } |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 855 | } |
Christopher Tate | 7323765 | 2013-03-25 10:06:34 -0700 | [diff] [blame] | 856 | |
| 857 | // Not a supported output location, or bad path: we need to consume the data |
| 858 | // anyway, so just use the default "copy the data out" implementation |
| 859 | // with a null destination. |
| 860 | if (DEBUG) Log.i(TAG, "[ skipping file " + path + "]"); |
| 861 | FullBackup.restoreFile(data, size, type, mode, mtime, null); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 862 | } |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 863 | |
Christopher Tate | 2e40d11 | 2014-07-15 12:37:38 -0700 | [diff] [blame] | 864 | /** |
| 865 | * The application's restore operation has completed. This method is called after |
| 866 | * all available data has been delivered to the application for restore (via either |
| 867 | * the {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} or |
| 868 | * {@link #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) onRestoreFile()} |
| 869 | * callbacks). This provides the app with a stable end-of-restore opportunity to |
| 870 | * perform any appropriate post-processing on the data that was just delivered. |
| 871 | * |
| 872 | * @see #onRestore(BackupDataInput, int, ParcelFileDescriptor) |
| 873 | * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) |
| 874 | */ |
| 875 | public void onRestoreFinished() { |
| 876 | } |
| 877 | |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 878 | // ----- Core implementation ----- |
Christopher Tate | 44a2790 | 2010-01-27 17:15:49 -0800 | [diff] [blame] | 879 | |
| 880 | /** @hide */ |
| 881 | public final IBinder onBind() { |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 882 | return mBinder; |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 883 | } |
| 884 | |
| 885 | private final IBinder mBinder = new BackupServiceBinder().asBinder(); |
| 886 | |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 887 | /** @hide */ |
| 888 | public void attach(Context context) { |
| 889 | attachBaseContext(context); |
| 890 | } |
| 891 | |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 892 | // ----- IBackupService binder interface ----- |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 893 | private class BackupServiceBinder extends IBackupAgent.Stub { |
| 894 | private static final String TAG = "BackupServiceBinder"; |
| 895 | |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 896 | @Override |
Christopher Tate | 22b8787 | 2009-05-04 16:41:53 -0700 | [diff] [blame] | 897 | public void doBackup(ParcelFileDescriptor oldState, |
| 898 | ParcelFileDescriptor data, |
Christopher Tate | 44a2790 | 2010-01-27 17:15:49 -0800 | [diff] [blame] | 899 | ParcelFileDescriptor newState, |
Shreyas Basarge | b6e73c9 | 2017-01-31 20:13:43 +0000 | [diff] [blame] | 900 | long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException { |
Christopher Tate | 1902492 | 2010-01-22 16:39:53 -0800 | [diff] [blame] | 901 | // Ensure that we're running with the app's normal permission level |
Christopher Tate | 44a2790 | 2010-01-27 17:15:49 -0800 | [diff] [blame] | 902 | long ident = Binder.clearCallingIdentity(); |
Christopher Tate | 1902492 | 2010-01-22 16:39:53 -0800 | [diff] [blame] | 903 | |
Christopher Tate | 436344a | 2009-09-30 16:17:37 -0700 | [diff] [blame] | 904 | if (DEBUG) Log.v(TAG, "doBackup() invoked"); |
Christopher Tate | ee87b96 | 2017-04-26 17:07:27 -0700 | [diff] [blame] | 905 | BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor(), quotaBytes); |
Christopher Tate | 4a627c7 | 2011-04-01 14:43:32 -0700 | [diff] [blame] | 906 | |
Joe Onorato | 290bb01 | 2009-05-13 18:57:29 -0400 | [diff] [blame] | 907 | try { |
Christopher Tate | 181fafa | 2009-05-14 11:12:14 -0700 | [diff] [blame] | 908 | BackupAgent.this.onBackup(oldState, output, newState); |
Joe Onorato | 4ababd9 | 2009-06-25 18:29:18 -0400 | [diff] [blame] | 909 | } catch (IOException ex) { |
| 910 | Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); |
| 911 | throw new RuntimeException(ex); |
Joe Onorato | 290bb01 | 2009-05-13 18:57:29 -0400 | [diff] [blame] | 912 | } catch (RuntimeException ex) { |
Joe Onorato | 83248c4 | 2009-06-17 17:55:20 -0700 | [diff] [blame] | 913 | Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); |
Joe Onorato | 290bb01 | 2009-05-13 18:57:29 -0400 | [diff] [blame] | 914 | throw ex; |
Christopher Tate | 1902492 | 2010-01-22 16:39:53 -0800 | [diff] [blame] | 915 | } finally { |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 916 | // Ensure that any SharedPreferences writes have landed after the backup, |
| 917 | // in case the app code has side effects (since apps cannot provide this |
| 918 | // guarantee themselves). |
| 919 | waitForSharedPrefs(); |
| 920 | |
Christopher Tate | 44a2790 | 2010-01-27 17:15:49 -0800 | [diff] [blame] | 921 | Binder.restoreCallingIdentity(ident); |
| 922 | try { |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 923 | callbackBinder.opComplete(token, 0); |
Christopher Tate | 44a2790 | 2010-01-27 17:15:49 -0800 | [diff] [blame] | 924 | } catch (RemoteException e) { |
| 925 | // we'll time out anyway, so we're safe |
| 926 | } |
Christopher Tate | 91bb0e5 | 2016-09-30 17:52:19 -0700 | [diff] [blame] | 927 | |
| 928 | // Don't close the fd out from under the system service if this was local |
| 929 | if (Binder.getCallingPid() != Process.myPid()) { |
| 930 | IoUtils.closeQuietly(oldState); |
| 931 | IoUtils.closeQuietly(data); |
| 932 | IoUtils.closeQuietly(newState); |
| 933 | } |
Joe Onorato | 290bb01 | 2009-05-13 18:57:29 -0400 | [diff] [blame] | 934 | } |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 935 | } |
| 936 | |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 937 | @Override |
Christopher Tate | 5cbbf56 | 2009-06-22 16:44:51 -0700 | [diff] [blame] | 938 | public void doRestore(ParcelFileDescriptor data, int appVersionCode, |
Christopher Tate | 44a2790 | 2010-01-27 17:15:49 -0800 | [diff] [blame] | 939 | ParcelFileDescriptor newState, |
| 940 | int token, IBackupManager callbackBinder) throws RemoteException { |
Christopher Tate | 1902492 | 2010-01-22 16:39:53 -0800 | [diff] [blame] | 941 | // Ensure that we're running with the app's normal permission level |
Christopher Tate | 44a2790 | 2010-01-27 17:15:49 -0800 | [diff] [blame] | 942 | long ident = Binder.clearCallingIdentity(); |
Christopher Tate | 1902492 | 2010-01-22 16:39:53 -0800 | [diff] [blame] | 943 | |
Christopher Tate | 436344a | 2009-09-30 16:17:37 -0700 | [diff] [blame] | 944 | if (DEBUG) Log.v(TAG, "doRestore() invoked"); |
Christopher Tate | fe2368c | 2017-05-17 15:42:35 -0700 | [diff] [blame] | 945 | |
| 946 | // Ensure that any side-effect SharedPreferences writes have landed *before* |
| 947 | // we may be about to rewrite the file out from underneath |
| 948 | waitForSharedPrefs(); |
| 949 | |
Joe Onorato | 83248c4 | 2009-06-17 17:55:20 -0700 | [diff] [blame] | 950 | BackupDataInput input = new BackupDataInput(data.getFileDescriptor()); |
| 951 | try { |
Christopher Tate | 5cbbf56 | 2009-06-22 16:44:51 -0700 | [diff] [blame] | 952 | BackupAgent.this.onRestore(input, appVersionCode, newState); |
Joe Onorato | 83248c4 | 2009-06-17 17:55:20 -0700 | [diff] [blame] | 953 | } catch (IOException ex) { |
| 954 | Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex); |
| 955 | throw new RuntimeException(ex); |
| 956 | } catch (RuntimeException ex) { |
| 957 | Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex); |
| 958 | throw ex; |
Christopher Tate | 1902492 | 2010-01-22 16:39:53 -0800 | [diff] [blame] | 959 | } finally { |
Christopher Tate | fe2368c | 2017-05-17 15:42:35 -0700 | [diff] [blame] | 960 | // And bring live SharedPreferences instances up to date |
| 961 | reloadSharedPreferences(); |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 962 | |
Christopher Tate | 44a2790 | 2010-01-27 17:15:49 -0800 | [diff] [blame] | 963 | Binder.restoreCallingIdentity(ident); |
| 964 | try { |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 965 | callbackBinder.opComplete(token, 0); |
Christopher Tate | 44a2790 | 2010-01-27 17:15:49 -0800 | [diff] [blame] | 966 | } catch (RemoteException e) { |
| 967 | // we'll time out anyway, so we're safe |
| 968 | } |
Christopher Tate | 91bb0e5 | 2016-09-30 17:52:19 -0700 | [diff] [blame] | 969 | |
| 970 | if (Binder.getCallingPid() != Process.myPid()) { |
| 971 | IoUtils.closeQuietly(data); |
| 972 | IoUtils.closeQuietly(newState); |
| 973 | } |
Joe Onorato | 83248c4 | 2009-06-17 17:55:20 -0700 | [diff] [blame] | 974 | } |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 975 | } |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 976 | |
| 977 | @Override |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 978 | public void doFullBackup(ParcelFileDescriptor data, |
Shreyas Basarge | b6e73c9 | 2017-01-31 20:13:43 +0000 | [diff] [blame] | 979 | long quotaBytes, int token, IBackupManager callbackBinder) { |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 980 | // Ensure that we're running with the app's normal permission level |
| 981 | long ident = Binder.clearCallingIdentity(); |
| 982 | |
| 983 | if (DEBUG) Log.v(TAG, "doFullBackup() invoked"); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 984 | |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 985 | // Ensure that any SharedPreferences writes have landed *before* |
| 986 | // we potentially try to back up the underlying files directly. |
| 987 | waitForSharedPrefs(); |
| 988 | |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 989 | try { |
Christopher Tate | ee87b96 | 2017-04-26 17:07:27 -0700 | [diff] [blame] | 990 | BackupAgent.this.onFullBackup(new FullBackupDataOutput(data, quotaBytes)); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 991 | } catch (IOException ex) { |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 992 | Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 993 | throw new RuntimeException(ex); |
| 994 | } catch (RuntimeException ex) { |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 995 | Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 996 | throw ex; |
| 997 | } finally { |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 998 | // ... and then again after, as in the doBackup() case |
| 999 | waitForSharedPrefs(); |
| 1000 | |
Christopher Tate | 7926a69 | 2011-07-11 11:31:57 -0700 | [diff] [blame] | 1001 | // Send the EOD marker indicating that there is no more data |
| 1002 | // forthcoming from this agent. |
| 1003 | try { |
| 1004 | FileOutputStream out = new FileOutputStream(data.getFileDescriptor()); |
| 1005 | byte[] buf = new byte[4]; |
| 1006 | out.write(buf); |
| 1007 | } catch (IOException e) { |
| 1008 | Log.e(TAG, "Unable to finalize backup stream!"); |
| 1009 | } |
| 1010 | |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 1011 | Binder.restoreCallingIdentity(ident); |
| 1012 | try { |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 1013 | callbackBinder.opComplete(token, 0); |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 1014 | } catch (RemoteException e) { |
| 1015 | // we'll time out anyway, so we're safe |
| 1016 | } |
Christopher Tate | 91bb0e5 | 2016-09-30 17:52:19 -0700 | [diff] [blame] | 1017 | |
| 1018 | if (Binder.getCallingPid() != Process.myPid()) { |
| 1019 | IoUtils.closeQuietly(data); |
| 1020 | } |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 1021 | } |
| 1022 | } |
| 1023 | |
Shreyas Basarge | b6e73c9 | 2017-01-31 20:13:43 +0000 | [diff] [blame] | 1024 | public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) { |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 1025 | // Ensure that we're running with the app's normal permission level |
| 1026 | final long ident = Binder.clearCallingIdentity(); |
Christopher Tate | ee87b96 | 2017-04-26 17:07:27 -0700 | [diff] [blame] | 1027 | FullBackupDataOutput measureOutput = new FullBackupDataOutput(quotaBytes); |
Shreyas Basarge | b6e73c9 | 2017-01-31 20:13:43 +0000 | [diff] [blame] | 1028 | |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 1029 | waitForSharedPrefs(); |
| 1030 | try { |
| 1031 | BackupAgent.this.onFullBackup(measureOutput); |
| 1032 | } catch (IOException ex) { |
| 1033 | Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex); |
| 1034 | throw new RuntimeException(ex); |
| 1035 | } catch (RuntimeException ex) { |
| 1036 | Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex); |
| 1037 | throw ex; |
| 1038 | } finally { |
| 1039 | Binder.restoreCallingIdentity(ident); |
| 1040 | try { |
| 1041 | callbackBinder.opComplete(token, measureOutput.getSize()); |
| 1042 | } catch (RemoteException e) { |
| 1043 | // timeout, so we're safe |
| 1044 | } |
| 1045 | } |
| 1046 | } |
| 1047 | |
Christopher Tate | 79ec80d | 2011-06-24 14:58:49 -0700 | [diff] [blame] | 1048 | @Override |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 1049 | public void doRestoreFile(ParcelFileDescriptor data, long size, |
| 1050 | int type, String domain, String path, long mode, long mtime, |
| 1051 | int token, IBackupManager callbackBinder) throws RemoteException { |
| 1052 | long ident = Binder.clearCallingIdentity(); |
| 1053 | try { |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 1054 | BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime); |
| 1055 | } catch (IOException e) { |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 1056 | Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e); |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 1057 | throw new RuntimeException(e); |
| 1058 | } finally { |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 1059 | // Ensure that any side-effect SharedPreferences writes have landed |
| 1060 | waitForSharedPrefs(); |
Christopher Tate | fe2368c | 2017-05-17 15:42:35 -0700 | [diff] [blame] | 1061 | // And bring live SharedPreferences instances up to date |
| 1062 | reloadSharedPreferences(); |
Christopher Tate | f85f5b2 | 2013-04-18 16:57:43 -0700 | [diff] [blame] | 1063 | |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 1064 | Binder.restoreCallingIdentity(ident); |
| 1065 | try { |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 1066 | callbackBinder.opComplete(token, 0); |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 1067 | } catch (RemoteException e) { |
| 1068 | // we'll time out anyway, so we're safe |
| 1069 | } |
Christopher Tate | 91bb0e5 | 2016-09-30 17:52:19 -0700 | [diff] [blame] | 1070 | |
| 1071 | if (Binder.getCallingPid() != Process.myPid()) { |
| 1072 | IoUtils.closeQuietly(data); |
| 1073 | } |
Christopher Tate | 75a9970 | 2011-05-18 16:28:19 -0700 | [diff] [blame] | 1074 | } |
| 1075 | } |
Christopher Tate | cba5941 | 2014-04-01 10:38:29 -0700 | [diff] [blame] | 1076 | |
| 1077 | @Override |
Christopher Tate | 2e40d11 | 2014-07-15 12:37:38 -0700 | [diff] [blame] | 1078 | public void doRestoreFinished(int token, IBackupManager callbackBinder) { |
| 1079 | long ident = Binder.clearCallingIdentity(); |
| 1080 | try { |
| 1081 | BackupAgent.this.onRestoreFinished(); |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 1082 | } catch (Exception e) { |
| 1083 | Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e); |
| 1084 | throw e; |
Christopher Tate | 2e40d11 | 2014-07-15 12:37:38 -0700 | [diff] [blame] | 1085 | } finally { |
| 1086 | // Ensure that any side-effect SharedPreferences writes have landed |
| 1087 | waitForSharedPrefs(); |
| 1088 | |
| 1089 | Binder.restoreCallingIdentity(ident); |
| 1090 | try { |
Christopher Tate | 11ae768 | 2015-03-24 18:48:10 -0700 | [diff] [blame] | 1091 | callbackBinder.opComplete(token, 0); |
Christopher Tate | 2e40d11 | 2014-07-15 12:37:38 -0700 | [diff] [blame] | 1092 | } catch (RemoteException e) { |
| 1093 | // we'll time out anyway, so we're safe |
| 1094 | } |
| 1095 | } |
| 1096 | } |
| 1097 | |
| 1098 | @Override |
Christopher Tate | cba5941 | 2014-04-01 10:38:29 -0700 | [diff] [blame] | 1099 | public void fail(String message) { |
| 1100 | getHandler().post(new FailRunnable(message)); |
| 1101 | } |
Sergey Poromov | 872d3b6 | 2016-01-12 15:48:08 +0100 | [diff] [blame] | 1102 | |
| 1103 | @Override |
| 1104 | public void doQuotaExceeded(long backupDataBytes, long quotaBytes) { |
| 1105 | long ident = Binder.clearCallingIdentity(); |
| 1106 | try { |
| 1107 | BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes); |
| 1108 | } catch (Exception e) { |
| 1109 | Log.d(TAG, "onQuotaExceeded(" + BackupAgent.this.getClass().getName() + ") threw", |
| 1110 | e); |
| 1111 | throw e; |
| 1112 | } finally { |
| 1113 | waitForSharedPrefs(); |
| 1114 | Binder.restoreCallingIdentity(ident); |
| 1115 | } |
| 1116 | } |
Christopher Tate | cba5941 | 2014-04-01 10:38:29 -0700 | [diff] [blame] | 1117 | } |
| 1118 | |
| 1119 | static class FailRunnable implements Runnable { |
| 1120 | private String mMessage; |
| 1121 | |
| 1122 | FailRunnable(String message) { |
| 1123 | mMessage = message; |
| 1124 | } |
| 1125 | |
| 1126 | @Override |
| 1127 | public void run() { |
| 1128 | throw new IllegalStateException(mMessage); |
| 1129 | } |
Christopher Tate | 487529a | 2009-04-29 14:03:25 -0700 | [diff] [blame] | 1130 | } |
| 1131 | } |