summaryrefslogtreecommitdiff
path: root/services/backup
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2020-08-31 21:21:38 -0700
committerXin Li <delphij@google.com>2020-08-31 21:21:38 -0700
commit628590d7ec80e10a3fc24b1c18a1afb55cca10a8 (patch)
tree4b1c3f52d86d7fb53afbe9e9438468588fa489f8 /services/backup
parentb11b8ec3aec8bb42f2c07e1c5ac7942da293baa8 (diff)
parentd2d3a20624d968199353ccf6ddbae6f3ac39c9af (diff)
Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)
Bug: 166295507 Merged-In: I3d92a6de21a938f6b352ec26dc23420c0fe02b27 Change-Id: Ifdb80563ef042738778ebb8a7581a97c4e3d96e2
Diffstat (limited to 'services/backup')
-rw-r--r--services/backup/Android.bp2
-rw-r--r--services/backup/TEST_MAPPING11
-rw-r--r--services/backup/backuplib/Android.bp12
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/TransportManager.java (renamed from services/backup/java/com/android/server/backup/TransportManager.java)0
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java414
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/OnTransportRegisteredListener.java (renamed from services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java)0
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java (renamed from services/backup/java/com/android/server/backup/transport/TransportClient.java)5
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java (renamed from services/backup/java/com/android/server/backup/transport/TransportClientManager.java)76
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java (renamed from services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java)0
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java (renamed from services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java)0
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/TransportNotRegisteredException.java (renamed from services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java)0
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java (renamed from services/backup/java/com/android/server/backup/transport/TransportStats.java)0
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/TransportUtils.java (renamed from services/backup/java/com/android/server/backup/transport/TransportUtils.java)0
-rw-r--r--services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java30
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerConstants.java9
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java1388
-rw-r--r--services/backup/java/com/android/server/backup/DataChangedJournal.java9
-rw-r--r--services/backup/java/com/android/server/backup/FullBackupJob.java8
-rw-r--r--services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java4
-rw-r--r--services/backup/java/com/android/server/backup/KeyValueBackupJob.java5
-rw-r--r--services/backup/java/com/android/server/backup/Trampoline.java823
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerFilePersistedSettings.java20
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java1159
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupPreferences.java54
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java54
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java89
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunk/ChunkListingMap.java109
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java30
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java67
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java43
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java80
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java90
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java47
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java46
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java75
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java36
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java91
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java45
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java68
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java51
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java25
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java52
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java130
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java136
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java95
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java115
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java78
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java113
-rw-r--r--services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java117
-rw-r--r--services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java120
-rw-r--r--services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java47
-rw-r--r--services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java113
-rw-r--r--services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDb.java59
-rw-r--r--services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java41
-rw-r--r--services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java102
-rw-r--r--services/backup/java/com/android/server/backup/encryption/storage/EncryptionDbException.java26
-rw-r--r--services/backup/java/com/android/server/backup/encryption/storage/TertiaryKey.java52
-rw-r--r--services/backup/java/com/android/server/backup/encryption/storage/TertiaryKeysTable.java134
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java4
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java4
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java98
-rw-r--r--services/backup/java/com/android/server/backup/internal/BackupHandler.java66
-rw-r--r--services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java110
-rw-r--r--services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java15
-rw-r--r--services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java3
-rw-r--r--services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java214
-rw-r--r--services/backup/java/com/android/server/backup/params/RestoreParams.java3
-rw-r--r--services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java4
-rw-r--r--services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java31
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java61
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java120
-rw-r--r--services/backup/java/com/android/server/backup/utils/TarBackupReader.java8
72 files changed, 2923 insertions, 4423 deletions
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index f945d0f837a1..56b788e34d35 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -7,6 +7,8 @@ filegroup {
java_library_static {
name: "services.backup",
+ defaults: ["services_defaults"],
srcs: [":services.backup-sources"],
libs: ["services.core"],
+ static_libs: ["backuplib"],
}
diff --git a/services/backup/TEST_MAPPING b/services/backup/TEST_MAPPING
new file mode 100644
index 000000000000..4a8bd8e8c9e7
--- /dev/null
+++ b/services/backup/TEST_MAPPING
@@ -0,0 +1,11 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsBackupTestCases"
+ }
+ ],
+ "postsubmit": [
+ ],
+ "imports": [
+ ]
+}
diff --git a/services/backup/backuplib/Android.bp b/services/backup/backuplib/Android.bp
new file mode 100644
index 000000000000..00f51c960636
--- /dev/null
+++ b/services/backup/backuplib/Android.bp
@@ -0,0 +1,12 @@
+filegroup {
+ name: "backuplib-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+ name: "backuplib",
+ srcs: [":backuplib-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
index 30ce4cf2fd3f..30ce4cf2fd3f 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java b/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java
new file mode 100644
index 000000000000..ab870803e60d
--- /dev/null
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.transport;
+
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.app.backup.RestoreSet;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Delegates all transport methods to the delegate() implemented in the derived class.
+ */
+public abstract class DelegatingTransport extends IBackupTransport.Stub {
+ protected abstract IBackupTransport getDelegate() throws RemoteException;
+
+ /**
+ * Ask the transport for the name under which it should be registered. This will
+ * typically be its host service's component name, but need not be.
+ */
+ @Override
+ public String name() throws RemoteException {
+ return getDelegate().name();
+ }
+
+ /**
+ * Ask the transport for an Intent that can be used to launch any internal
+ * configuration Activity that it wishes to present. For example, the transport
+ * may offer a UI for allowing the user to supply login credentials for the
+ * transport's off-device backend.
+ *
+ * If the transport does not supply any user-facing configuration UI, it should
+ * return null from this method.
+ *
+ * @return An Intent that can be passed to Context.startActivity() in order to
+ * launch the transport's configuration UI. This method will return null
+ * if the transport does not offer any user-facing configuration UI.
+ */
+ @Override
+ public Intent configurationIntent() throws RemoteException {
+ return getDelegate().configurationIntent();
+ }
+
+ /**
+ * On demand, supply a one-line string that can be shown to the user that
+ * describes the current backend destination. For example, a transport that
+ * can potentially associate backup data with arbitrary user accounts should
+ * include the name of the currently-active account here.
+ *
+ * @return A string describing the destination to which the transport is currently
+ * sending data. This method should not return null.
+ */
+ @Override
+ public String currentDestinationString() throws RemoteException {
+ return getDelegate().currentDestinationString();
+ }
+
+ /**
+ * Ask the transport for an Intent that can be used to launch a more detailed
+ * secondary data management activity. For example, the configuration intent might
+ * be one for allowing the user to select which account they wish to associate
+ * their backups with, and the management intent might be one which presents a
+ * UI for managing the data on the backend.
+ *
+ * <p>In the Settings UI, the configuration intent will typically be invoked
+ * when the user taps on the preferences item labeled with the current
+ * destination string, and the management intent will be placed in an overflow
+ * menu labelled with the management label string.
+ *
+ * <p>If the transport does not supply any user-facing data management
+ * UI, then it should return {@code null} from this method.
+ *
+ * @return An intent that can be passed to Context.startActivity() in order to
+ * launch the transport's data-management UI. This method will return
+ * {@code null} if the transport does not offer any user-facing data
+ * management UI.
+ */
+ @Override
+ public Intent dataManagementIntent() throws RemoteException {
+ return getDelegate().dataManagementIntent();
+ }
+
+ /**
+ * On demand, supply a short {@link CharSequence} that can be shown to the user as the
+ * label on
+ * an overflow menu item used to invoke the data management UI.
+ *
+ * @return A {@link CharSequence} to be used as the label for the transport's data management
+ * affordance. If the transport supplies a data management intent, this
+ * method must not return {@code null}.
+ */
+ @Override
+ public CharSequence dataManagementIntentLabel() throws RemoteException {
+ return getDelegate().dataManagementIntentLabel();
+ }
+
+ /**
+ * Ask the transport where, on local device storage, to keep backup state blobs.
+ * This is per-transport so that mock transports used for testing can coexist with
+ * "live" backup services without interfering with the live bookkeeping. The
+ * returned string should be a name that is expected to be unambiguous among all
+ * available backup transports; the name of the class implementing the transport
+ * is a good choice. This MUST be constant.
+ *
+ * @return A unique name, suitable for use as a file or directory name, that the
+ * Backup Manager could use to disambiguate state files associated with
+ * different backup transports.
+ */
+ @Override
+ public String transportDirName() throws RemoteException {
+ return getDelegate().transportDirName();
+ }
+
+ /**
+ * Verify that this is a suitable time for a backup pass. This should return zero
+ * if a backup is reasonable right now, some positive value otherwise. This method
+ * will be called outside of the {@link #startSession}/{@link #endSession} pair.
+ *
+ * <p>If this is not a suitable time for a backup, the transport should return a
+ * backoff delay, in milliseconds, after which the Backup Manager should try again.
+ *
+ * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+ * in milliseconds to suggest deferring the backup pass for a while.
+ */
+ @Override
+ public long requestBackupTime() throws RemoteException {
+ return getDelegate().requestBackupTime();
+ }
+
+ /**
+ * Initialize the server side storage for this device, erasing all stored data.
+ * The transport may send the request immediately, or may buffer it. After
+ * this is called, {@link #finishBackup} must be called to ensure the request
+ * is sent and received successfully.
+ *
+ * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or
+ * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure).
+ */
+ @Override
+ public int initializeDevice() throws RemoteException {
+ return getDelegate().initializeDevice();
+ }
+
+ /**
+ * Send one application's data to the backup destination. The transport may send
+ * the data immediately, or may buffer it. After this is called, {@link #finishBackup}
+ * must be called to ensure the data is sent and recorded successfully.
+ *
+ * @param packageInfo The identity of the application whose data is being backed up.
+ * This specifically includes the signature list for the package.
+ * @param inFd Descriptor of file with data that resulted from invoking the application's
+ * BackupService.doBackup() method. This may be a pipe rather than a file on
+ * persistent media, so it may not be seekable.
+ * @param flags Some of {@link BackupTransport#FLAG_USER_INITIATED}.
+ * @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far),
+ * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or
+ * {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
+ * become lost due to inactive expiry or some other reason and needs re-initializing)
+ */
+ @Override
+ public int performBackup(PackageInfo packageInfo,
+ ParcelFileDescriptor inFd, int flags) throws RemoteException {
+ return getDelegate().performBackup(packageInfo, inFd, flags);
+ }
+
+ /**
+ * Erase the give application's data from the backup destination. This clears
+ * out the given package's data from the current backup set, making it as though
+ * the app had never yet been backed up. After this is called, {@link finishBackup}
+ * must be called to ensure that the operation is recorded successfully.
+ *
+ * @return the same error codes as {@link #performBackup}.
+ * @param packageInfo
+ */
+ @Override
+ public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
+ return getDelegate().clearBackupData(packageInfo);
+ }
+
+ /**
+ * Finish sending application data to the backup destination. This must be
+ * called after {@link #performBackup} or {@link clearBackupData} to ensure that
+ * all data is sent. Only when this method returns true can a backup be assumed
+ * to have succeeded.
+ *
+ * @return the same error codes as {@link #performBackup}.
+ */
+ @Override
+ public int finishBackup() throws RemoteException {
+ return getDelegate().finishBackup();
+ }
+
+ /**
+ * Get the set of all backups currently available over this transport.
+ *
+ * @return Descriptions of the set of restore images available for this device,
+ * or null if an error occurred (the attempt should be rescheduled).
+ **/
+ @Override
+ public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
+ return getDelegate().getAvailableRestoreSets();
+ }
+
+ /**
+ * Get the identifying token of the backup set currently being stored from
+ * this device. This is used in the case of applications wishing to restore
+ * their last-known-good data.
+ *
+ * @return A token that can be passed to {@link #startRestore}, or 0 if there
+ * is no backup set available corresponding to the current device state.
+ */
+ @Override
+ public long getCurrentRestoreSet() throws RemoteException {
+ return getDelegate().getCurrentRestoreSet();
+ }
+
+ /**
+ * Start restoring application data from backup. After calling this function,
+ * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
+ * to walk through the actual application data.
+ *
+ * @param token A backup token as returned by {@link #getAvailableRestoreSets}
+ * or {@link #getCurrentRestoreSet}.
+ * @param packages List of applications to restore (if data is available).
+ * Application data will be restored in the order given.
+ * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call
+ * {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR}
+ * (an error occurred, the restore should be aborted and rescheduled).
+ */
+ @Override
+ public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
+ return getDelegate().startRestore(token, packages);
+ }
+
+ /**
+ * Get the package name of the next application with data in the backup store, plus
+ * a description of the structure of the restored archive: either TYPE_KEY_VALUE for
+ * an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream.
+ *
+ * <p>If the package name in the returned RestoreDescription object is the singleton
+ * {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available
+ * in the current restore session: all packages described in startRestore() have been
+ * processed.
+ *
+ * <p>If this method returns {@code null}, it means that a transport-level error has
+ * occurred and the entire restore operation should be abandoned.
+ *
+ * @return A RestoreDescription object containing the name of one of the packages
+ * supplied to {@link #startRestore} plus an indicator of the data type of that
+ * restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that
+ * no more packages can be restored in this session; or {@code null} to indicate
+ * a transport-level error.
+ */
+ @Override
+ public RestoreDescription nextRestorePackage() throws RemoteException {
+ return getDelegate().nextRestorePackage();
+ }
+
+ /**
+ * Get the data for the application returned by {@link #nextRestorePackage}.
+ *
+ * @param outFd An open, writable file into which the backup data should be stored.
+ * @return the same error codes as {@link #startRestore}.
+ */
+ @Override
+ public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
+ return getDelegate().getRestoreData(outFd);
+ }
+
+ /**
+ * End a restore session (aborting any in-process data transfer as necessary),
+ * freeing any resources and connections used during the restore process.
+ */
+ @Override
+ public void finishRestore() throws RemoteException {
+ getDelegate().finishRestore();
+ }
+
+ @Override
+ public long requestFullBackupTime() throws RemoteException {
+ return getDelegate().requestFullBackupTime();
+ }
+
+ @Override
+ public int performFullBackup(PackageInfo targetPackage,
+ ParcelFileDescriptor socket, int flags) throws RemoteException {
+ return getDelegate().performFullBackup(targetPackage, socket, flags);
+ }
+
+ @Override
+ public int checkFullBackupSize(long size) throws RemoteException {
+ return getDelegate().checkFullBackupSize(size);
+ }
+
+ @Override
+ public int sendBackupData(int numBytes) throws RemoteException {
+ return getDelegate().sendBackupData(numBytes);
+ }
+
+ @Override
+ public void cancelFullBackup() throws RemoteException {
+ getDelegate().cancelFullBackup();
+ }
+
+ /**
+ * Ask the transport whether this app is eligible for backup.
+ *
+ * @param targetPackage The identity of the application.
+ * @param isFullBackup If set, transport should check if app is eligible for full data backup,
+ * otherwise to check if eligible for key-value backup.
+ * @return Whether this app is eligible for backup.
+ */
+ @Override
+ public boolean isAppEligibleForBackup(PackageInfo targetPackage,
+ boolean isFullBackup) throws RemoteException {
+ return getDelegate().isAppEligibleForBackup(targetPackage, isFullBackup);
+ }
+
+ /**
+ * Ask the transport about current quota for backup size of the package.
+ *
+ * @param packageName ID of package to provide the quota.
+ * @param isFullBackup If set, transport should return limit for full data backup, otherwise
+ * for key-value backup.
+ * @return Current limit on full data backup size in bytes.
+ */
+ @Override
+ public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException {
+ return getDelegate().getBackupQuota(packageName, isFullBackup);
+ }
+
+ /**
+ * Ask the transport to provide data for the "current" package being restored. This
+ * is the package that was just reported by {@link #nextRestorePackage()} as having
+ * {@link RestoreDescription#TYPE_FULL_STREAM} data.
+ *
+ * The transport writes some data to the socket supplied to this call, and returns
+ * the number of bytes written. The system will then read that many bytes and
+ * stream them to the application's agent for restore, then will call this method again
+ * to receive the next chunk of the archive. This sequence will be repeated until the
+ * transport returns zero indicating that all of the package's data has been delivered
+ * (or returns a negative value indicating some sort of hard error condition at the
+ * transport level).
+ *
+ * <p>After this method returns zero, the system will then call
+ * {@link #getNextFullRestorePackage()} to begin the restore process for the next
+ * application, and the sequence begins again.
+ *
+ * <p>The transport should always close this socket when returning from this method.
+ * Do not cache this socket across multiple calls or you may leak file descriptors.
+ *
+ * @param socket The file descriptor that the transport will use for delivering the
+ * streamed archive. The transport must close this socket in all cases when returning
+ * from this method.
+ * @return 0 when no more data for the current package is available. A positive value
+ * indicates the presence of that many bytes to be delivered to the app. Any negative
+ * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
+ * indicating a fatal error condition that precludes further restore operations
+ * on the current dataset.
+ */
+ @Override
+ public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException {
+ return getDelegate().getNextFullRestoreDataChunk(socket);
+ }
+
+ /**
+ * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
+ * data for restore, it will invoke this method to tell the transport that it should
+ * abandon the data download for the current package. The OS will then either call
+ * {@link #nextRestorePackage()} again to move on to restoring the next package in the
+ * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
+ * operation.
+ *
+ * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
+ * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
+ * transport-level failure. If the transport reports an error here, the entire restore
+ * operation will immediately be finished with no further attempts to restore app data.
+ */
+ @Override
+ public int abortFullRestore() throws RemoteException {
+ return getDelegate().abortFullRestore();
+ }
+
+ /**
+ * Returns flags with additional information about the transport, which is accessible to the
+ * {@link BackupAgent}. This allows the agent to decide what to backup or
+ * restore based on properties of the transport.
+ *
+ * <p>For supported flags see {@link BackupAgent}.
+ */
+ @Override
+ public int getTransportFlags() throws RemoteException {
+ return getDelegate().getTransportFlags();
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
index 391ec2d7f294..391ec2d7f294 100644
--- a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java
index 7c5a57c004e4..ca89f7f69fbc 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java
@@ -26,6 +26,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Binder;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
@@ -662,6 +663,10 @@ public class TransportClient {
referenceLost("TransportConnection.onServiceConnected()");
return;
}
+ // TODO (b/147705255): Remove when binder calls to IBackupTransport are not blocking
+ // In short-term, blocking calls are OK as the transports come from the whitelist at
+ // {@link SystemConfig#getBackupTransportWhitelist()}
+ Binder.allowBlocking(binder);
transportClient.onServiceConnected(binder);
}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
index a4e9b1091bed..72b1ee741d95 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
@@ -19,18 +19,21 @@ package com.android.server.backup.transport;
import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
import static com.android.server.backup.transport.TransportUtils.formatMessage;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.TransportManager;
import com.android.server.backup.transport.TransportUtils.Priority;
import java.io.PrintWriter;
import java.util.Map;
import java.util.WeakHashMap;
+import java.util.function.Function;
/**
* Manages the creation and disposal of {@link TransportClient}s. The only class that should use
@@ -38,6 +41,12 @@ import java.util.WeakHashMap;
*/
public class TransportClientManager {
private static final String TAG = "TransportClientManager";
+ private static final String SERVICE_ACTION_ENCRYPTING_TRANSPORT =
+ "android.encryption.BACKUP_ENCRYPTION";
+ private static final ComponentName ENCRYPTING_TRANSPORT = new ComponentName(
+ "com.android.server.backup.encryption",
+ "com.android.server.backup.encryption.BackupEncryptionService");
+ private static final String ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY = "transport";
private final @UserIdInt int mUserId;
private final Context mContext;
@@ -45,12 +54,64 @@ public class TransportClientManager {
private final Object mTransportClientsLock = new Object();
private int mTransportClientsCreated = 0;
private Map<TransportClient, String> mTransportClientsCallerMap = new WeakHashMap<>();
+ private final Function<ComponentName, Intent> mIntentFunction;
+
+ /**
+ * Return an {@link Intent} which resolves to an intermediate {@link IBackupTransport} that
+ * encrypts (or decrypts) the data when sending it (or receiving it) from the {@link
+ * IBackupTransport} for the given {@link ComponentName}.
+ */
+ public static Intent getEncryptingTransportIntent(ComponentName tranportComponent) {
+ return new Intent(SERVICE_ACTION_ENCRYPTING_TRANSPORT)
+ .setComponent(ENCRYPTING_TRANSPORT)
+ .putExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY, tranportComponent);
+ }
+
+ /**
+ * Return an {@link Intent} which resolves to the {@link IBackupTransport} for the {@link
+ * ComponentName}.
+ */
+ private static Intent getRealTransportIntent(ComponentName transportComponent) {
+ return new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
+ }
+
+ /**
+ * Given a {@link Intent} originally created by {@link
+ * #getEncryptingTransportIntent(ComponentName)}, returns the {@link Intent} which resolves to
+ * the {@link IBackupTransport} for that {@link ComponentName}.
+ */
+ public static Intent getRealTransportIntent(Intent encryptingTransportIntent) {
+ ComponentName transportComponent = encryptingTransportIntent.getParcelableExtra(
+ ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY);
+ Intent intent = getRealTransportIntent(transportComponent)
+ .putExtras(encryptingTransportIntent.getExtras());
+ intent.removeExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY);
+ return intent;
+ }
+
+ /**
+ * Create a {@link TransportClientManager} such that {@link #getTransportClient(ComponentName,
+ * Bundle, String)} returns a {@link TransportClient} which connects to an intermediate {@link
+ * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
+ * the {@link IBackupTransport} for the given {@link ComponentName}.
+ */
+ public static TransportClientManager createEncryptingClientManager(@UserIdInt int userId,
+ Context context, TransportStats transportStats) {
+ return new TransportClientManager(userId, context, transportStats,
+ TransportClientManager::getEncryptingTransportIntent);
+ }
public TransportClientManager(@UserIdInt int userId, Context context,
TransportStats transportStats) {
+ this(userId, context, transportStats, TransportClientManager::getRealTransportIntent);
+ }
+
+ private TransportClientManager(@UserIdInt int userId, Context context,
+ TransportStats transportStats, Function<ComponentName, Intent> intentFunction) {
mUserId = userId;
mContext = context;
mTransportStats = transportStats;
+ mIntentFunction = intentFunction;
}
/**
@@ -64,10 +125,7 @@ public class TransportClientManager {
* @return A {@link TransportClient}.
*/
public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
- Intent bindIntent =
- new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
-
- return getTransportClient(transportComponent, caller, bindIntent);
+ return getTransportClient(transportComponent, null, caller);
}
/**
@@ -82,11 +140,11 @@ public class TransportClientManager {
* @return A {@link TransportClient}.
*/
public TransportClient getTransportClient(
- ComponentName transportComponent, Bundle extras, String caller) {
- Intent bindIntent =
- new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
- bindIntent.putExtras(extras);
-
+ ComponentName transportComponent, @Nullable Bundle extras, String caller) {
+ Intent bindIntent = mIntentFunction.apply(transportComponent);
+ if (extras != null) {
+ bindIntent.putExtras(extras);
+ }
return getTransportClient(transportComponent, caller, bindIntent);
}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
index 1ccffd01d12c..1ccffd01d12c 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java
index c08eb7f4a54e..c08eb7f4a54e 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotRegisteredException.java
index 02766deeb7e2..02766deeb7e2 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotRegisteredException.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportStats.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java
index bd84782122ad..bd84782122ad 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportStats.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportUtils.java
index 766d77bd639c..766d77bd639c 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportUtils.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportUtils.java
diff --git a/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java b/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java
index 2bca34d9cef5..0e99b34e9dd1 100644
--- a/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java
+++ b/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java
@@ -16,14 +16,12 @@
package com.android.server.backup;
-import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
import android.content.ContentResolver;
import android.os.Handler;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.KeyValueSettingObserver;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -35,8 +33,6 @@ import com.android.internal.annotations.VisibleForTesting;
* are represented as a comma-delimited key value list.
*/
public class BackupAgentTimeoutParameters extends KeyValueSettingObserver {
- private static final String TAG = "BackupAgentTimeout";
-
@VisibleForTesting
public static final String SETTING = Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS;
@@ -140,62 +136,36 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver {
public long getKvBackupAgentTimeoutMillis() {
synchronized (mLock) {
- if (DEBUG_SCHEDULING) {
- Slog.v(TAG, "getKvBackupAgentTimeoutMillis(): " + mKvBackupAgentTimeoutMillis);
- }
return mKvBackupAgentTimeoutMillis;
}
}
public long getFullBackupAgentTimeoutMillis() {
synchronized (mLock) {
- if (DEBUG_SCHEDULING) {
- Slog.v(TAG, "getFullBackupAgentTimeoutMillis(): " + mFullBackupAgentTimeoutMillis);
- }
return mFullBackupAgentTimeoutMillis;
}
}
public long getSharedBackupAgentTimeoutMillis() {
synchronized (mLock) {
- if (DEBUG_SCHEDULING) {
- Slog.v(
- TAG,
- "getSharedBackupAgentTimeoutMillis(): " + mSharedBackupAgentTimeoutMillis);
- }
return mSharedBackupAgentTimeoutMillis;
}
}
public long getRestoreAgentTimeoutMillis() {
synchronized (mLock) {
- if (DEBUG_SCHEDULING) {
- Slog.v(TAG, "getRestoreAgentTimeoutMillis(): " + mRestoreAgentTimeoutMillis);
- }
return mRestoreAgentTimeoutMillis;
}
}
public long getRestoreAgentFinishedTimeoutMillis() {
synchronized (mLock) {
- if (DEBUG_SCHEDULING) {
- Slog.v(
- TAG,
- "getRestoreAgentFinishedTimeoutMillis(): "
- + mRestoreAgentFinishedTimeoutMillis);
- }
return mRestoreAgentFinishedTimeoutMillis;
}
}
public long getQuotaExceededTimeoutMillis() {
synchronized (mLock) {
- if (DEBUG_SCHEDULING) {
- Slog.v(
- TAG,
- "getQuotaExceededTimeoutMillis(): "
- + mQuotaExceededTimeoutMillis);
- }
return mQuotaExceededTimeoutMillis;
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerConstants.java b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
index 785d3ca8a4a2..d8c5f6f804c8 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerConstants.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
@@ -19,6 +19,7 @@ package com.android.server.backup;
import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
import android.app.AlarmManager;
+import android.app.job.JobInfo;
import android.content.ContentResolver;
import android.os.Handler;
import android.provider.Settings;
@@ -80,14 +81,18 @@ public class BackupManagerConstants extends KeyValueSettingObserver {
public static final long DEFAULT_KEY_VALUE_BACKUP_FUZZ_MILLISECONDS = 10 * 60 * 1000;
@VisibleForTesting public static final boolean DEFAULT_KEY_VALUE_BACKUP_REQUIRE_CHARGING = true;
- @VisibleForTesting public static final int DEFAULT_KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE = 1;
+ @VisibleForTesting
+ public static final int DEFAULT_KEY_VALUE_BACKUP_REQUIRED_NETWORK_TYPE =
+ JobInfo.NETWORK_TYPE_ANY;
@VisibleForTesting
public static final long DEFAULT_FULL_BACKUP_INTERVAL_MILLISECONDS =
24 * AlarmManager.INTERVAL_HOUR;
@VisibleForTesting public static final boolean DEFAULT_FULL_BACKUP_REQUIRE_CHARGING = true;
- @VisibleForTesting public static final int DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE = 2;
+ @VisibleForTesting
+ public static final int DEFAULT_FULL_BACKUP_REQUIRED_NETWORK_TYPE =
+ JobInfo.NETWORK_TYPE_UNMETERED;
@VisibleForTesting
public static final String DEFAULT_BACKUP_FINISHED_NOTIFICATION_RECEIVERS = "";
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 302e3ffa5200..b13bef2de151 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,13 +16,15 @@
package com.android.server.backup;
-import static com.android.internal.util.Preconditions.checkNotNull;
+import static java.util.Collections.emptySet;
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
+import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
@@ -39,24 +41,32 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.FileUtils;
+import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
+import com.android.server.backup.utils.RandomAccessFileUtils;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.IOException;
import java.io.PrintWriter;
-import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -65,8 +75,20 @@ import java.util.Set;
* <p>This class is responsible for handling user-aware operations and acts as a delegator, routing
* incoming calls to the appropriate per-user {@link UserBackupManagerService} to handle the
* corresponding backup/restore operation.
+ *
+ * <p>It also determines whether the backup service is available. It can be disabled in the
+ * following two ways:
+ *
+ * <ul>
+ * <li>Temporary - call {@link #setBackupServiceActive(int, boolean)}, or
+ * <li>Permanent - set the system property {@link #BACKUP_DISABLE_PROPERTY} to true.
+ * </ul>
+ *
+ * Temporary disabling is controlled by {@link #setBackupServiceActive(int, boolean)} through
+ * privileged callers (currently {@link DevicePolicyManager}). If called on {@link
+ * UserHandle#USER_SYSTEM}, backup is disabled for all users.
*/
-public class BackupManagerService {
+public class BackupManagerService extends IBackupManager.Stub {
public static final String TAG = "BackupManagerService";
public static final boolean DEBUG = true;
public static final boolean MORE_DEBUG = false;
@@ -75,24 +97,48 @@ public class BackupManagerService {
@VisibleForTesting
static final String DUMP_RUNNING_USERS_MESSAGE = "Backup Manager is running for users:";
- // The published binder is a singleton Trampoline object that calls through to the proper code.
- // This indirection lets us turn down the heavy implementation object on the fly without
- // disturbing binders that have been cached elsewhere in the system.
- private static Trampoline sInstance;
+ /**
+ * Name of file that disables the backup service. If this file exists, then backup is disabled
+ * for all users.
+ */
+ private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";
+
+ /**
+ * Name of file for non-system users that enables the backup service for the user. Backup is
+ * disabled by default in non-system users.
+ */
+ private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated";
+
+ /**
+ * Name of file for non-system users that remembers whether backup was explicitly activated or
+ * deactivated with a call to setBackupServiceActive.
+ */
+ private static final String REMEMBER_ACTIVATED_FILENAME = "backup-remember-activated";
+
+ // Product-level suppression of backup/restore.
+ private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
+
+ private static final String BACKUP_THREAD = "backup";
- static Trampoline getInstance() {
- // Always constructed during system bring up, so no need to lazy-init.
- return sInstance;
+ static BackupManagerService sInstance;
+
+ static BackupManagerService getInstance() {
+ return Objects.requireNonNull(sInstance);
}
private final Context mContext;
- private final Trampoline mTrampoline;
- private final HandlerThread mBackupThread;
+ private final UserManager mUserManager;
+
+ private final boolean mGlobalDisable;
+ // Lock to write backup suppress files.
+ // TODD(b/121198006): remove this object and synchronized all methods on "this".
+ private final Object mStateLock = new Object();
- // Keeps track of all unlocked users registered with this service. Indexed by user id.
- private final SparseArray<UserBackupManagerService> mServiceUsers = new SparseArray<>();
+ private final Handler mHandler;
+ private final Set<ComponentName> mTransportWhitelist;
- private Set<ComponentName> mTransportWhitelist;
+ /** Keeps track of all unlocked users registered with this service. Indexed by user id. */
+ private final SparseArray<UserBackupManagerService> mUserServices;
private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
@Override
@@ -100,28 +146,65 @@ public class BackupManagerService {
if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (userId > 0) { // for only non system users
- onRemovedNonSystemUser(userId);
+ mHandler.post(() -> onRemovedNonSystemUser(userId));
}
}
}
};
- /** Instantiate a new instance of {@link BackupManagerService}. */
- public BackupManagerService(
- Context context, Trampoline trampoline, HandlerThread backupThread) {
- mContext = checkNotNull(context);
- mTrampoline = checkNotNull(trampoline);
- mBackupThread = checkNotNull(backupThread);
+ public BackupManagerService(Context context) {
+ this(context, new SparseArray<>());
+ }
- // Set up our transport options.
- SystemConfig systemConfig = SystemConfig.getInstance();
- mTransportWhitelist = systemConfig.getBackupTransportWhitelist();
- if (mTransportWhitelist == null) {
- mTransportWhitelist = Collections.emptySet();
- }
+ @VisibleForTesting
+ BackupManagerService(Context context, SparseArray<UserBackupManagerService> userServices) {
+ mContext = context;
+ mGlobalDisable = isBackupDisabled();
+ HandlerThread handlerThread =
+ new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ mUserManager = UserManager.get(context);
+ mUserServices = userServices;
+ Set<ComponentName> transportWhitelist =
+ SystemConfig.getInstance().getBackupTransportWhitelist();
+ mTransportWhitelist = (transportWhitelist == null) ? emptySet() : transportWhitelist;
+ mContext.registerReceiver(
+ mUserRemovedReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
+ }
+
+ // TODO: Remove this when we implement DI by injecting in the construtor.
+ @VisibleForTesting
+ Handler getBackupHandler() {
+ return mHandler;
+ }
+
+ protected boolean isBackupDisabled() {
+ return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
+ }
+
+ protected int binderGetCallingUserId() {
+ return Binder.getCallingUserHandle().getIdentifier();
+ }
+
+ protected int binderGetCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ /** Stored in the system user's directory. */
+ protected File getSuppressFileForSystemUser() {
+ return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
+ BACKUP_SUPPRESS_FILENAME);
+ }
+
+ /** Stored in the system user's directory and the file is indexed by the user it refers to. */
+ protected File getRememberActivatedFileForNonSystemUser(int userId) {
+ return UserBackupManagerFiles.getStateFileInSystemDir(REMEMBER_ACTIVATED_FILENAME, userId);
+ }
- mContext.registerReceiver(mUserRemovedReceiver,
- new IntentFilter(Intent.ACTION_USER_REMOVED));
+ /** Stored in the system user's directory and the file is indexed by the user it refers to. */
+ protected File getActivatedFileForNonSystemUser(int userId) {
+ return UserBackupManagerFiles.getStateFileInSystemDir(BACKUP_ACTIVATED_FILENAME, userId);
}
/**
@@ -138,38 +221,130 @@ public class BackupManagerService {
}
}
+ // TODO (b/124359804) move to util method in FileUtils
+ private void createFile(File file) throws IOException {
+ if (file.exists()) {
+ return;
+ }
+
+ file.getParentFile().mkdirs();
+ if (!file.createNewFile()) {
+ Slog.w(TAG, "Failed to create file " + file.getPath());
+ }
+ }
+
+ // TODO (b/124359804) move to util method in FileUtils
+ private void deleteFile(File file) {
+ if (!file.exists()) {
+ return;
+ }
+
+ if (!file.delete()) {
+ Slog.w(TAG, "Failed to delete file " + file.getPath());
+ }
+ }
+
/**
- * If {@code userId} is different from the calling user id, then the caller must hold the
- * android.permission.INTERACT_ACROSS_USERS_FULL permission.
- *
- * @param userId User id on which the backup operation is being requested.
- * @param message A message to include in the exception if it is thrown.
+ * Deactivates the backup service for user {@code userId}. If this is the system user, it
+ * creates a suppress file which disables backup for all users. If this is a non-system user, it
+ * only deactivates backup for that user by deleting its activate file.
*/
- private void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) {
- if (Binder.getCallingUserHandle().getIdentifier() != userId) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+ @GuardedBy("mStateLock")
+ private void deactivateBackupForUserLocked(int userId) throws IOException {
+ if (userId == UserHandle.USER_SYSTEM) {
+ createFile(getSuppressFileForSystemUser());
+ } else {
+ deleteFile(getActivatedFileForNonSystemUser(userId));
}
}
- // ---------------------------------------------
- // USER LIFECYCLE CALLBACKS
- // ---------------------------------------------
+ /**
+ * Enables the backup service for user {@code userId}. If this is the system user, it deletes
+ * the suppress file. If this is a non-system user, it creates the user's activate file. Note,
+ * deleting the suppress file does not automatically enable backup for non-system users, they
+ * need their own activate file in order to participate in the service.
+ */
+ @GuardedBy("mStateLock")
+ private void activateBackupForUserLocked(int userId) throws IOException {
+ if (userId == UserHandle.USER_SYSTEM) {
+ deleteFile(getSuppressFileForSystemUser());
+ } else {
+ createFile(getActivatedFileForNonSystemUser(userId));
+ }
+ }
+
+ /**
+ * This method should not perform any I/O (e.g. do not call isBackupActivatedForUser),
+ * it's used in multiple places where I/O waits would cause system lock-ups.
+ * @param userId User id for which this operation should be performed.
+ * @return true if the user is ready for backup and false otherwise.
+ */
+ @Override
+ public boolean isUserReadyForBackup(int userId) {
+ return mUserServices.get(UserHandle.USER_SYSTEM) != null
+ && mUserServices.get(userId) != null;
+ }
+
+ /**
+ * Backup is activated for the system user if the suppress file does not exist. Backup is
+ * activated for non-system users if the suppress file does not exist AND the user's activated
+ * file exists.
+ */
+ private boolean isBackupActivatedForUser(int userId) {
+ if (getSuppressFileForSystemUser().exists()) {
+ return false;
+ }
+
+ return userId == UserHandle.USER_SYSTEM
+ || getActivatedFileForNonSystemUser(userId).exists();
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ protected UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ protected void postToHandler(Runnable runnable) {
+ mHandler.post(runnable);
+ }
+
+ /**
+ * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked.
+ * Starts the backup service for this user if backup is active for this user. Offloads work onto
+ * the handler thread {@link #mHandlerThread} to keep unlock time low since backup is not
+ * essential for device functioning.
+ */
+ void onUnlockUser(int userId) {
+ postToHandler(() -> startServiceForUser(userId));
+ }
/**
* Starts the backup service for user {@code userId} by creating a new instance of {@link
* UserBackupManagerService} and registering it with this service.
*/
@VisibleForTesting
- protected void startServiceForUser(int userId) {
- if (mServiceUsers.get(userId) != null) {
+ void startServiceForUser(int userId) {
+ // We know that the user is unlocked here because it is called from setBackupServiceActive
+ // and unlockUser which have these guarantees. So we can check if the file exists.
+ if (mGlobalDisable) {
+ Slog.i(TAG, "Backup service not supported");
+ return;
+ }
+ if (!isBackupActivatedForUser(userId)) {
+ Slog.i(TAG, "Backup not activated for user " + userId);
+ return;
+ }
+ if (mUserServices.get(userId) != null) {
Slog.i(TAG, "userId " + userId + " already started, so not starting again");
return;
}
-
+ Slog.i(TAG, "Starting service for user: " + userId);
UserBackupManagerService userBackupManagerService =
UserBackupManagerService.createAndInitializeService(
- userId, mContext, mTrampoline, mTransportWhitelist);
+ userId, mContext, this, mTransportWhitelist);
startServiceForUser(userId, userBackupManagerService);
}
@@ -178,7 +353,7 @@ public class BackupManagerService {
* UserBackupManagerService} with this service and setting enabled state.
*/
void startServiceForUser(int userId, UserBackupManagerService userBackupManagerService) {
- mServiceUsers.put(userId, userBackupManagerService);
+ mUserServices.put(userId, userBackupManagerService);
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
userBackupManagerService.initializeBackupEnableState();
@@ -188,7 +363,7 @@ public class BackupManagerService {
/** Stops the backup service for user {@code userId} when the user is stopped. */
@VisibleForTesting
protected void stopServiceForUser(int userId) {
- UserBackupManagerService userBackupManagerService = mServiceUsers.removeReturnOld(userId);
+ UserBackupManagerService userBackupManagerService = mUserServices.removeReturnOld(userId);
if (userBackupManagerService != null) {
userBackupManagerService.tearDownService();
@@ -199,47 +374,154 @@ public class BackupManagerService {
}
/**
- * Returns a lst of users currently unlocked that have a
- * {@link UserBackupManagerService} registered.
+ * Returns a list of users currently unlocked that have a {@link UserBackupManagerService}
+ * registered.
+ *
+ * Warning: Do NOT modify returned object as it's used inside.
+ *
+ * TODO: Return a copy or only expose read-only information through other means.
*/
@VisibleForTesting
- public SparseArray<UserBackupManagerService> getServiceUsers() {
- return mServiceUsers;
+ SparseArray<UserBackupManagerService> getUserServices() {
+ return mUserServices;
}
/**
- * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}.
- * If the user is not registered with the service (either the user is locked or not eligible for
- * the backup service) then return {@code null}.
- *
- * @param userId The id of the user to retrieve its instance of {@link
- * UserBackupManagerService}.
- * @param caller A {@link String} identifying the caller for logging purposes.
- * @throws SecurityException if {@code userId} is different from the calling user id and the
- * caller does NOT have the android.permission.INTERACT_ACROSS_USERS_FULL permission.
+ * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is stopped.
+ * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low.
*/
+ void onStopUser(int userId) {
+ postToHandler(
+ () -> {
+ if (!mGlobalDisable) {
+ Slog.i(TAG, "Stopping service for user: " + userId);
+ stopServiceForUser(userId);
+ }
+ });
+ }
+
+ /** Returns {@link UserBackupManagerService} for user {@code userId}. */
@Nullable
- @VisibleForTesting
- UserBackupManagerService getServiceForUserIfCallerHasPermission(
- @UserIdInt int userId, String caller) {
- enforceCallingPermissionOnUserId(userId, caller);
- UserBackupManagerService userBackupManagerService = mServiceUsers.get(userId);
- if (userBackupManagerService == null) {
- Slog.w(TAG, "Called " + caller + " for unknown user: " + userId);
+ public UserBackupManagerService getUserService(int userId) {
+ return mUserServices.get(userId);
+ }
+
+ /**
+ * The system user and managed profiles can only be acted on by callers in the system or root
+ * processes. Other users can be acted on by callers who have both android.permission.BACKUP and
+ * android.permission.INTERACT_ACROSS_USERS_FULL permissions.
+ */
+ private void enforcePermissionsOnUser(int userId) throws SecurityException {
+ boolean isRestrictedUser =
+ userId == UserHandle.USER_SYSTEM
+ || getUserManager().getUserInfo(userId).isManagedProfile();
+
+ if (isRestrictedUser) {
+ int caller = binderGetCallingUid();
+ if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
+ throw new SecurityException("No permission to configure backup activity");
+ }
+ } else {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.BACKUP, "No permission to configure backup activity");
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "No permission to configure backup activity");
}
- return userBackupManagerService;
}
- /*
- * The following methods are implementations of IBackupManager methods called from Trampoline.
- * They delegate to the appropriate per-user instance of UserBackupManagerService to perform the
- * action on the passed in user. Currently this is a straight redirection (see TODO).
+ /**
+ * Only privileged callers should be changing the backup state. Deactivating backup in the
+ * system user also deactivates backup in all users. We are not guaranteed that {@code userId}
+ * is unlocked at this point yet, so handle both cases.
*/
- // TODO (b/118520567): Stop hardcoding system user when we pass in user id as a parameter
+ public void setBackupServiceActive(int userId, boolean makeActive) {
+ enforcePermissionsOnUser(userId);
+
+ // In Q, backup is OFF by default for non-system users. In the future, we will change that
+ // to ON unless backup was explicitly deactivated with a (permissioned) call to
+ // setBackupServiceActive.
+ // Therefore, remember this for use in the future. Basically the default in the future will
+ // be: rememberFile.exists() ? rememberFile.value() : ON
+ // Note that this has to be done right after the permission checks and before any other
+ // action since we need to remember that a permissioned call was made irrespective of
+ // whether the call changes the state or not.
+ if (userId != UserHandle.USER_SYSTEM) {
+ try {
+ File rememberFile = getRememberActivatedFileForNonSystemUser(userId);
+ createFile(rememberFile);
+ RandomAccessFileUtils.writeBoolean(rememberFile, makeActive);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to persist backup service activity", e);
+ }
+ }
- // ---------------------------------------------
- // BACKUP AGENT OPERATIONS
- // ---------------------------------------------
+ if (mGlobalDisable) {
+ Slog.i(TAG, "Backup service not supported");
+ return;
+ }
+
+ synchronized (mStateLock) {
+ Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active");
+ if (makeActive) {
+ try {
+ activateBackupForUserLocked(userId);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to persist backup service activity");
+ }
+
+ // If the user is unlocked, we can start the backup service for it. Otherwise we
+ // will start the service when the user is unlocked as part of its unlock callback.
+ if (getUserManager().isUserUnlocked(userId)) {
+ // Clear calling identity as initialization enforces the system identity but we
+ // can be coming from shell.
+ long oldId = Binder.clearCallingIdentity();
+ try {
+ startServiceForUser(userId);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+ } else {
+ try {
+ //TODO(b/121198006): what if this throws an exception?
+ deactivateBackupForUserLocked(userId);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to persist backup service inactivity");
+ }
+ //TODO(b/121198006): loop through active users that have work profile and
+ // stop them as well.
+ onStopUser(userId);
+ }
+ }
+ }
+
+ // IBackupManager binder API
+
+ /**
+ * Querying activity state of backup service.
+ *
+ * @param userId The user in which the activity state of backup service is queried.
+ * @return true if the service is active.
+ */
+ @Override
+ public boolean isBackupServiceActive(int userId) {
+ synchronized (mStateLock) {
+ return !mGlobalDisable && isBackupActivatedForUser(userId);
+ }
+ }
+
+ @Override
+ public void dataChangedForUser(int userId, String packageName) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ dataChanged(userId, packageName);
+ }
+ }
+
+ @Override
+ public void dataChanged(String packageName) throws RemoteException {
+ dataChangedForUser(binderGetCallingUserId(), packageName);
+ }
/**
* An app's backup agent calls this method to let the service know that there's new data to
@@ -255,6 +537,69 @@ public class BackupManagerService {
}
}
+ // ---------------------------------------------
+ // TRANSPORT OPERATIONS
+ // ---------------------------------------------
+
+ @Override
+ public void initializeTransportsForUser(
+ int userId, String[] transportNames, IBackupObserver observer) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ initializeTransports(userId, transportNames, observer);
+ }
+ }
+
+ /** Run an initialize operation for the given transports {@code transportNames}. */
+ public void initializeTransports(
+ @UserIdInt int userId, String[] transportNames, IBackupObserver observer) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "initializeTransports()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.initializeTransports(transportNames, observer);
+ }
+ }
+
+ @Override
+ public void clearBackupDataForUser(int userId, String transportName, String packageName)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ clearBackupData(userId, transportName, packageName);
+ }
+ }
+
+ /**
+ * Clear the given package {@code packageName}'s backup data from the transport {@code
+ * transportName}.
+ */
+ public void clearBackupData(@UserIdInt int userId, String transportName, String packageName) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "clearBackupData()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.clearBackupData(transportName, packageName);
+ }
+ }
+
+ @Override
+ public void clearBackupData(String transportName, String packageName)
+ throws RemoteException {
+ clearBackupDataForUser(binderGetCallingUserId(), transportName, packageName);
+ }
+
+ @Override
+ public void agentConnectedForUser(int userId, String packageName, IBinder agent)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ agentConnected(userId, packageName, agent);
+ }
+ }
+
+ @Override
+ public void agentConnected(String packageName, IBinder agent) throws RemoteException {
+ agentConnectedForUser(binderGetCallingUserId(), packageName, agent);
+ }
+
/**
* Callback: a requested backup agent has been instantiated. This should only be called from the
* {@link ActivityManager}.
@@ -268,6 +613,18 @@ public class BackupManagerService {
}
}
+ @Override
+ public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ agentDisconnected(userId, packageName);
+ }
+ }
+
+ @Override
+ public void agentDisconnected(String packageName) throws RemoteException {
+ agentDisconnectedForUser(binderGetCallingUserId(), packageName);
+ }
+
/**
* Callback: a backup agent has failed to come up, or has unexpectedly quit. This should only be
* called from the {@link ActivityManager}.
@@ -281,47 +638,283 @@ public class BackupManagerService {
}
}
+ @Override
+ public void restoreAtInstallForUser(int userId, String packageName, int token)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ restoreAtInstall(userId, packageName, token);
+ }
+ }
+
+ @Override
+ public void restoreAtInstall(String packageName, int token) throws RemoteException {
+ restoreAtInstallForUser(binderGetCallingUserId(), packageName, token);
+ }
+
/**
- * Used by a currently-active backup agent to notify the service that it has completed its given
- * outstanding asynchronous backup/restore operation.
+ * Used to run a restore pass for an application that is being installed. This should only be
+ * called from the {@link PackageManager}.
*/
- public void opComplete(@UserIdInt int userId, int token, long result) {
+ public void restoreAtInstall(@UserIdInt int userId, String packageName, int token) {
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "opComplete()");
+ getServiceForUserIfCallerHasPermission(userId, "restoreAtInstall()");
if (userBackupManagerService != null) {
- userBackupManagerService.opComplete(token, result);
+ userBackupManagerService.restoreAtInstall(packageName, token);
}
}
- // ---------------------------------------------
- // TRANSPORT OPERATIONS
- // ---------------------------------------------
+ @Override
+ public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ setBackupEnabled(userId, isEnabled);
+ }
+ }
- /** Run an initialize operation for the given transports {@code transportNames}. */
- public void initializeTransports(
- @UserIdInt int userId, String[] transportNames, IBackupObserver observer) {
+ @Override
+ public void setBackupEnabled(boolean isEnabled) throws RemoteException {
+ setBackupEnabledForUser(binderGetCallingUserId(), isEnabled);
+ }
+
+ /** Enable/disable the backup service. This is user-configurable via backup settings. */
+ public void setBackupEnabled(@UserIdInt int userId, boolean enable) {
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "initializeTransports()");
+ getServiceForUserIfCallerHasPermission(userId, "setBackupEnabled()");
if (userBackupManagerService != null) {
- userBackupManagerService.initializeTransports(transportNames, observer);
+ userBackupManagerService.setBackupEnabled(enable);
}
}
+ @Override
+ public void setAutoRestoreForUser(int userId, boolean doAutoRestore) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ setAutoRestore(userId, doAutoRestore);
+ }
+ }
+
+ @Override
+ public void setAutoRestore(boolean doAutoRestore) throws RemoteException {
+ setAutoRestoreForUser(binderGetCallingUserId(), doAutoRestore);
+ }
+
+ /** Enable/disable automatic restore of app data at install time. */
+ public void setAutoRestore(@UserIdInt int userId, boolean autoRestore) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "setAutoRestore()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.setAutoRestore(autoRestore);
+ }
+ }
+
+ @Override
+ public boolean isBackupEnabledForUser(@UserIdInt int userId) throws RemoteException {
+ return isUserReadyForBackup(userId) && isBackupEnabled(userId);
+ }
+
+ @Override
+ public boolean isBackupEnabled() throws RemoteException {
+ return isBackupEnabledForUser(binderGetCallingUserId());
+ }
+
/**
- * Clear the given package {@code packageName}'s backup data from the transport {@code
- * transportName}.
+ * Return {@code true} if the backup mechanism is currently enabled, else returns {@code false}.
*/
- public void clearBackupData(@UserIdInt int userId, String transportName, String packageName) {
+ public boolean isBackupEnabled(@UserIdInt int userId) {
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "clearBackupData()");
+ getServiceForUserIfCallerHasPermission(userId, "isBackupEnabled()");
+
+ return userBackupManagerService != null && userBackupManagerService.isBackupEnabled();
+ }
+
+ /** Sets the backup password used when running adb backup. */
+ @Override
+ public boolean setBackupPassword(String currentPassword, String newPassword) {
+ int userId = binderGetCallingUserId();
+ if (!isUserReadyForBackup(userId)) {
+ return false;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(
+ UserHandle.USER_SYSTEM, "setBackupPassword()");
+
+ return userBackupManagerService != null
+ && userBackupManagerService.setBackupPassword(currentPassword, newPassword);
+ }
+
+ /** Returns {@code true} if adb backup was run with a password, else returns {@code false}. */
+ @Override
+ public boolean hasBackupPassword() throws RemoteException {
+ int userId = binderGetCallingUserId();
+ if (!isUserReadyForBackup(userId)) {
+ return false;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(
+ UserHandle.USER_SYSTEM, "hasBackupPassword()");
+
+ return userBackupManagerService != null && userBackupManagerService.hasBackupPassword();
+ }
+
+ @Override
+ public void backupNowForUser(@UserIdInt int userId) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ backupNow(userId);
+ }
+ }
+
+ @Override
+ public void backupNow() throws RemoteException {
+ backupNowForUser(binderGetCallingUserId());
+ }
+
+ /**
+ * Run a backup pass immediately for any key-value backup applications that have declared that
+ * they have pending updates.
+ */
+ public void backupNow(@UserIdInt int userId) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "backupNow()");
if (userBackupManagerService != null) {
- userBackupManagerService.clearBackupData(transportName, packageName);
+ userBackupManagerService.backupNow();
+ }
+ }
+
+ /**
+ * Used by 'adb backup' to run a backup pass for packages {@code packageNames} supplied via the
+ * command line, writing the resulting data stream to the supplied {@code fd}. This method is
+ * synchronous and does not return to the caller until the backup has been completed. It
+ * requires on-screen confirmation by the user.
+ */
+ @Override
+ public void adbBackup(
+ @UserIdInt int userId,
+ ParcelFileDescriptor fd,
+ boolean includeApks,
+ boolean includeObbs,
+ boolean includeShared,
+ boolean doWidgets,
+ boolean doAllApps,
+ boolean includeSystem,
+ boolean doCompress,
+ boolean doKeyValue,
+ String[] packageNames) {
+ if (!isUserReadyForBackup(userId)) {
+ return;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "adbBackup()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.adbBackup(
+ fd,
+ includeApks,
+ includeObbs,
+ includeShared,
+ doWidgets,
+ doAllApps,
+ includeSystem,
+ doCompress,
+ doKeyValue,
+ packageNames);
+ }
+ }
+
+ @Override
+ public void fullTransportBackupForUser(int userId, String[] packageNames)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ fullTransportBackup(userId, packageNames);
+ }
+ }
+
+ /**
+ * Run a full backup pass for the given packages {@code packageNames}. Used by 'adb shell bmgr'.
+ */
+ public void fullTransportBackup(@UserIdInt int userId, String[] packageNames) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "fullTransportBackup()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.fullTransportBackup(packageNames);
+ }
+ }
+
+ /**
+ * Used by 'adb restore' to run a restore pass reading from the supplied {@code fd}. This method
+ * is synchronous and does not return to the caller until the restore has been completed. It
+ * requires on-screen confirmation by the user.
+ */
+ @Override
+ public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) {
+ if (!isUserReadyForBackup(userId)) {
+ return;
+ }
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "adbRestore()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.adbRestore(fd);
}
}
+ @Override
+ public void acknowledgeFullBackupOrRestoreForUser(
+ int userId,
+ int token,
+ boolean allow,
+ String curPassword,
+ String encryptionPassword,
+ IFullBackupRestoreObserver observer)
+ throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ acknowledgeAdbBackupOrRestore(userId, token, allow,
+ curPassword, encryptionPassword, observer);
+ }
+ }
+
+ /**
+ * Confirm that the previously requested adb backup/restore operation can proceed. This is used
+ * to require a user-facing disclosure about the operation.
+ */
+ public void acknowledgeAdbBackupOrRestore(
+ @UserIdInt int userId,
+ int token,
+ boolean allow,
+ String currentPassword,
+ String encryptionPassword,
+ IFullBackupRestoreObserver observer) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(userId, "acknowledgeAdbBackupOrRestore()");
+
+ if (userBackupManagerService != null) {
+ userBackupManagerService.acknowledgeAdbBackupOrRestore(
+ token, allow, currentPassword, encryptionPassword, observer);
+ }
+ }
+
+ @Override
+ public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword,
+ String encryptionPassword, IFullBackupRestoreObserver observer)
+ throws RemoteException {
+ acknowledgeFullBackupOrRestoreForUser(
+ binderGetCallingUserId(), token, allow, curPassword, encryptionPassword, observer);
+ }
+
+
+ @Override
+ public String getCurrentTransportForUser(int userId) throws RemoteException {
+ return (isUserReadyForBackup(userId)) ? getCurrentTransport(userId) : null;
+ }
+
+ @Override
+ public String getCurrentTransport() throws RemoteException {
+ return getCurrentTransportForUser(binderGetCallingUserId());
+ }
+
/** Return the name of the currently active transport. */
@Nullable
public String getCurrentTransport(@UserIdInt int userId) {
@@ -334,6 +927,16 @@ public class BackupManagerService {
}
/**
+ * Returns the {@link ComponentName} of the host service of the selected transport or
+ * {@code null} if no transport selected or if the transport selected is not registered.
+ */
+ @Override
+ @Nullable
+ public ComponentName getCurrentTransportComponentForUser(int userId) {
+ return (isUserReadyForBackup(userId)) ? getCurrentTransportComponent(userId) : null;
+ }
+
+ /**
* Returns the {@link ComponentName} of the host service of the selected transport or {@code
* null} if no transport selected or if the transport selected is not registered.
*/
@@ -347,6 +950,11 @@ public class BackupManagerService {
: userBackupManagerService.getCurrentTransportComponent();
}
+ @Override
+ public String[] listAllTransportsForUser(int userId) throws RemoteException {
+ return (isUserReadyForBackup(userId)) ? listAllTransports(userId) : null;
+ }
+
/** Report all known, available backup transports by name. */
@Nullable
public String[] listAllTransports(@UserIdInt int userId) {
@@ -358,6 +966,17 @@ public class BackupManagerService {
: userBackupManagerService.listAllTransports();
}
+ @Override
+ public String[] listAllTransports() throws RemoteException {
+ return listAllTransportsForUser(binderGetCallingUserId());
+ }
+
+ @Override
+ public ComponentName[] listAllTransportComponentsForUser(int userId) throws RemoteException {
+ return (isUserReadyForBackup(userId))
+ ? listAllTransportComponents(userId) : null;
+ }
+
/** Report all known, available backup transports by {@link ComponentName}. */
@Nullable
public ComponentName[] listAllTransportComponents(@UserIdInt int userId) {
@@ -369,9 +988,12 @@ public class BackupManagerService {
: userBackupManagerService.listAllTransportComponents();
}
- /** Report all system whitelisted transports. */
- @Nullable
+ @Override
public String[] getTransportWhitelist() {
+ int userId = binderGetCallingUserId();
+ if (!isUserReadyForBackup(userId)) {
+ return null;
+ }
// No permission check, intentionally.
String[] whitelistedTransports = new String[mTransportWhitelist.size()];
int i = 0;
@@ -382,6 +1004,27 @@ public class BackupManagerService {
return whitelistedTransports;
}
+ @Override
+ public void updateTransportAttributesForUser(
+ int userId,
+ ComponentName transportComponent,
+ String name,
+ @Nullable Intent configurationIntent,
+ String currentDestinationString,
+ @Nullable Intent dataManagementIntent,
+ CharSequence dataManagementLabel) {
+ if (isUserReadyForBackup(userId)) {
+ updateTransportAttributes(
+ userId,
+ transportComponent,
+ name,
+ configurationIntent,
+ currentDestinationString,
+ dataManagementIntent,
+ dataManagementLabel);
+ }
+ }
+
/**
* Update the attributes of the transport identified by {@code transportComponent}. If the
* specified transport has not been bound at least once (for registration), this call will be
@@ -427,6 +1070,18 @@ public class BackupManagerService {
}
}
+ @Override
+ public String selectBackupTransportForUser(int userId, String transport)
+ throws RemoteException {
+ return (isUserReadyForBackup(userId))
+ ? selectBackupTransport(userId, transport) : null;
+ }
+
+ @Override
+ public String selectBackupTransport(String transport) throws RemoteException {
+ return selectBackupTransportForUser(binderGetCallingUserId(), transport);
+ }
+
/**
* Selects transport {@code transportName} and returns the previously selected transport.
*
@@ -444,6 +1099,22 @@ public class BackupManagerService {
: userBackupManagerService.selectBackupTransport(transportName);
}
+ @Override
+ public void selectBackupTransportAsyncForUser(int userId, ComponentName transport,
+ ISelectBackupTransportCallback listener) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ selectBackupTransportAsync(userId, transport, listener);
+ } else {
+ if (listener != null) {
+ try {
+ listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ } catch (RemoteException ex) {
+ // ignore
+ }
+ }
+ }
+ }
+
/**
* Selects transport {@code transportComponent} asynchronously and notifies {@code listener}
* with the result upon completion.
@@ -460,6 +1131,19 @@ public class BackupManagerService {
}
}
+ @Override
+ public Intent getConfigurationIntentForUser(int userId, String transport)
+ throws RemoteException {
+ return isUserReadyForBackup(userId) ? getConfigurationIntent(userId, transport)
+ : null;
+ }
+
+ @Override
+ public Intent getConfigurationIntent(String transport)
+ throws RemoteException {
+ return getConfigurationIntentForUser(binderGetCallingUserId(), transport);
+ }
+
/**
* Supply the configuration intent for the given transport. If the name is not one of the
* available transports, or if the transport does not supply any configuration UI, the method
@@ -471,56 +1155,19 @@ public class BackupManagerService {
getServiceForUserIfCallerHasPermission(userId, "getConfigurationIntent()");
return userBackupManagerService == null
- ? null
- : userBackupManagerService.getConfigurationIntent(transportName);
+ ? null
+ : userBackupManagerService.getConfigurationIntent(transportName);
}
- /**
- * Sets the ancestral work profile for the calling user.
- *
- * <p> The ancestral work profile corresponds to the profile that was used to restore to the
- * callers profile.
- */
- public void setAncestralSerialNumber(long ancestralSerialNumber) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(
- Binder.getCallingUserHandle().getIdentifier(),
- "setAncestralSerialNumber()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.setAncestralSerialNumber(ancestralSerialNumber);
- }
+ @Override
+ public String getDestinationStringForUser(int userId, String transport) throws RemoteException {
+ return isUserReadyForBackup(userId) ? getDestinationString(userId, transport)
+ : null;
}
- /**
- * Returns a {@link UserHandle} for the user that has {@code ancestralSerialNumber} as the
- * serial number of the its ancestral work profile.
- *
- * <p> The ancestral work profile is set by {@link #setAncestralSerialNumber(long)}
- * and it corresponds to the profile that was used to restore to the callers profile.
- */
- @Nullable
- public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
- int callingUserId = Binder.getCallingUserHandle().getIdentifier();
- long oldId = Binder.clearCallingIdentity();
- int[] userIds;
- try {
- userIds = mContext.getSystemService(UserManager.class).getProfileIds(callingUserId,
- false);
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
-
- for (int userId : userIds) {
- UserBackupManagerService userBackupManagerService = getServiceUsers().get(userId);
- if (userBackupManagerService != null) {
- if (userBackupManagerService.getAncestralSerialNumber() == ancestralSerialNumber) {
- return UserHandle.of(userId);
- }
- }
- }
-
- return null;
+ @Override
+ public String getDestinationString(String transport) throws RemoteException {
+ return getDestinationStringForUser(binderGetCallingUserId(), transport);
}
/**
@@ -542,6 +1189,19 @@ public class BackupManagerService {
: userBackupManagerService.getDestinationString(transportName);
}
+ @Override
+ public Intent getDataManagementIntentForUser(int userId, String transport)
+ throws RemoteException {
+ return isUserReadyForBackup(userId)
+ ? getDataManagementIntent(userId, transport) : null;
+ }
+
+ @Override
+ public Intent getDataManagementIntent(String transport)
+ throws RemoteException {
+ return getDataManagementIntentForUser(binderGetCallingUserId(), transport);
+ }
+
/** Supply the manage-data intent for the given transport. */
@Nullable
public Intent getDataManagementIntent(@UserIdInt int userId, String transportName) {
@@ -553,6 +1213,13 @@ public class BackupManagerService {
: userBackupManagerService.getDataManagementIntent(transportName);
}
+ @Override
+ public CharSequence getDataManagementLabelForUser(int userId, String transport)
+ throws RemoteException {
+ return isUserReadyForBackup(userId) ? getDataManagementLabel(userId, transport)
+ : null;
+ }
+
/**
* Supply the menu label for affordances that fire the manage-data intent for the given
* transport.
@@ -567,43 +1234,76 @@ public class BackupManagerService {
: userBackupManagerService.getDataManagementLabel(transportName);
}
- // ---------------------------------------------
- // SETTINGS OPERATIONS
- // ---------------------------------------------
+ @Override
+ public IRestoreSession beginRestoreSessionForUser(
+ int userId, String packageName, String transportID) throws RemoteException {
+ return isUserReadyForBackup(userId)
+ ? beginRestoreSession(userId, packageName, transportID) : null;
+ }
- /** Enable/disable the backup service. This is user-configurable via backup settings. */
- public void setBackupEnabled(@UserIdInt int userId, boolean enable) {
+ /**
+ * Begin a restore for the specified package {@code packageName} using the specified transport
+ * {@code transportName}.
+ */
+ @Nullable
+ public IRestoreSession beginRestoreSession(
+ @UserIdInt int userId, String packageName, String transportName) {
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "setBackupEnabled()");
+ getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()");
- if (userBackupManagerService != null) {
- userBackupManagerService.setBackupEnabled(enable);
+ return userBackupManagerService == null
+ ? null
+ : userBackupManagerService.beginRestoreSession(packageName, transportName);
+ }
+
+ @Override
+ public void opCompleteForUser(int userId, int token, long result) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ opComplete(userId, token, result);
}
}
- /** Enable/disable automatic restore of app data at install time. */
- public void setAutoRestore(@UserIdInt int userId, boolean autoRestore) {
+ @Override
+ public void opComplete(int token, long result) throws RemoteException {
+ opCompleteForUser(binderGetCallingUserId(), token, result);
+ }
+
+ /**
+ * Used by a currently-active backup agent to notify the service that it has completed its given
+ * outstanding asynchronous backup/restore operation.
+ */
+ public void opComplete(@UserIdInt int userId, int token, long result) {
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "setAutoRestore()");
+ getServiceForUserIfCallerHasPermission(userId, "opComplete()");
if (userBackupManagerService != null) {
- userBackupManagerService.setAutoRestore(autoRestore);
+ userBackupManagerService.opComplete(token, result);
}
}
+ @Override
+ public long getAvailableRestoreTokenForUser(int userId, String packageName) {
+ return isUserReadyForBackup(userId) ? getAvailableRestoreToken(userId, packageName) : 0;
+ }
+
/**
- * Return {@code true} if the backup mechanism is currently enabled, else returns {@code false}.
+ * Get the restore-set token for the best-available restore set for this {@code packageName}:
+ * the active set if possible, else the ancestral one. Returns zero if none available.
*/
- public boolean isBackupEnabled(@UserIdInt int userId) {
+ public long getAvailableRestoreToken(@UserIdInt int userId, String packageName) {
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "isBackupEnabled()");
+ getServiceForUserIfCallerHasPermission(userId, "getAvailableRestoreToken()");
- return userBackupManagerService != null && userBackupManagerService.isBackupEnabled();
+ return userBackupManagerService == null
+ ? 0
+ : userBackupManagerService.getAvailableRestoreToken(packageName);
}
- // ---------------------------------------------
- // BACKUP OPERATIONS
- // ---------------------------------------------
+ @Override
+ public boolean isAppEligibleForBackupForUser(int userId, String packageName) {
+ return isUserReadyForBackup(userId) && isAppEligibleForBackup(userId,
+ packageName);
+ }
/** Checks if the given package {@code packageName} is eligible for backup. */
public boolean isAppEligibleForBackup(@UserIdInt int userId, String packageName) {
@@ -614,6 +1314,11 @@ public class BackupManagerService {
&& userBackupManagerService.isAppEligibleForBackup(packageName);
}
+ @Override
+ public String[] filterAppsEligibleForBackupForUser(int userId, String[] packages) {
+ return isUserReadyForBackup(userId) ? filterAppsEligibleForBackup(userId, packages) : null;
+ }
+
/**
* Returns from the inputted packages {@code packages}, the ones that are eligible for backup.
*/
@@ -627,17 +1332,20 @@ public class BackupManagerService {
: userBackupManagerService.filterAppsEligibleForBackup(packages);
}
- /**
- * Run a backup pass immediately for any key-value backup applications that have declared that
- * they have pending updates.
- */
- public void backupNow(@UserIdInt int userId) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "backupNow()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.backupNow();
+ @Override
+ public int requestBackupForUser(@UserIdInt int userId, String[] packages, IBackupObserver
+ observer, IBackupManagerMonitor monitor, int flags) throws RemoteException {
+ if (!isUserReadyForBackup(userId)) {
+ return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
}
+ return requestBackup(userId, packages, observer, monitor, flags);
+ }
+
+ @Override
+ public int requestBackup(String[] packages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, int flags) throws RemoteException {
+ return requestBackupForUser(binderGetCallingUserId(), packages,
+ observer, monitor, flags);
}
/**
@@ -658,6 +1366,18 @@ public class BackupManagerService {
: userBackupManagerService.requestBackup(packages, observer, monitor, flags);
}
+ @Override
+ public void cancelBackupsForUser(@UserIdInt int userId) throws RemoteException {
+ if (isUserReadyForBackup(userId)) {
+ cancelBackups(userId);
+ }
+ }
+
+ @Override
+ public void cancelBackups() throws RemoteException {
+ cancelBackupsForUser(binderGetCallingUserId());
+ }
+
/** Cancel all running backup operations. */
public void cancelBackups(@UserIdInt int userId) {
UserBackupManagerService userBackupManagerService =
@@ -669,238 +1389,230 @@ public class BackupManagerService {
}
/**
- * Used by the {@link JobScheduler} to run a full backup when conditions are right. The model we
- * use is to perform one app backup per scheduled job execution, and to reschedule the job with
- * zero latency as long as conditions remain right and we still have work to do.
+ * Returns a {@link UserHandle} for the user that has {@code ancestralSerialNumber} as the
+ * serial number of its ancestral work profile or null if there is no {@link
+ * UserBackupManagerService} associated with that user.
*
- * @return Whether ongoing work will continue. The return value here will be passed along as the
- * return value to the callback {@link JobService#onStartJob(JobParameters)}.
+ * <p> The ancestral work profile is set by {@link #setAncestralSerialNumber(long)}
+ * and it corresponds to the profile that was used to restore to the callers profile.
*/
- public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()");
+ @Override
+ @Nullable
+ public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
+ if (mGlobalDisable) {
+ return null;
+ }
+ int callingUserId = Binder.getCallingUserHandle().getIdentifier();
+ long oldId = Binder.clearCallingIdentity();
+ final int[] userIds;
+ try {
+ userIds = getUserManager().getProfileIds(callingUserId, false);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
- return userBackupManagerService != null
- && userBackupManagerService.beginFullBackup(scheduledJob);
+ for (int userId : userIds) {
+ UserBackupManagerService userBackupManagerService = mUserServices.get(userId);
+ if (userBackupManagerService != null) {
+ if (userBackupManagerService.getAncestralSerialNumber() == ancestralSerialNumber) {
+ return UserHandle.of(userId);
+ }
+ }
+ }
+
+ return null;
}
/**
- * Used by the {@link JobScheduler} to end the current full backup task when conditions are no
- * longer met for running the full backup job.
+ * Sets the ancestral work profile for the calling user.
+ *
+ * <p> The ancestral work profile corresponds to the profile that was used to restore to the
+ * callers profile.
*/
- public void endFullBackup(@UserIdInt int userId) {
+ @Override
+ public void setAncestralSerialNumber(long ancestralSerialNumber) {
+ if (mGlobalDisable) {
+ return;
+ }
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "endFullBackup()");
+ getServiceForUserIfCallerHasPermission(
+ Binder.getCallingUserHandle().getIdentifier(),
+ "setAncestralSerialNumber()");
if (userBackupManagerService != null) {
- userBackupManagerService.endFullBackup();
+ userBackupManagerService.setAncestralSerialNumber(ancestralSerialNumber);
}
}
- /**
- * Run a full backup pass for the given packages {@code packageNames}. Used by 'adb shell bmgr'.
- */
- public void fullTransportBackup(@UserIdInt int userId, String[] packageNames) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "fullTransportBackup()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.fullTransportBackup(packageNames);
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+ return;
}
+ dumpWithoutCheckingPermission(fd, pw, args);
}
- // ---------------------------------------------
- // RESTORE OPERATIONS
- // ---------------------------------------------
-
- /**
- * Used to run a restore pass for an application that is being installed. This should only be
- * called from the {@link PackageManager}.
- */
- public void restoreAtInstall(@UserIdInt int userId, String packageName, int token) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "restoreAtInstall()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.restoreAtInstall(packageName, token);
+ @VisibleForTesting
+ void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) {
+ int userId = binderGetCallingUserId();
+ if (!isUserReadyForBackup(userId)) {
+ pw.println("Inactive");
+ return;
}
- }
- /**
- * Begin a restore for the specified package {@code packageName} using the specified transport
- * {@code transportName}.
- */
- @Nullable
- public IRestoreSession beginRestoreSession(
- @UserIdInt int userId, String packageName, String transportName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()");
+ if (args != null) {
+ for (String arg : args) {
+ if ("-h".equals(arg)) {
+ pw.println("'dumpsys backup' optional arguments:");
+ pw.println(" -h : this help text");
+ pw.println(" a[gents] : dump information about defined backup agents");
+ pw.println(" transportclients : dump information about transport clients");
+ pw.println(" transportstats : dump transport statts");
+ pw.println(" users : dump the list of users for which backup service "
+ + "is running");
+ return;
+ } else if ("users".equals(arg.toLowerCase())) {
+ pw.print(DUMP_RUNNING_USERS_MESSAGE);
+ for (int i = 0; i < mUserServices.size(); i++) {
+ pw.print(" " + mUserServices.keyAt(i));
+ }
+ pw.println();
+ return;
+ }
+ }
+ }
- return userBackupManagerService == null
- ? null
- : userBackupManagerService.beginRestoreSession(packageName, transportName);
+ for (int i = 0; i < mUserServices.size(); i++) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+ if (userBackupManagerService != null) {
+ userBackupManagerService.dump(fd, pw, args);
+ }
+ }
}
/**
- * Get the restore-set token for the best-available restore set for this {@code packageName}:
- * the active set if possible, else the ancestral one. Returns zero if none available.
+ * Used by the {@link JobScheduler} to run a full backup when conditions are right. The model we
+ * use is to perform one app backup per scheduled job execution, and to reschedule the job with
+ * zero latency as long as conditions remain right and we still have work to do.
+ *
+ * @return Whether ongoing work will continue. The return value here will be passed along as the
+ * return value to the callback {@link JobService#onStartJob(JobParameters)}.
*/
- public long getAvailableRestoreToken(@UserIdInt int userId, String packageName) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "getAvailableRestoreToken()");
-
- return userBackupManagerService == null
- ? 0
- : userBackupManagerService.getAvailableRestoreToken(packageName);
- }
-
- // ---------------------------------------------
- // ADB BACKUP/RESTORE OPERATIONS
- // ---------------------------------------------
-
- /** Sets the backup password used when running adb backup. */
- public boolean setBackupPassword(String currentPassword, String newPassword) {
+ public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
+ if (!isUserReadyForBackup(userId)) {
+ return false;
+ }
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(
- UserHandle.USER_SYSTEM, "setBackupPassword()");
+ getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()");
return userBackupManagerService != null
- && userBackupManagerService.setBackupPassword(currentPassword, newPassword);
- }
-
- /** Returns {@code true} if adb backup was run with a password, else returns {@code false}. */
- public boolean hasBackupPassword() {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(
- UserHandle.USER_SYSTEM, "hasBackupPassword()");
-
- return userBackupManagerService != null && userBackupManagerService.hasBackupPassword();
+ && userBackupManagerService.beginFullBackup(scheduledJob);
}
/**
- * Used by 'adb backup' to run a backup pass for packages {@code packageNames} supplied via the
- * command line, writing the resulting data stream to the supplied {@code fd}. This method is
- * synchronous and does not return to the caller until the backup has been completed. It
- * requires on-screen confirmation by the user.
+ * Used by the {@link JobScheduler} to end the current full backup task when conditions are no
+ * longer met for running the full backup job.
*/
- public void adbBackup(
- @UserIdInt int userId,
- ParcelFileDescriptor fd,
- boolean includeApks,
- boolean includeObbs,
- boolean includeShared,
- boolean doWidgets,
- boolean doAllApps,
- boolean includeSystem,
- boolean doCompress,
- boolean doKeyValue,
- String[] packageNames) {
+ public void endFullBackup(@UserIdInt int userId) {
+ if (!isUserReadyForBackup(userId)) {
+ return;
+ }
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "adbBackup()");
+ getServiceForUserIfCallerHasPermission(userId, "endFullBackup()");
if (userBackupManagerService != null) {
- userBackupManagerService.adbBackup(
- fd,
- includeApks,
- includeObbs,
- includeShared,
- doWidgets,
- doAllApps,
- includeSystem,
- doCompress,
- doKeyValue,
- packageNames);
+ userBackupManagerService.endFullBackup();
}
}
/**
- * Used by 'adb restore' to run a restore pass reading from the supplied {@code fd}. This method
- * is synchronous and does not return to the caller until the restore has been completed. It
- * requires on-screen confirmation by the user.
+ * Excludes keys from KV restore for a given package. The corresponding data will be excluded
+ * from the data set available the backup agent during restore. However, final list of keys
+ * that have been excluded will be passed to the agent to make it aware of the exclusions.
*/
- public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) {
+ public void excludeKeysFromRestore(String packageName, List<String> keys) {
+ int userId = Binder.getCallingUserHandle().getIdentifier();
+ if (!isUserReadyForBackup(userId)) {
+ Slog.w(TAG, "Returning from excludeKeysFromRestore as backup for user" + userId +
+ " is not initialized yet");
+ return;
+ }
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "adbRestore()");
+ getServiceForUserIfCallerHasPermission(userId, "excludeKeysFromRestore()");
if (userBackupManagerService != null) {
- userBackupManagerService.adbRestore(fd);
+ userBackupManagerService.excludeKeysFromRestore(packageName, keys);
}
}
/**
- * Confirm that the previously requested adb backup/restore operation can proceed. This is used
- * to require a user-facing disclosure about the operation.
+ * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}.
+ * If the user is not registered with the service (either the user is locked or not eligible for
+ * the backup service) then return {@code null}.
+ *
+ * @param userId The id of the user to retrieve its instance of {@link
+ * UserBackupManagerService}.
+ * @param caller A {@link String} identifying the caller for logging purposes.
+ * @throws SecurityException if {@code userId} is different from the calling user id and the
+ * caller does NOT have the android.permission.INTERACT_ACROSS_USERS_FULL permission.
*/
- public void acknowledgeAdbBackupOrRestore(
- @UserIdInt int userId,
- int token,
- boolean allow,
- String currentPassword,
- String encryptionPassword,
- IFullBackupRestoreObserver observer) {
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(userId, "acknowledgeAdbBackupOrRestore()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.acknowledgeAdbBackupOrRestore(
- token, allow, currentPassword, encryptionPassword, observer);
+ @Nullable
+ @VisibleForTesting
+ UserBackupManagerService getServiceForUserIfCallerHasPermission(
+ @UserIdInt int userId, String caller) {
+ enforceCallingPermissionOnUserId(userId, caller);
+ UserBackupManagerService userBackupManagerService = mUserServices.get(userId);
+ if (userBackupManagerService == null) {
+ Slog.w(TAG, "Called " + caller + " for unknown user: " + userId);
}
+ return userBackupManagerService;
}
- // ---------------------------------------------
- // SERVICE OPERATIONS
- // ---------------------------------------------
-
- /** Prints service state for 'dumpsys backup'. */
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
- return;
- }
-
- if (args != null) {
- for (String arg : args) {
- if ("users".equals(arg.toLowerCase())) {
- pw.print(DUMP_RUNNING_USERS_MESSAGE);
- for (int i = 0; i < mServiceUsers.size(); i++) {
- pw.print(" " + mServiceUsers.keyAt(i));
- }
- pw.println();
- return;
- }
- }
- }
-
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.dump(fd, pw, args);
+ /**
+ * If {@code userId} is different from the calling user id, then the caller must hold the
+ * android.permission.INTERACT_ACROSS_USERS_FULL permission.
+ *
+ * @param userId User id on which the backup operation is being requested.
+ * @param message A message to include in the exception if it is thrown.
+ */
+ void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) {
+ if (Binder.getCallingUserHandle().getIdentifier() != userId) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
}
}
/** Implementation to receive lifecycle event callbacks for system services. */
- public static final class Lifecycle extends SystemService {
+ public static class Lifecycle extends SystemService {
public Lifecycle(Context context) {
+ this(context, new BackupManagerService(context));
+ }
+
+ @VisibleForTesting
+ Lifecycle(Context context, BackupManagerService backupManagerService) {
super(context);
- sInstance = new Trampoline(context);
+ sInstance = backupManagerService;
}
@Override
public void onStart() {
- publishBinderService(Context.BACKUP_SERVICE, sInstance);
+ publishService(Context.BACKUP_SERVICE, BackupManagerService.sInstance);
}
@Override
public void onUnlockUser(int userId) {
- if (userId == UserHandle.USER_SYSTEM) {
- sInstance.initializeService();
- }
- sInstance.unlockUser(userId);
+ sInstance.onUnlockUser(userId);
}
@Override
public void onStopUser(int userId) {
- sInstance.stopUser(userId);
+ sInstance.onStopUser(userId);
+ }
+
+ @VisibleForTesting
+ void publishService(String name, IBinder service) {
+ publishBinderService(name, service);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/DataChangedJournal.java b/services/backup/java/com/android/server/backup/DataChangedJournal.java
index 498185c9a645..e75eb731a73e 100644
--- a/services/backup/java/com/android/server/backup/DataChangedJournal.java
+++ b/services/backup/java/com/android/server/backup/DataChangedJournal.java
@@ -17,6 +17,7 @@
package com.android.server.backup;
import android.annotation.Nullable;
+import android.util.Slog;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
@@ -36,6 +37,7 @@ import java.util.function.Consumer;
* reboot.
*/
public class DataChangedJournal {
+ private static final String TAG = "DataChangedJournal";
private static final String FILE_NAME_PREFIX = "journal";
/**
@@ -139,7 +141,12 @@ public class DataChangedJournal {
*/
static ArrayList<DataChangedJournal> listJournals(File journalDirectory) {
ArrayList<DataChangedJournal> journals = new ArrayList<>();
- for (File file : journalDirectory.listFiles()) {
+ File[] journalFiles = journalDirectory.listFiles();
+ if (journalFiles == null) {
+ Slog.w(TAG, "Failed to read journal files");
+ return journals;
+ }
+ for (File file : journalFiles) {
journals.add(new DataChangedJournal(file));
}
return journals;
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index f62a87517973..0bb25e360f15 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -16,6 +16,8 @@
package com.android.server.backup;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -37,7 +39,7 @@ public class FullBackupJob extends JobService {
public static final int MAX_JOB_ID = 52419896;
private static ComponentName sIdleService =
- new ComponentName("android", FullBackupJob.class.getName());
+ new ComponentName(PLATFORM_PACKAGE_NAME, FullBackupJob.class.getName());
@GuardedBy("mParamsForUser")
private final SparseArray<JobParameters> mParamsForUser = new SparseArray<>();
@@ -89,7 +91,7 @@ public class FullBackupJob extends JobService {
mParamsForUser.put(userId, params);
}
- Trampoline service = BackupManagerService.getInstance();
+ BackupManagerService service = BackupManagerService.getInstance();
return service.beginFullBackup(userId, this);
}
@@ -103,7 +105,7 @@ public class FullBackupJob extends JobService {
}
}
- Trampoline service = BackupManagerService.getInstance();
+ BackupManagerService service = BackupManagerService.getInstance();
service.endFullBackup(userId);
return false;
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 92c2ee4c4e71..c9b09e31f94b 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -21,7 +21,6 @@ import android.os.RemoteException;
import android.os.SELinux;
import android.util.Slog;
-import com.android.internal.util.Preconditions;
import com.android.server.backup.fullbackup.AppMetadataBackupWriter;
import com.android.server.backup.remote.ServiceBackupCallback;
import com.android.server.backup.utils.FullBackupUtils;
@@ -33,6 +32,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.Objects;
/**
* Used by BackupManagerService to perform adb backup for key-value packages. At the moment this
@@ -87,7 +87,7 @@ public class KeyValueAdbBackupEngine {
pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
mManifestFile = new File(mDataDir, BACKUP_MANIFEST_FILENAME);
- mAgentTimeoutParameters = Preconditions.checkNotNull(
+ mAgentTimeoutParameters = Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
}
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index 72d81d336e91..058dcae3102f 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -17,6 +17,7 @@
package com.android.server.backup;
import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.app.AlarmManager;
import android.app.job.JobInfo;
@@ -43,7 +44,7 @@ import java.util.Random;
public class KeyValueBackupJob extends JobService {
private static final String TAG = "KeyValueBackupJob";
private static ComponentName sKeyValueJobService =
- new ComponentName("android", KeyValueBackupJob.class.getName());
+ new ComponentName(PLATFORM_PACKAGE_NAME, KeyValueBackupJob.class.getName());
private static final String USER_ID_EXTRA_KEY = "userId";
@@ -143,7 +144,7 @@ public class KeyValueBackupJob extends JobService {
}
// Time to run a key/value backup!
- Trampoline service = BackupManagerService.getInstance();
+ BackupManagerService service = BackupManagerService.getInstance();
try {
service.backupNowForUser(userId);
} catch (RemoteException e) {}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
deleted file mode 100644
index a9b292b37903..000000000000
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ /dev/null
@@ -1,823 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup;
-
-import static com.android.server.backup.BackupManagerService.TAG;
-
-import android.Manifest;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.admin.DevicePolicyManager;
-import android.app.backup.BackupManager;
-import android.app.backup.IBackupManager;
-import android.app.backup.IBackupManagerMonitor;
-import android.app.backup.IBackupObserver;
-import android.app.backup.IFullBackupRestoreObserver;
-import android.app.backup.IRestoreSession;
-import android.app.backup.ISelectBackupTransportCallback;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.DumpUtils;
-import com.android.server.backup.utils.RandomAccessFileUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.PrintWriter;
-
-/**
- * A proxy to the {@link BackupManagerService} implementation.
- *
- * <p>This is an external interface to the {@link BackupManagerService} which is being accessed via
- * published binder {@link BackupManagerService.Lifecycle}. This lets us turn down the heavy
- * implementation object on the fly without disturbing binders that have been cached somewhere in
- * the system.
- *
- * <p>Trampoline determines whether the backup service is available. It can be disabled in the
- * following two ways:
- *
- * <ul>
- * <li>Temporary - create the file {@link #BACKUP_SUPPRESS_FILENAME}, or
- * <li>Permanent - set the system property {@link #BACKUP_DISABLE_PROPERTY} to true.
- * </ul>
- *
- * Temporary disabling is controlled by {@link #setBackupServiceActive(int, boolean)} through
- * privileged callers (currently {@link DevicePolicyManager}). This is called on {@link
- * UserHandle#USER_SYSTEM} and disables backup for all users.
- *
- * <p>Creation of the backup service is done when {@link UserHandle#USER_SYSTEM} is unlocked. The
- * system user is unlocked before any other users.
- */
-public class Trampoline extends IBackupManager.Stub {
- /**
- * Name of file that disables the backup service. If this file exists, then backup is disabled
- * for all users.
- */
- private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";
-
- /**
- * Name of file for non-system users that enables the backup service for the user. Backup is
- * disabled by default in non-system users.
- */
- private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated";
-
- /**
- * Name of file for non-system users that remembers whether backup was explicitly activated or
- * deactivated with a call to setBackupServiceActive.
- */
- private static final String REMEMBER_ACTIVATED_FILENAME = "backup-remember-activated";
-
- // Product-level suppression of backup/restore.
- private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
-
- private static final String BACKUP_THREAD = "backup";
-
- private final Context mContext;
- private final UserManager mUserManager;
-
- private final boolean mGlobalDisable;
- // Lock to write backup suppress files.
- // TODD(b/121198006): remove this object and synchronized all methods on "this".
- private final Object mStateLock = new Object();
-
- private volatile BackupManagerService mService;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
-
- public Trampoline(Context context) {
- mContext = context;
- mGlobalDisable = isBackupDisabled();
- mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
- mUserManager = UserManager.get(context);
- }
-
- protected boolean isBackupDisabled() {
- return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
- }
-
- protected int binderGetCallingUserId() {
- return Binder.getCallingUserHandle().getIdentifier();
- }
-
- protected int binderGetCallingUid() {
- return Binder.getCallingUid();
- }
-
- /** Stored in the system user's directory. */
- protected File getSuppressFileForSystemUser() {
- return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
- BACKUP_SUPPRESS_FILENAME);
- }
-
- /** Stored in the system user's directory and the file is indexed by the user it refers to. */
- protected File getRememberActivatedFileForNonSystemUser(int userId) {
- return UserBackupManagerFiles.getStateFileInSystemDir(REMEMBER_ACTIVATED_FILENAME, userId);
- }
-
- /** Stored in the system user's directory and the file is indexed by the user it refers to. */
- protected File getActivatedFileForNonSystemUser(int userId) {
- return UserBackupManagerFiles.getStateFileInSystemDir(BACKUP_ACTIVATED_FILENAME, userId);
- }
-
- // TODO (b/124359804) move to util method in FileUtils
- private void createFile(File file) throws IOException {
- if (file.exists()) {
- return;
- }
-
- file.getParentFile().mkdirs();
- if (!file.createNewFile()) {
- Slog.w(TAG, "Failed to create file " + file.getPath());
- }
- }
-
- // TODO (b/124359804) move to util method in FileUtils
- private void deleteFile(File file) {
- if (!file.exists()) {
- return;
- }
-
- if (!file.delete()) {
- Slog.w(TAG, "Failed to delete file " + file.getPath());
- }
- }
-
- /**
- * Deactivates the backup service for user {@code userId}. If this is the system user, it
- * creates a suppress file which disables backup for all users. If this is a non-system user, it
- * only deactivates backup for that user by deleting its activate file.
- */
- @GuardedBy("mStateLock")
- private void deactivateBackupForUserLocked(int userId) throws IOException {
- if (userId == UserHandle.USER_SYSTEM) {
- createFile(getSuppressFileForSystemUser());
- } else {
- deleteFile(getActivatedFileForNonSystemUser(userId));
- }
- }
-
- /**
- * Enables the backup service for user {@code userId}. If this is the system user, it deletes
- * the suppress file. If this is a non-system user, it creates the user's activate file. Note,
- * deleting the suppress file does not automatically enable backup for non-system users, they
- * need their own activate file in order to participate in the service.
- */
- @GuardedBy("mStateLock")
- private void activateBackupForUserLocked(int userId) throws IOException {
- if (userId == UserHandle.USER_SYSTEM) {
- deleteFile(getSuppressFileForSystemUser());
- } else {
- createFile(getActivatedFileForNonSystemUser(userId));
- }
- }
-
- // A user is ready for a backup if it's unlocked and is not suppressed by a device
- // admin (device owner or profile owner).
- private boolean isUserReadyForBackup(int userId) {
- return mService != null && mService.getServiceUsers().get(userId) != null
- && isBackupActivatedForUser(userId);
- }
-
- /**
- * Backup is activated for the system user if the suppress file does not exist. Backup is
- * activated for non-system users if the suppress file does not exist AND the user's activated
- * file exists.
- */
- private boolean isBackupActivatedForUser(int userId) {
- if (getSuppressFileForSystemUser().exists()) {
- return false;
- }
-
- return userId == UserHandle.USER_SYSTEM
- || getActivatedFileForNonSystemUser(userId).exists();
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- protected UserManager getUserManager() {
- return mUserManager;
- }
-
- protected BackupManagerService createBackupManagerService() {
- return new BackupManagerService(mContext, this, mHandlerThread);
- }
-
- protected void postToHandler(Runnable runnable) {
- mHandler.post(runnable);
- }
-
- /**
- * Called from {@link BackupManagerService.Lifecycle} when the system user is unlocked. Attempts
- * to initialize {@link BackupManagerService}. Offloads work onto the handler thread {@link
- * #mHandlerThread} to keep unlock time low.
- */
- void initializeService() {
- postToHandler(
- () -> {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
- if (mGlobalDisable) {
- Slog.i(TAG, "Backup service not supported");
- return;
- }
- synchronized (mStateLock) {
- if (mService == null) {
- mService = createBackupManagerService();
- }
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- });
- }
-
- /**
- * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked.
- * Starts the backup service for this user if backup is active for this user. Offloads work onto
- * the handler thread {@link #mHandlerThread} to keep unlock time low.
- */
- void unlockUser(int userId) {
- postToHandler(() -> startServiceForUser(userId));
- }
-
- private void startServiceForUser(int userId) {
- // We know that the user is unlocked here because it is called from setBackupServiceActive
- // and unlockUser which have these guarantees. So we can check if the file exists.
- if (mService != null && isBackupActivatedForUser(userId)) {
- Slog.i(TAG, "Starting service for user: " + userId);
- mService.startServiceForUser(userId);
- }
- }
-
- /**
- * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is stopped.
- * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low.
- */
- void stopUser(int userId) {
- postToHandler(
- () -> {
- if (mService != null) {
- Slog.i(TAG, "Stopping service for user: " + userId);
- mService.stopServiceForUser(userId);
- }
- });
- }
-
- /**
- * The system user and managed profiles can only be acted on by callers in the system or root
- * processes. Other users can be acted on by callers who have both android.permission.BACKUP and
- * android.permission.INTERACT_ACROSS_USERS_FULL permissions.
- */
- private void enforcePermissionsOnUser(int userId) throws SecurityException {
- boolean isRestrictedUser =
- userId == UserHandle.USER_SYSTEM
- || getUserManager().getUserInfo(userId).isManagedProfile();
-
- if (isRestrictedUser) {
- int caller = binderGetCallingUid();
- if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
- throw new SecurityException("No permission to configure backup activity");
- }
- } else {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.BACKUP, "No permission to configure backup activity");
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "No permission to configure backup activity");
- }
- }
-
- /**
- * Only privileged callers should be changing the backup state. Deactivating backup in the
- * system user also deactivates backup in all users. We are not guaranteed that {@code userId}
- * is unlocked at this point yet, so handle both cases.
- */
- public void setBackupServiceActive(int userId, boolean makeActive) {
- enforcePermissionsOnUser(userId);
-
- // In Q, backup is OFF by default for non-system users. In the future, we will change that
- // to ON unless backup was explicitly deactivated with a (permissioned) call to
- // setBackupServiceActive.
- // Therefore, remember this for use in the future. Basically the default in the future will
- // be: rememberFile.exists() ? rememberFile.value() : ON
- // Note that this has to be done right after the permission checks and before any other
- // action since we need to remember that a permissioned call was made irrespective of
- // whether the call changes the state or not.
- if (userId != UserHandle.USER_SYSTEM) {
- try {
- File rememberFile = getRememberActivatedFileForNonSystemUser(userId);
- createFile(rememberFile);
- RandomAccessFileUtils.writeBoolean(rememberFile, makeActive);
- } catch (IOException e) {
- Slog.e(TAG, "Unable to persist backup service activity", e);
- }
- }
-
- if (mGlobalDisable) {
- Slog.i(TAG, "Backup service not supported");
- return;
- }
-
- synchronized (mStateLock) {
- Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active");
- if (makeActive) {
- if (mService == null) {
- mService = createBackupManagerService();
- }
- try {
- activateBackupForUserLocked(userId);
- } catch (IOException e) {
- Slog.e(TAG, "Unable to persist backup service activity");
- }
-
- // If the user is unlocked, we can start the backup service for it. Otherwise we
- // will start the service when the user is unlocked as part of its unlock callback.
- if (getUserManager().isUserUnlocked(userId)) {
- // Clear calling identity as initialization enforces the system identity but we
- // can be coming from shell.
- long oldId = Binder.clearCallingIdentity();
- try {
- startServiceForUser(userId);
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
- }
- } else {
- try {
- //TODO(b/121198006): what if this throws an exception?
- deactivateBackupForUserLocked(userId);
- } catch (IOException e) {
- Slog.e(TAG, "Unable to persist backup service inactivity");
- }
- //TODO(b/121198006): loop through active users that have work profile and
- // stop them as well.
- stopUser(userId);
- }
- }
- }
-
- // IBackupManager binder API
-
- /**
- * Querying activity state of backup service. Calling this method before initialize yields
- * undefined result.
- *
- * @param userId The user in which the activity state of backup service is queried.
- * @return true if the service is active.
- */
- @Override
- public boolean isBackupServiceActive(int userId) {
- synchronized (mStateLock) {
- return mService != null && isBackupActivatedForUser(userId);
- }
- }
-
- @Override
- public void dataChangedForUser(int userId, String packageName) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.dataChanged(userId, packageName);
- }
- }
-
- @Override
- public void dataChanged(String packageName) throws RemoteException {
- dataChangedForUser(binderGetCallingUserId(), packageName);
- }
-
- @Override
- public void initializeTransportsForUser(
- int userId, String[] transportNames, IBackupObserver observer) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.initializeTransports(userId, transportNames, observer);
- }
- }
-
- @Override
- public void clearBackupDataForUser(int userId, String transportName, String packageName)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.clearBackupData(userId, transportName, packageName);
- }
- }
-
- @Override
- public void clearBackupData(String transportName, String packageName)
- throws RemoteException {
- clearBackupDataForUser(binderGetCallingUserId(), transportName, packageName);
- }
-
- @Override
- public void agentConnectedForUser(int userId, String packageName, IBinder agent)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.agentConnected(userId, packageName, agent);
- }
- }
-
- @Override
- public void agentConnected(String packageName, IBinder agent) throws RemoteException {
- agentConnectedForUser(binderGetCallingUserId(), packageName, agent);
- }
-
- @Override
- public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.agentDisconnected(userId, packageName);
- }
- }
-
- @Override
- public void agentDisconnected(String packageName) throws RemoteException {
- agentDisconnectedForUser(binderGetCallingUserId(), packageName);
- }
-
- @Override
- public void restoreAtInstallForUser(int userId, String packageName, int token)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.restoreAtInstall(userId, packageName, token);
- }
- }
-
- @Override
- public void restoreAtInstall(String packageName, int token) throws RemoteException {
- restoreAtInstallForUser(binderGetCallingUserId(), packageName, token);
- }
-
- @Override
- public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.setBackupEnabled(userId, isEnabled);
- }
- }
-
- @Override
- public void setBackupEnabled(boolean isEnabled) throws RemoteException {
- setBackupEnabledForUser(binderGetCallingUserId(), isEnabled);
- }
-
- @Override
- public void setAutoRestoreForUser(int userId, boolean doAutoRestore) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.setAutoRestore(userId, doAutoRestore);
- }
- }
-
- @Override
- public void setAutoRestore(boolean doAutoRestore) throws RemoteException {
- setAutoRestoreForUser(binderGetCallingUserId(), doAutoRestore);
- }
-
- @Override
- public boolean isBackupEnabledForUser(@UserIdInt int userId) throws RemoteException {
- return isUserReadyForBackup(userId) && mService.isBackupEnabled(userId);
- }
-
- @Override
- public boolean isBackupEnabled() throws RemoteException {
- return isBackupEnabledForUser(binderGetCallingUserId());
- }
-
- @Override
- public boolean setBackupPassword(String currentPw, String newPw) throws RemoteException {
- int userId = binderGetCallingUserId();
- return (isUserReadyForBackup(userId)) && mService.setBackupPassword(currentPw, newPw);
- }
-
- @Override
- public boolean hasBackupPassword() throws RemoteException {
- int userId = binderGetCallingUserId();
- return (isUserReadyForBackup(userId)) && mService.hasBackupPassword();
- }
-
- @Override
- public void backupNowForUser(@UserIdInt int userId) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.backupNow(userId);
- }
- }
-
- @Override
- public void backupNow() throws RemoteException {
- backupNowForUser(binderGetCallingUserId());
- }
-
- public void adbBackup(@UserIdInt int userId, ParcelFileDescriptor fd,
- boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
- boolean allApps, boolean allIncludesSystem, boolean doCompress, boolean doKeyValue,
- String[] packageNames) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.adbBackup(userId, fd, includeApks, includeObbs, includeShared, doWidgets,
- allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
- }
- }
-
- @Override
- public void fullTransportBackupForUser(int userId, String[] packageNames)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.fullTransportBackup(userId, packageNames);
- }
- }
-
- @Override
- public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.adbRestore(userId, fd);
- }
- }
-
- @Override
- public void acknowledgeFullBackupOrRestoreForUser(
- int userId,
- int token,
- boolean allow,
- String curPassword,
- String encryptionPassword,
- IFullBackupRestoreObserver observer)
- throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.acknowledgeAdbBackupOrRestore(userId, token, allow,
- curPassword, encryptionPassword, observer);
- }
- }
-
- @Override
- public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword,
- String encryptionPassword, IFullBackupRestoreObserver observer)
- throws RemoteException {
- acknowledgeFullBackupOrRestoreForUser(
- binderGetCallingUserId(), token, allow, curPassword, encryptionPassword, observer);
- }
-
-
- @Override
- public String getCurrentTransportForUser(int userId) throws RemoteException {
- return (isUserReadyForBackup(userId)) ? mService.getCurrentTransport(userId) : null;
- }
-
- @Override
- public String getCurrentTransport() throws RemoteException {
- return getCurrentTransportForUser(binderGetCallingUserId());
- }
-
- /**
- * Returns the {@link ComponentName} of the host service of the selected transport or
- * {@code null} if no transport selected or if the transport selected is not registered.
- */
- @Override
- @Nullable
- public ComponentName getCurrentTransportComponentForUser(int userId) {
- return (isUserReadyForBackup(userId)) ? mService.getCurrentTransportComponent(userId)
- : null;
- }
-
- @Override
- public String[] listAllTransportsForUser(int userId) throws RemoteException {
- return (isUserReadyForBackup(userId)) ? mService.listAllTransports(userId) : null;
- }
-
- @Override
- public String[] listAllTransports() throws RemoteException {
- return listAllTransportsForUser(binderGetCallingUserId());
- }
-
- @Override
- public ComponentName[] listAllTransportComponentsForUser(int userId) throws RemoteException {
- return (isUserReadyForBackup(userId)) ? mService.listAllTransportComponents(userId)
- : null;
- }
-
- @Override
- public String[] getTransportWhitelist() {
- int userId = binderGetCallingUserId();
- return (isUserReadyForBackup(userId)) ? mService.getTransportWhitelist() : null;
- }
-
- @Override
- public void updateTransportAttributesForUser(
- int userId,
- ComponentName transportComponent,
- String name,
- @Nullable Intent configurationIntent,
- String currentDestinationString,
- @Nullable Intent dataManagementIntent,
- CharSequence dataManagementLabel) {
- if (isUserReadyForBackup(userId)) {
- mService.updateTransportAttributes(
- userId,
- transportComponent,
- name,
- configurationIntent,
- currentDestinationString,
- dataManagementIntent,
- dataManagementLabel);
- }
- }
-
- @Override
- public String selectBackupTransportForUser(int userId, String transport)
- throws RemoteException {
- return (isUserReadyForBackup(userId)) ? mService.selectBackupTransport(userId, transport)
- : null;
- }
-
- @Override
- public String selectBackupTransport(String transport) throws RemoteException {
- return selectBackupTransportForUser(binderGetCallingUserId(), transport);
- }
-
- @Override
- public void selectBackupTransportAsyncForUser(int userId, ComponentName transport,
- ISelectBackupTransportCallback listener) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.selectBackupTransportAsync(userId, transport, listener);
- } else {
- if (listener != null) {
- try {
- listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
- } catch (RemoteException ex) {
- // ignore
- }
- }
- }
- }
-
- @Override
- public Intent getConfigurationIntentForUser(int userId, String transport)
- throws RemoteException {
- return isUserReadyForBackup(userId) ? mService.getConfigurationIntent(userId, transport)
- : null;
- }
-
- @Override
- public Intent getConfigurationIntent(String transport)
- throws RemoteException {
- return getConfigurationIntentForUser(binderGetCallingUserId(), transport);
- }
-
- @Override
- public String getDestinationStringForUser(int userId, String transport) throws RemoteException {
- return isUserReadyForBackup(userId) ? mService.getDestinationString(userId, transport)
- : null;
- }
-
- @Override
- public String getDestinationString(String transport) throws RemoteException {
- return getDestinationStringForUser(binderGetCallingUserId(), transport);
- }
-
- @Override
- public Intent getDataManagementIntentForUser(int userId, String transport)
- throws RemoteException {
- return isUserReadyForBackup(userId) ? mService.getDataManagementIntent(userId, transport)
- : null;
- }
-
- @Override
- public Intent getDataManagementIntent(String transport)
- throws RemoteException {
- return getDataManagementIntentForUser(binderGetCallingUserId(), transport);
- }
-
- @Override
- public CharSequence getDataManagementLabelForUser(int userId, String transport)
- throws RemoteException {
- return isUserReadyForBackup(userId) ? mService.getDataManagementLabel(userId, transport)
- : null;
- }
-
- @Override
- public IRestoreSession beginRestoreSessionForUser(
- int userId, String packageName, String transportID) throws RemoteException {
- return isUserReadyForBackup(userId) ? mService.beginRestoreSession(userId, packageName,
- transportID) : null;
- }
-
- @Override
- public void opCompleteForUser(int userId, int token, long result) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.opComplete(userId, token, result);
- }
- }
-
- @Override
- public void opComplete(int token, long result) throws RemoteException {
- opCompleteForUser(binderGetCallingUserId(), token, result);
- }
-
- @Override
- public long getAvailableRestoreTokenForUser(int userId, String packageName) {
- return isUserReadyForBackup(userId) ? mService.getAvailableRestoreToken(userId,
- packageName) : 0;
- }
-
- @Override
- public boolean isAppEligibleForBackupForUser(int userId, String packageName) {
- return isUserReadyForBackup(userId) && mService.isAppEligibleForBackup(userId,
- packageName);
- }
-
- @Override
- public String[] filterAppsEligibleForBackupForUser(int userId, String[] packages) {
- return isUserReadyForBackup(userId) ? mService.filterAppsEligibleForBackup(userId,
- packages) : null;
- }
-
- @Override
- public int requestBackupForUser(@UserIdInt int userId, String[] packages, IBackupObserver
- observer, IBackupManagerMonitor monitor, int flags) throws RemoteException {
- if (!isUserReadyForBackup(userId)) {
- return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
- }
- return mService.requestBackup(userId, packages, observer, monitor, flags);
- }
-
- @Override
- public int requestBackup(String[] packages, IBackupObserver observer,
- IBackupManagerMonitor monitor, int flags) throws RemoteException {
- return requestBackupForUser(binderGetCallingUserId(), packages,
- observer, monitor, flags);
- }
-
- @Override
- public void cancelBackupsForUser(@UserIdInt int userId) throws RemoteException {
- if (isUserReadyForBackup(userId)) {
- mService.cancelBackups(userId);
- }
- }
-
- @Override
- public void cancelBackups() throws RemoteException {
- cancelBackupsForUser(binderGetCallingUserId());
- }
-
- @Override
- @Nullable public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
- if (mService != null) {
- return mService.getUserForAncestralSerialNumber(ancestralSerialNumber);
- }
- return null;
- }
-
- @Override
- public void setAncestralSerialNumber(long ancestralSerialNumber) {
- if (mService != null) {
- mService.setAncestralSerialNumber(ancestralSerialNumber);
- }
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- int userId = binderGetCallingUserId();
- if (isUserReadyForBackup(userId)) {
- mService.dump(fd, pw, args);
- } else {
- pw.println("Inactive");
- }
- }
-
- // Full backup/restore entry points - non-Binder; called directly
- // by the full-backup scheduled job
- /* package */ boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
- return (isUserReadyForBackup(userId)) && mService.beginFullBackup(userId, scheduledJob);
- }
-
- /* package */ void endFullBackup(@UserIdInt int userId) {
- if (isUserReadyForBackup(userId)) {
- mService.endFullBackup(userId);
- }
- }
-}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerFilePersistedSettings.java b/services/backup/java/com/android/server/backup/UserBackupManagerFilePersistedSettings.java
index 6a1de6378a5e..205b7dda267e 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerFilePersistedSettings.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerFilePersistedSettings.java
@@ -16,7 +16,6 @@
package com.android.server.backup;
-import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import android.util.Slog;
@@ -33,10 +32,13 @@ final class UserBackupManagerFilePersistedSettings {
private static final String BACKUP_ENABLE_FILE = "backup_enabled";
static boolean readBackupEnableState(int userId) {
- return readBackupEnableState(UserBackupManagerFiles.getBaseStateDir(userId));
+ boolean enabled = readBackupEnableState(UserBackupManagerFiles.getBaseStateDir(userId));
+ Slog.d(TAG, "user:" + userId + " readBackupEnableState enabled:" + enabled);
+ return enabled;
}
static void writeBackupEnableState(int userId, boolean enable) {
+ Slog.d(TAG, "user:" + userId + " writeBackupEnableState enable:" + enable);
writeBackupEnableState(UserBackupManagerFiles.getBaseStateDir(userId), enable);
}
@@ -45,15 +47,17 @@ final class UserBackupManagerFilePersistedSettings {
if (enableFile.exists()) {
try (FileInputStream fin = new FileInputStream(enableFile)) {
int state = fin.read();
+ if (state != 0 && state != 1) {
+ // TODO (b/148587496) handle instead of only logging
+ Slog.e(TAG, "Unexpected enabled state:" + state);
+ }
return state != 0;
} catch (IOException e) {
// can't read the file; fall through to assume disabled
Slog.e(TAG, "Cannot read enable state; assuming disabled");
}
} else {
- if (DEBUG) {
- Slog.i(TAG, "isBackupEnabled() => false due to absent settings file");
- }
+ Slog.i(TAG, "isBackupEnabled() => false due to absent settings file");
}
return false;
}
@@ -64,7 +68,11 @@ final class UserBackupManagerFilePersistedSettings {
try (FileOutputStream fout = new FileOutputStream(stage)) {
fout.write(enable ? 1 : 0);
fout.close();
- stage.renameTo(enableFile);
+ boolean renamed = stage.renameTo(enableFile);
+ if (!renamed) {
+ // TODO (b/148587496) handle instead of only logging
+ Slog.e(TAG, "Write enable failed as could not rename staging file to actual");
+ }
// will be synced immediately by the try-with-resources call to close()
} catch (IOException | RuntimeException e) {
Slog.e(
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index c17aa4ecabbb..0493b84997d9 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -18,7 +18,6 @@ package com.android.server.backup;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND;
-import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
@@ -32,6 +31,7 @@ import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSI
import static com.android.server.backup.internal.BackupHandler.MSG_RETRY_CLEAR;
import static com.android.server.backup.internal.BackupHandler.MSG_RUN_ADB_BACKUP;
import static com.android.server.backup.internal.BackupHandler.MSG_RUN_ADB_RESTORE;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_BACKUP;
import static com.android.server.backup.internal.BackupHandler.MSG_RUN_CLEAR;
import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
import static com.android.server.backup.internal.BackupHandler.MSG_SCHEDULE_BACKUP_PACKAGE;
@@ -112,7 +112,6 @@ import com.android.server.backup.internal.ClearDataObserver;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
import com.android.server.backup.internal.PerformInitializeTask;
-import com.android.server.backup.internal.RunBackupReceiver;
import com.android.server.backup.internal.RunInitializeReceiver;
import com.android.server.backup.internal.SetupObserver;
import com.android.server.backup.keyvalue.BackupRequest;
@@ -159,6 +158,7 @@ import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
@@ -167,33 +167,54 @@ import java.util.concurrent.atomic.AtomicInteger;
/** System service that performs backup/restore operations. */
public class UserBackupManagerService {
- /** Wrapper over {@link PowerManager.WakeLock} to prevent double-free exceptions on release()
+ /**
+ * Wrapper over {@link PowerManager.WakeLock} to prevent double-free exceptions on release()
* after quit().
- * */
+ */
public static class BackupWakeLock {
private final PowerManager.WakeLock mPowerManagerWakeLock;
private boolean mHasQuit = false;
+ private int mUserId;
- public BackupWakeLock(PowerManager.WakeLock powerManagerWakeLock) {
+ public BackupWakeLock(PowerManager.WakeLock powerManagerWakeLock, int userId) {
mPowerManagerWakeLock = powerManagerWakeLock;
+ mUserId = userId;
}
/** Acquires the {@link PowerManager.WakeLock} if hasn't been quit. */
public synchronized void acquire() {
if (mHasQuit) {
- Slog.v(TAG, "Ignore wakelock acquire after quit:" + mPowerManagerWakeLock.getTag());
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Ignore wakelock acquire after quit: "
+ + mPowerManagerWakeLock.getTag()));
return;
}
mPowerManagerWakeLock.acquire();
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Acquired wakelock:" + mPowerManagerWakeLock.getTag()));
}
/** Releases the {@link PowerManager.WakeLock} if hasn't been quit. */
public synchronized void release() {
if (mHasQuit) {
- Slog.v(TAG, "Ignore wakelock release after quit:" + mPowerManagerWakeLock.getTag());
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Ignore wakelock release after quit: "
+ + mPowerManagerWakeLock.getTag()));
return;
}
mPowerManagerWakeLock.release();
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Released wakelock:" + mPowerManagerWakeLock.getTag()));
}
/**
@@ -206,7 +227,10 @@ public class UserBackupManagerService {
/** Release the {@link PowerManager.WakeLock} till it isn't held. */
public synchronized void quit() {
while (mPowerManagerWakeLock.isHeld()) {
- Slog.v(TAG, "Releasing wakelock:" + mPowerManagerWakeLock.getTag());
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Releasing wakelock: " + mPowerManagerWakeLock.getTag()));
mPowerManagerWakeLock.release();
}
mHasQuit = true;
@@ -256,7 +280,6 @@ public class UserBackupManagerService {
// Retry interval for clear/init when the transport is unavailable
private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
- public static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
public static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
@@ -296,6 +319,9 @@ public class UserBackupManagerService {
private static final String SERIAL_ID_FILE = "serial_id";
+ private static final String SKIP_USER_FACING_PACKAGES = "backup_skip_user_facing_packages";
+ private static final String WALLPAPER_PACKAGE = "com.android.wallpaperbackup";
+
private final @UserIdInt int mUserId;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
private final TransportManager mTransportManager;
@@ -318,7 +344,6 @@ public class UserBackupManagerService {
private boolean mSetupComplete;
private boolean mAutoRestore;
- private final PendingIntent mRunBackupIntent;
private final PendingIntent mRunInitIntent;
private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names
@@ -332,6 +357,8 @@ public class UserBackupManagerService {
// locking around the pending-backup management
private final Object mQueueLock = new Object();
+ private final UserBackupPreferences mBackupPreferences;
+
// The thread performing the sequence of queued backups binds to each app's agent
// in succession. Bind notifications are asynchronously delivered through the
// Activity Manager; use this lock object to signal when a requested binding has
@@ -414,20 +441,19 @@ public class UserBackupManagerService {
@Nullable private File mAncestralSerialNumberFile;
private final ContentObserver mSetupObserver;
- private final BroadcastReceiver mRunBackupReceiver;
private final BroadcastReceiver mRunInitReceiver;
/**
* Creates an instance of {@link UserBackupManagerService} and initializes state for it. This
* includes setting up the directories where we keep our bookkeeping and transport management.
*
- * @see #createAndInitializeService(int, Context, Trampoline, HandlerThread, File, File,
- * TransportManager)
+ * @see #createAndInitializeService(int, Context, BackupManagerService, HandlerThread, File,
+ * File, TransportManager)
*/
static UserBackupManagerService createAndInitializeService(
@UserIdInt int userId,
Context context,
- Trampoline trampoline,
+ BackupManagerService backupManagerService,
Set<ComponentName> transportWhitelist) {
String currentTransport =
Settings.Secure.getStringForUser(
@@ -437,7 +463,9 @@ public class UserBackupManagerService {
}
if (DEBUG) {
- Slog.v(TAG, "Starting with transport " + currentTransport);
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(userId, "Starting with transport " + currentTransport));
}
TransportManager transportManager =
new TransportManager(userId, context, transportWhitelist, currentTransport);
@@ -449,13 +477,15 @@ public class UserBackupManagerService {
new HandlerThread("backup-" + userId, Process.THREAD_PRIORITY_BACKGROUND);
userBackupThread.start();
if (DEBUG) {
- Slog.d(TAG, "Started thread " + userBackupThread.getName() + " for user " + userId);
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(userId, "Started thread " + userBackupThread.getName()));
}
return createAndInitializeService(
userId,
context,
- trampoline,
+ backupManagerService,
userBackupThread,
baseStateDir,
dataDir,
@@ -467,7 +497,7 @@ public class UserBackupManagerService {
*
* @param userId The user which this service is for.
* @param context The system server context.
- * @param trampoline A reference to the proxy to {@link BackupManagerService}.
+ * @param backupManagerService A reference to the proxy to {@link BackupManagerService}.
* @param userBackupThread The thread running backup/restore operations for the user.
* @param baseStateDir The directory we store the user's persistent bookkeeping data.
* @param dataDir The directory we store the user's temporary staging data.
@@ -478,7 +508,7 @@ public class UserBackupManagerService {
public static UserBackupManagerService createAndInitializeService(
@UserIdInt int userId,
Context context,
- Trampoline trampoline,
+ BackupManagerService backupManagerService,
HandlerThread userBackupThread,
File baseStateDir,
File dataDir,
@@ -486,7 +516,7 @@ public class UserBackupManagerService {
return new UserBackupManagerService(
userId,
context,
- trampoline,
+ backupManagerService,
userBackupThread,
baseStateDir,
dataDir,
@@ -509,13 +539,13 @@ public class UserBackupManagerService {
private UserBackupManagerService(
@UserIdInt int userId,
Context context,
- Trampoline parent,
+ BackupManagerService parent,
HandlerThread userBackupThread,
File baseStateDir,
File dataDir,
TransportManager transportManager) {
mUserId = userId;
- mContext = checkNotNull(context, "context cannot be null");
+ mContext = Objects.requireNonNull(context, "context cannot be null");
mPackageManager = context.getPackageManager();
mPackageManagerBinder = AppGlobals.getPackageManager();
mActivityManager = ActivityManager.getService();
@@ -525,14 +555,14 @@ public class UserBackupManagerService {
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
- checkNotNull(parent, "trampoline cannot be null");
- mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
+ Objects.requireNonNull(parent, "parent cannot be null");
+ mBackupManagerBinder = BackupManagerService.asInterface(parent.asBinder());
mAgentTimeoutParameters = new
BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
mAgentTimeoutParameters.start();
- checkNotNull(userBackupThread, "userBackupThread cannot be null");
+ Objects.requireNonNull(userBackupThread, "userBackupThread cannot be null");
mBackupHandler = new BackupHandler(this, userBackupThread);
// Set up our bookkeeping
@@ -548,34 +578,27 @@ public class UserBackupManagerService {
mSetupObserver,
mUserId);
- mBaseStateDir = checkNotNull(baseStateDir, "baseStateDir cannot be null");
+ mBaseStateDir = Objects.requireNonNull(baseStateDir, "baseStateDir cannot be null");
// TODO (b/120424138): Remove once the system user is migrated to use the per-user CE
// directory. Per-user CE directories are managed by vold.
if (userId == UserHandle.USER_SYSTEM) {
mBaseStateDir.mkdirs();
if (!SELinux.restorecon(mBaseStateDir)) {
- Slog.w(TAG, "SELinux restorecon failed on " + mBaseStateDir);
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ userId, "SELinux restorecon failed on " + mBaseStateDir));
}
}
// TODO (b/120424138): The system user currently uses the cache which is managed by init.rc
// Initialization and restorecon is managed by vold for per-user CE directories.
- mDataDir = checkNotNull(dataDir, "dataDir cannot be null");
+ mDataDir = Objects.requireNonNull(dataDir, "dataDir cannot be null");
mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
- // Receivers for scheduled backups and transport initialization operations.
- mRunBackupReceiver = new RunBackupReceiver(this);
- IntentFilter filter = new IntentFilter();
- filter.addAction(RUN_BACKUP_ACTION);
- context.registerReceiverAsUser(
- mRunBackupReceiver,
- UserHandle.of(userId),
- filter,
- android.Manifest.permission.BACKUP,
- /* scheduler */ null);
-
+ // Receiver for transport initialization.
mRunInitReceiver = new RunInitializeReceiver(this);
- filter = new IntentFilter();
+ IntentFilter filter = new IntentFilter();
filter.addAction(RUN_INITIALIZE_ACTION);
context.registerReceiverAsUser(
mRunInitReceiver,
@@ -584,16 +607,6 @@ public class UserBackupManagerService {
android.Manifest.permission.BACKUP,
/* scheduler */ null);
- Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
- backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mRunBackupIntent =
- PendingIntent.getBroadcastAsUser(
- context,
- /* requestCode */ 0,
- backupIntent,
- /* flags */ 0,
- UserHandle.of(userId));
-
Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mRunInitIntent =
@@ -622,7 +635,8 @@ public class UserBackupManagerService {
addPackageParticipantsLocked(null);
}
- mTransportManager = checkNotNull(transportManager, "transportManager cannot be null");
+ mTransportManager =
+ Objects.requireNonNull(transportManager, "transportManager cannot be null");
mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
mBackupHandler.postDelayed(
@@ -632,11 +646,13 @@ public class UserBackupManagerService {
// the pending backup set
mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
+ mBackupPreferences = new UserBackupPreferences(mContext, mBaseStateDir);
+
// Power management
mWakelock = new BackupWakeLock(
mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
- "*backup*-" + userId + "-" + userBackupThread.getThreadId()));
+ "*backup*-" + userId + "-" + userBackupThread.getThreadId()), userId);
// Set up the various sorts of package tracking we do
mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
@@ -649,11 +665,11 @@ public class UserBackupManagerService {
}
/** Cleans up state when the user of this service is stopped. */
- void tearDownService() {
+ @VisibleForTesting
+ protected void tearDownService() {
mAgentTimeoutParameters.stop();
mConstants.stop();
mContext.getContentResolver().unregisterContentObserver(mSetupObserver);
- mContext.unregisterReceiver(mRunBackupReceiver);
mContext.unregisterReceiver(mRunInitReceiver);
mContext.unregisterReceiver(mPackageTrackingReceiver);
mBackupHandler.stop();
@@ -845,10 +861,6 @@ public class UserBackupManagerService {
mPendingInits.clear();
}
- public PerformFullTransportBackupTask getRunningFullBackupTask() {
- return mRunningFullBackupTask;
- }
-
public void setRunningFullBackupTask(
PerformFullTransportBackupTask runningFullBackupTask) {
mRunningFullBackupTask = runningFullBackupTask;
@@ -889,7 +901,7 @@ public class UserBackupManagerService {
}
private void initPackageTracking() {
- if (MORE_DEBUG) Slog.v(TAG, "` tracking");
+ if (MORE_DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, "` tracking"));
// Remember our ancestral dataset
mTokenFile = new File(mBaseStateDir, "ancestral");
@@ -911,9 +923,9 @@ public class UserBackupManagerService {
}
} catch (FileNotFoundException fnf) {
// Probably innocuous
- Slog.v(TAG, "No ancestral data");
+ Slog.v(TAG, addUserIdToLogMessage(mUserId, "No ancestral data"));
} catch (IOException e) {
- Slog.w(TAG, "Unable to read token file", e);
+ Slog.w(TAG, addUserIdToLogMessage(mUserId, "Unable to read token file"), e);
}
mProcessedPackagesJournal = new ProcessedPackagesJournal(mBaseStateDir);
@@ -961,7 +973,10 @@ public class UserBackupManagerService {
DataInputStream in = new DataInputStream(bufStream)) {
int version = in.readInt();
if (version != SCHEDULE_FILE_VERSION) {
- Slog.e(TAG, "Unknown backup schedule version " + version);
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Unknown backup schedule version " + version));
return null;
}
@@ -986,14 +1001,14 @@ public class UserBackupManagerService {
schedule.add(new FullBackupEntry(pkgName, lastBackup));
} else {
if (DEBUG) {
- Slog.i(TAG, "Package " + pkgName
- + " no longer eligible for full backup");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Package " + pkgName
+ + " no longer eligible for full backup"));
}
}
} catch (NameNotFoundException e) {
if (DEBUG) {
- Slog.i(TAG, "Package " + pkgName
- + " not installed; dropping from full backup");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Package " + pkgName
+ + " not installed; dropping from full backup"));
}
}
}
@@ -1006,7 +1021,13 @@ public class UserBackupManagerService {
mUserId)) {
if (!foundApps.contains(app.packageName)) {
if (MORE_DEBUG) {
- Slog.i(TAG, "New full backup app " + app.packageName + " found");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "New full backup app "
+ + app.packageName
+ + " found"));
}
schedule.add(new FullBackupEntry(app.packageName, 0));
changed = true;
@@ -1016,7 +1037,7 @@ public class UserBackupManagerService {
Collections.sort(schedule);
} catch (Exception e) {
- Slog.e(TAG, "Unable to read backup schedule", e);
+ Slog.e(TAG, addUserIdToLogMessage(mUserId, "Unable to read backup schedule"), e);
mFullBackupScheduleFile.delete();
schedule = null;
}
@@ -1072,7 +1093,11 @@ public class UserBackupManagerService {
out.write(bufStream.toByteArray());
af.finishWrite(out);
} catch (Exception e) {
- Slog.e(TAG, "Unable to write backup schedule!", e);
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Unable to write backup schedule!"),
+ e);
}
}
}
@@ -1088,7 +1113,8 @@ public class UserBackupManagerService {
// TODO(b/162022005): Fix DataChangedJournal implementing equals() but not hashCode().
journals.removeAll(Collections.singletonList(mJournal));
if (!journals.isEmpty()) {
- Slog.i(TAG, "Found " + journals.size() + " stale backup journal(s), scheduling.");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId,
+ "Found " + journals.size() + " stale backup journal(s), scheduling."));
}
Set<String> packageNames = new LinkedHashSet<>();
for (DataChangedJournal journal : journals) {
@@ -1099,7 +1125,7 @@ public class UserBackupManagerService {
}
});
} catch (IOException e) {
- Slog.e(TAG, "Can't read " + journal, e);
+ Slog.e(TAG, addUserIdToLogMessage(mUserId, "Can't read " + journal), e);
}
}
if (!packageNames.isEmpty()) {
@@ -1108,10 +1134,14 @@ public class UserBackupManagerService {
if (MORE_DEBUG) {
msg += ": " + packageNames;
}
- Slog.i(TAG, msg);
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, msg));
}
}
+ public Set<String> getExcludedRestoreKeys(String packageName) {
+ return mBackupPreferences.getExcludedRestoreKeysForPackage(packageName);
+ }
+
/** Used for generating random salts or passwords. */
public byte[] randomBytes(int bits) {
byte[] array = new byte[bits / 8];
@@ -1142,7 +1172,14 @@ public class UserBackupManagerService {
boolean isPending, String transportName, String transportDirName) {
synchronized (mQueueLock) {
if (MORE_DEBUG) {
- Slog.i(TAG, "recordInitPending(" + isPending + ") on transport " + transportName);
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "recordInitPending("
+ + isPending
+ + ") on transport "
+ + transportName));
}
File stateDir = new File(mBaseStateDir, transportDirName);
@@ -1203,8 +1240,17 @@ public class UserBackupManagerService {
private void onTransportRegistered(String transportName, String transportDirName) {
if (DEBUG) {
long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime;
- Slog.d(TAG, "Transport " + transportName + " registered " + timeMs
- + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS + "ms)");
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Transport "
+ + transportName
+ + " registered "
+ + timeMs
+ + "ms after first request (delay = "
+ + INITIALIZATION_DELAY_MILLIS
+ + "ms)"));
}
File stateDir = new File(mBaseStateDir, transportDirName);
@@ -1230,7 +1276,7 @@ public class UserBackupManagerService {
private BroadcastReceiver mPackageTrackingReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (MORE_DEBUG) {
- Slog.d(TAG, "Received broadcast " + intent);
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "Received broadcast " + intent));
}
String action = intent.getAction();
@@ -1250,24 +1296,33 @@ public class UserBackupManagerService {
String packageName = uri.getSchemeSpecificPart();
if (packageName != null) {
- packageList = new String[]{packageName};
+ packageList = new String[] {packageName};
}
changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
if (changed) {
// Look at new transport states for package changed events.
String[] components =
- intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (MORE_DEBUG) {
- Slog.i(TAG, "Package " + packageName + " changed");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Package " + packageName + " changed"));
for (int i = 0; i < components.length; i++) {
- Slog.i(TAG, " * " + components[i]);
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, " * " + components[i]));
}
}
mBackupHandler.post(
- () -> mTransportManager.onPackageChanged(packageName, components));
+ () ->
+ mTransportManager.onPackageChanged(
+ packageName, components));
return;
}
@@ -1289,7 +1344,8 @@ public class UserBackupManagerService {
if (added) {
synchronized (mBackupParticipants) {
if (replacing) {
- // Remove the entry under the old uid and fall through to re-add. If an app
+ // Remove the entry under the old uid and fall through to re-add. If
+ // an app
// just opted into key/value backup, add it as a known participant.
removePackageParticipantsLocked(packageList, uid);
}
@@ -1303,13 +1359,15 @@ public class UserBackupManagerService {
mPackageManager.getPackageInfoAsUser(
packageName, /* flags */ 0, mUserId);
if (AppBackupUtils.appGetsFullBackup(app)
- && AppBackupUtils.appIsEligibleForBackup(app.applicationInfo,
- mUserId)) {
+ && AppBackupUtils.appIsEligibleForBackup(
+ app.applicationInfo, mUserId)) {
enqueueFullBackup(packageName, now);
scheduleNextFullBackupJob(0);
} else {
- // The app might have just transitioned out of full-data into doing
- // key/value backups, or might have just disabled backups entirely. Make
+ // The app might have just transitioned out of full-data into
+ // doing
+ // key/value backups, or might have just disabled backups
+ // entirely. Make
// sure it is no longer in the full-data queue.
synchronized (mQueueLock) {
dequeueFullBackupLocked(packageName);
@@ -1321,17 +1379,23 @@ public class UserBackupManagerService {
() -> mTransportManager.onPackageAdded(packageName));
} catch (NameNotFoundException e) {
if (DEBUG) {
- Slog.w(TAG, "Can't resolve new app " + packageName);
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Can't resolve new app " + packageName));
}
}
}
- // Whenever a package is added or updated we need to update the package metadata
+ // Whenever a package is added or updated we need to update the package
+ // metadata
// bookkeeping.
dataChangedImpl(PACKAGE_MANAGER_SENTINEL);
} else {
if (!replacing) {
- // Outright removal. In the full-data case, the app will be dropped from the
+ // Outright removal. In the full-data case, the app will be dropped from
+ // the
// queue when its (now obsolete) name comes up again for backup.
synchronized (mBackupParticipants) {
removePackageParticipantsLocked(packageList, uid);
@@ -1352,12 +1416,19 @@ public class UserBackupManagerService {
// Look for apps that define the android:backupAgent attribute
List<PackageInfo> targetApps = allAgentPackages();
if (packageNames != null) {
- if (MORE_DEBUG) Slog.v(TAG, "addPackageParticipantsLocked: #" + packageNames.length);
+ if (MORE_DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "addPackageParticipantsLocked: #" + packageNames.length));
+ }
for (String packageName : packageNames) {
addPackageParticipantsLockedInner(packageName, targetApps);
}
} else {
- if (MORE_DEBUG) Slog.v(TAG, "addPackageParticipantsLocked: all");
+ if (MORE_DEBUG) {
+ Slog.v(TAG, addUserIdToLogMessage(mUserId, "addPackageParticipantsLocked: all"));
+ }
addPackageParticipantsLockedInner(null, targetApps);
}
}
@@ -1365,7 +1436,10 @@ public class UserBackupManagerService {
private void addPackageParticipantsLockedInner(String packageName,
List<PackageInfo> targetPkgs) {
if (MORE_DEBUG) {
- Slog.v(TAG, "Examining " + packageName + " for backup agent");
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Examining " + packageName + " for backup agent"));
}
for (PackageInfo pkg : targetPkgs) {
@@ -1377,10 +1451,15 @@ public class UserBackupManagerService {
mBackupParticipants.put(uid, set);
}
set.add(pkg.packageName);
- if (MORE_DEBUG) Slog.v(TAG, "Agent found; added");
+ if (MORE_DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, "Agent found; added"));
// Schedule a backup for it on general principles
- if (MORE_DEBUG) Slog.i(TAG, "Scheduling backup for new app " + pkg.packageName);
+ if (MORE_DEBUG) {
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Scheduling backup for new app " + pkg.packageName));
+ }
Message msg = mBackupHandler
.obtainMessage(MSG_SCHEDULE_BACKUP_PACKAGE, pkg.packageName);
mBackupHandler.sendMessage(msg);
@@ -1391,13 +1470,19 @@ public class UserBackupManagerService {
// Remove the given packages' entries from our known active set.
private void removePackageParticipantsLocked(String[] packageNames, int oldUid) {
if (packageNames == null) {
- Slog.w(TAG, "removePackageParticipants with null list");
+ Slog.w(TAG, addUserIdToLogMessage(mUserId, "removePackageParticipants with null list"));
return;
}
if (MORE_DEBUG) {
- Slog.v(TAG, "removePackageParticipantsLocked: uid=" + oldUid
- + " #" + packageNames.length);
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "removePackageParticipantsLocked: uid="
+ + oldUid
+ + " #"
+ + packageNames.length));
}
for (String pkg : packageNames) {
// Known previous UID, so we know which package set to check
@@ -1405,7 +1490,12 @@ public class UserBackupManagerService {
if (set != null && set.contains(pkg)) {
removePackageFromSetLocked(set, pkg);
if (set.isEmpty()) {
- if (MORE_DEBUG) Slog.v(TAG, " last one of this uid; purging set");
+ if (MORE_DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, " last one of this uid; purging set"));
+ }
mBackupParticipants.remove(oldUid);
}
}
@@ -1421,7 +1511,11 @@ public class UserBackupManagerService {
// Note that we deliberately leave it 'known' in the "ever backed up"
// bookkeeping so that its current-dataset data will be retrieved
// if the app is subsequently reinstalled
- if (MORE_DEBUG) Slog.v(TAG, " removing participant " + packageName);
+ if (MORE_DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(mUserId, " removing participant " + packageName));
+ }
set.remove(packageName);
mPendingBackups.remove(packageName);
}
@@ -1495,14 +1589,19 @@ public class UserBackupManagerService {
af.writeInt(-1);
} else {
af.writeInt(mAncestralPackages.size());
- if (DEBUG) Slog.v(TAG, "Ancestral packages: " + mAncestralPackages.size());
+ if (DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Ancestral packages: " + mAncestralPackages.size()));
+ }
for (String pkgName : mAncestralPackages) {
af.writeUTF(pkgName);
- if (MORE_DEBUG) Slog.v(TAG, " " + pkgName);
+ if (MORE_DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, " " + pkgName));
}
}
} catch (IOException e) {
- Slog.w(TAG, "Unable to write token file:", e);
+ Slog.w(TAG, addUserIdToLogMessage(mUserId, "Unable to write token file:"), e);
}
}
@@ -1515,7 +1614,7 @@ public class UserBackupManagerService {
mConnectedAgent = null;
try {
if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId)) {
- Slog.d(TAG, "awaiting agent for " + app);
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
// success; wait for the agent to arrive
// only wait 10 seconds for the bind to happen
@@ -1526,7 +1625,7 @@ public class UserBackupManagerService {
mAgentConnectLock.wait(5000);
} catch (InterruptedException e) {
// just bail
- Slog.w(TAG, "Interrupted: " + e);
+ Slog.w(TAG, addUserIdToLogMessage(mUserId, "Interrupted: " + e));
mConnecting = false;
mConnectedAgent = null;
}
@@ -1534,10 +1633,14 @@ public class UserBackupManagerService {
// if we timed out with no connect, abort and move on
if (mConnecting) {
- Slog.w(TAG, "Timeout waiting for agent " + app);
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Timeout waiting for agent " + app));
mConnectedAgent = null;
}
- if (DEBUG) Slog.i(TAG, "got agent " + mConnectedAgent);
+ if (DEBUG) {
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "got agent " + mConnectedAgent));
+ }
agent = mConnectedAgent;
}
} catch (RemoteException e) {
@@ -1603,13 +1706,20 @@ public class UserBackupManagerService {
if (!shouldClearData) {
if (MORE_DEBUG) {
- Slog.i(TAG, "Clearing app data is not allowed so not wiping "
- + packageName);
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Clearing app data is not allowed so not wiping "
+ + packageName));
}
return;
}
} catch (NameNotFoundException e) {
- Slog.w(TAG, "Tried to clear data for " + packageName + " but not found");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Tried to clear data for " + packageName + " but not found"));
return;
}
@@ -1632,13 +1742,22 @@ public class UserBackupManagerService {
} catch (InterruptedException e) {
// won't happen, but still.
mClearingData = false;
- Slog.w(TAG, "Interrupted while waiting for " + packageName
- + " data to be cleared", e);
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Interrupted while waiting for "
+ + packageName
+ + " data to be cleared"),
+ e);
}
}
if (mClearingData) {
- Slog.w(TAG, "Clearing app data for " + packageName + " timed out");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Clearing app data for " + packageName + " timed out"));
}
}
}
@@ -1655,12 +1774,17 @@ public class UserBackupManagerService {
synchronized (mQueueLock) {
if (mCurrentToken != 0 && mProcessedPackagesJournal.hasBeenProcessed(packageName)) {
if (MORE_DEBUG) {
- Slog.i(TAG, "App in ever-stored, so using current token");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "App in ever-stored, so using current token"));
}
token = mCurrentToken;
}
}
- if (MORE_DEBUG) Slog.i(TAG, "getAvailableRestoreToken() == " + token);
+ if (MORE_DEBUG) {
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "getAvailableRestoreToken() == " + token));
+ }
return token;
}
@@ -1682,7 +1806,7 @@ public class UserBackupManagerService {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
if (packages == null || packages.length < 1) {
- Slog.e(TAG, "No packages named for backup request");
+ Slog.e(TAG, addUserIdToLogMessage(mUserId, "No packages named for backup request"));
BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES,
@@ -1693,10 +1817,10 @@ public class UserBackupManagerService {
if (!mEnabled || !mSetupComplete) {
Slog.i(
TAG,
- "Backup requested but enabled="
+ addUserIdToLogMessage(mUserId, "Backup requested but enabled="
+ mEnabled
+ " setupComplete="
- + mSetupComplete);
+ + mSetupComplete));
BackupObserverUtils.sendBackupFinished(observer,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
final int logTag = mSetupComplete
@@ -1754,9 +1878,17 @@ public class UserBackupManagerService {
EventLog.writeEvent(EventLogTags.BACKUP_REQUESTED, packages.length, kvBackupList.size(),
fullBackupList.size());
if (MORE_DEBUG) {
- Slog.i(TAG, "Backup requested for " + packages.length + " packages, of them: "
- + fullBackupList.size() + " full backups, " + kvBackupList.size()
- + " k/v backups");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Backup requested for "
+ + packages.length
+ + " packages, of them: "
+ + fullBackupList.size()
+ + " full backups, "
+ + kvBackupList.size()
+ + " k/v backups"));
}
boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
@@ -1772,7 +1904,7 @@ public class UserBackupManagerService {
public void cancelBackups() {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups");
if (MORE_DEBUG) {
- Slog.i(TAG, "cancelBackups() called.");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "cancelBackups() called."));
}
final long oldToken = Binder.clearCallingIdentity();
try {
@@ -1802,13 +1934,27 @@ public class UserBackupManagerService {
public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
int operationType) {
if (operationType != OP_TYPE_BACKUP_WAIT && operationType != OP_TYPE_RESTORE_WAIT) {
- Slog.wtf(TAG, "prepareOperationTimeout() doesn't support operation "
- + Integer.toHexString(token) + " of type " + operationType);
+ Slog.wtf(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "prepareOperationTimeout() doesn't support operation "
+ + Integer.toHexString(token)
+ + " of type "
+ + operationType));
return;
}
if (MORE_DEBUG) {
- Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
- + " interval=" + interval + " callback=" + callback);
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "starting timeout: token="
+ + Integer.toHexString(token)
+ + " interval="
+ + interval
+ + " callback="
+ + callback));
}
synchronized (mCurrentOpLock) {
@@ -1826,8 +1972,12 @@ public class UserBackupManagerService {
case OP_TYPE_RESTORE_WAIT:
return MSG_RESTORE_OPERATION_TIMEOUT;
default:
- Slog.wtf(TAG, "getMessageIdForOperationType called on invalid operation type: "
- + operationType);
+ Slog.wtf(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "getMessageIdForOperationType called on invalid operation type: "
+ + operationType));
return -1;
}
}
@@ -1838,8 +1988,14 @@ public class UserBackupManagerService {
*/
public void putOperation(int token, Operation operation) {
if (MORE_DEBUG) {
- Slog.d(TAG, "Adding operation token=" + Integer.toHexString(token) + ", operation type="
- + operation.type);
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Adding operation token="
+ + Integer.toHexString(token)
+ + ", operation type="
+ + operation.type));
}
synchronized (mCurrentOpLock) {
mCurrentOperations.put(token, operation);
@@ -1852,12 +2008,15 @@ public class UserBackupManagerService {
*/
public void removeOperation(int token) {
if (MORE_DEBUG) {
- Slog.d(TAG, "Removing operation token=" + Integer.toHexString(token));
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Removing operation token=" + Integer.toHexString(token)));
}
synchronized (mCurrentOpLock) {
if (mCurrentOperations.get(token) == null) {
- Slog.w(TAG, "Duplicate remove for operation. token="
- + Integer.toHexString(token));
+ Slog.w(TAG, addUserIdToLogMessage(mUserId, "Duplicate remove for operation. token="
+ + Integer.toHexString(token)));
}
mCurrentOperations.remove(token);
}
@@ -1866,8 +2025,8 @@ public class UserBackupManagerService {
/** Block until we received an operation complete message (from the agent or cancellation). */
public boolean waitUntilOperationComplete(int token) {
if (MORE_DEBUG) {
- Slog.i(TAG, "Blocking until operation complete for "
- + Integer.toHexString(token));
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Blocking until operation complete for "
+ + Integer.toHexString(token)));
}
int finalState = OP_PENDING;
Operation op = null;
@@ -1886,8 +2045,12 @@ public class UserBackupManagerService {
// When the wait is notified we loop around and recheck the current state
} else {
if (MORE_DEBUG) {
- Slog.d(TAG, "Unblocked waiting for operation token="
- + Integer.toHexString(token));
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Unblocked waiting for operation token="
+ + Integer.toHexString(token)));
}
// No longer pending; we're done
finalState = op.state;
@@ -1902,8 +2065,8 @@ public class UserBackupManagerService {
mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
}
if (MORE_DEBUG) {
- Slog.v(TAG, "operation " + Integer.toHexString(token)
- + " complete: finalState=" + finalState);
+ Slog.v(TAG, addUserIdToLogMessage(mUserId, "operation " + Integer.toHexString(token)
+ + " complete: finalState=" + finalState));
}
return finalState == OP_ACKNOWLEDGED;
}
@@ -1916,21 +2079,31 @@ public class UserBackupManagerService {
op = mCurrentOperations.get(token);
if (MORE_DEBUG) {
if (op == null) {
- Slog.w(TAG, "Cancel of token " + Integer.toHexString(token)
- + " but no op found");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Cancel of token "
+ + Integer.toHexString(token)
+ + " but no op found"));
}
}
int state = (op != null) ? op.state : OP_TIMEOUT;
if (state == OP_ACKNOWLEDGED) {
// The operation finished cleanly, so we have nothing more to do.
if (DEBUG) {
- Slog.w(TAG, "Operation already got an ack."
- + "Should have been removed from mCurrentOperations.");
+ Slog.w(TAG, addUserIdToLogMessage(mUserId, "Operation already got an ack."
+ + "Should have been removed from mCurrentOperations."));
}
op = null;
mCurrentOperations.delete(token);
} else if (state == OP_PENDING) {
- if (DEBUG) Slog.v(TAG, "Cancel: token=" + Integer.toHexString(token));
+ if (DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Cancel: token=" + Integer.toHexString(token)));
+ }
op.state = OP_TIMEOUT;
// Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
// called after we receive cancel here. We need this op's state there.
@@ -1948,7 +2121,7 @@ public class UserBackupManagerService {
// If there's a TimeoutHandler for this event, call it
if (op != null && op.callback != null) {
if (MORE_DEBUG) {
- Slog.v(TAG, " Invoking cancel on " + op.callback);
+ Slog.v(TAG, addUserIdToLogMessage(mUserId, " Invoking cancel on " + op.callback));
}
op.callback.handleCancel(cancelAll);
}
@@ -1983,13 +2156,20 @@ public class UserBackupManagerService {
// manifest flag! TODO something less direct.
if (!UserHandle.isCore(app.uid)
&& !app.packageName.equals("com.android.backupconfirm")) {
- if (MORE_DEBUG) Slog.d(TAG, "Killing agent host process");
+ if (MORE_DEBUG) {
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "Killing agent host process"));
+ }
mActivityManager.killApplicationProcess(app.processName, app.uid);
} else {
- if (MORE_DEBUG) Slog.d(TAG, "Not killing after operation: " + app.processName);
+ if (MORE_DEBUG) {
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Not killing after operation: " + app.processName));
+ }
}
} catch (RemoteException e) {
- Slog.d(TAG, "Lost app trying to shut down");
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "Lost app trying to shut down"));
}
}
@@ -2003,7 +2183,12 @@ public class UserBackupManagerService {
} catch (Exception e) {
// If we can't talk to the storagemanager service we have a serious problem; fail
// "secure" i.e. assuming that the device is encrypted.
- Slog.e(TAG, "Unable to communicate with storagemanager service: " + e.getMessage());
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Unable to communicate with storagemanager service: "
+ + e.getMessage()));
return true;
}
}
@@ -2027,7 +2212,10 @@ public class UserBackupManagerService {
FullBackupJob.schedule(mUserId, mContext, latency, mConstants);
} else {
if (DEBUG_SCHEDULING) {
- Slog.i(TAG, "Full backup queue empty; not scheduling");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Full backup queue empty; not scheduling"));
}
}
}
@@ -2082,7 +2270,10 @@ public class UserBackupManagerService {
private boolean fullBackupAllowable(String transportName) {
if (!mTransportManager.isTransportRegistered(transportName)) {
- Slog.w(TAG, "Transport not registered; full data backup not performed");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Transport not registered; full data backup not performed"));
return false;
}
@@ -2094,12 +2285,19 @@ public class UserBackupManagerService {
File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL);
if (pmState.length() <= 0) {
if (DEBUG) {
- Slog.i(TAG, "Full backup requested but dataset not yet initialized");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Full backup requested but dataset not yet initialized"));
}
return false;
}
} catch (Exception e) {
- Slog.w(TAG, "Unable to get transport name: " + e.getMessage());
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Unable to get transport name: " + e.getMessage()));
return false;
}
@@ -2132,8 +2330,8 @@ public class UserBackupManagerService {
// the job driving automatic backups; that job will be scheduled again when
// the user enables backup.
if (MORE_DEBUG) {
- Slog.i(TAG, "beginFullBackup but enabled=" + mEnabled
- + " setupComplete=" + mSetupComplete + "; ignoring");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "beginFullBackup but enabled=" + mEnabled
+ + " setupComplete=" + mSetupComplete + "; ignoring"));
}
return false;
}
@@ -2143,19 +2341,29 @@ public class UserBackupManagerService {
final PowerSaveState result =
mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP);
if (result.batterySaverEnabled) {
- if (DEBUG) Slog.i(TAG, "Deferring scheduled full backups in battery saver mode");
+ if (DEBUG) {
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Deferring scheduled full backups in battery saver mode"));
+ }
FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, mConstants);
return false;
}
if (DEBUG_SCHEDULING) {
- Slog.i(TAG, "Beginning scheduled full backup operation");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Beginning scheduled full backup operation"));
}
// Great; we're able to run full backup jobs now. See if we have any work to do.
synchronized (mQueueLock) {
if (mRunningFullBackupTask != null) {
- Slog.e(TAG, "Backup triggered but one already/still running!");
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Backup triggered but one already/still running!"));
return false;
}
@@ -2171,7 +2379,10 @@ public class UserBackupManagerService {
if (mFullBackupQueue.size() == 0) {
// no work to do so just bow out
if (DEBUG) {
- Slog.i(TAG, "Backup queue empty; doing nothing");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Backup queue empty; doing nothing"));
}
runBackup = false;
break;
@@ -2182,7 +2393,10 @@ public class UserBackupManagerService {
String transportName = mTransportManager.getCurrentTransportName();
if (!fullBackupAllowable(transportName)) {
if (MORE_DEBUG) {
- Slog.i(TAG, "Preconditions not met; not running full backup");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Preconditions not met; not running full backup"));
}
runBackup = false;
// Typically this means we haven't run a key/value backup yet. Back off
@@ -2198,7 +2412,11 @@ public class UserBackupManagerService {
if (!runBackup) {
// It's too early to back up the next thing in the queue, so bow out
if (MORE_DEBUG) {
- Slog.i(TAG, "Device ready but too early to back up next app");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Device ready but too early to back up next app"));
}
// Wait until the next app in the queue falls due for a full data backup
latency = fullBackupInterval - timeSinceRun;
@@ -2213,8 +2431,14 @@ public class UserBackupManagerService {
// so we cull it and force a loop around to consider the new head
// app.
if (MORE_DEBUG) {
- Slog.i(TAG, "Culling package " + entry.packageName
- + " in full-backup queue but not eligible");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Culling package "
+ + entry.packageName
+ + " in full-backup queue but not"
+ + " eligible"));
}
mFullBackupQueue.remove(0);
headBusy = true; // force the while() condition
@@ -2232,9 +2456,14 @@ public class UserBackupManagerService {
+ mTokenGenerator.nextInt(BUSY_BACKOFF_FUZZ);
if (DEBUG_SCHEDULING) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Slog.i(TAG, "Full backup time but " + entry.packageName
- + " is busy; deferring to "
- + sdf.format(new Date(nextEligible)));
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Full backup time but "
+ + entry.packageName
+ + " is busy; deferring to "
+ + sdf.format(new Date(nextEligible))));
}
// This relocates the app's entry from the head of the queue to
// its order-appropriate position further down, so upon looping
@@ -2253,7 +2482,11 @@ public class UserBackupManagerService {
if (!runBackup) {
if (DEBUG_SCHEDULING) {
- Slog.i(TAG, "Nothing pending full backup; rescheduling +" + latency);
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Nothing pending full backup; rescheduling +" + latency));
}
final long deferTime = latency; // pin for the closure
FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants);
@@ -2301,7 +2534,10 @@ public class UserBackupManagerService {
}
if (pftbt != null) {
if (DEBUG_SCHEDULING) {
- Slog.i(TAG, "Telling running backup to stop");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Telling running backup to stop"));
}
pftbt.handleCancel(true);
}
@@ -2314,7 +2550,7 @@ public class UserBackupManagerService {
public void restoreWidgetData(String packageName, byte[] widgetData) {
// Apply the restored widget state and generate the ID update for the app
if (MORE_DEBUG) {
- Slog.i(TAG, "Incorporating restored widget data");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Incorporating restored widget data"));
}
AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, mUserId);
}
@@ -2334,8 +2570,15 @@ public class UserBackupManagerService {
// may share a uid, we need to note all candidates within that uid and schedule
// a backup pass for each of them.
if (targets == null) {
- Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
- + " uid=" + Binder.getCallingUid());
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "dataChanged but no participant pkg='"
+ + packageName
+ + "'"
+ + " uid="
+ + Binder.getCallingUid()));
return;
}
@@ -2346,7 +2589,12 @@ public class UserBackupManagerService {
// one already there, then overwrite it, but no harm done.
BackupRequest req = new BackupRequest(packageName);
if (mPendingBackups.put(packageName, req) == null) {
- if (MORE_DEBUG) Slog.d(TAG, "Now staging backup of " + packageName);
+ if (MORE_DEBUG) {
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Now staging backup of " + packageName));
+ }
// Journal this request in case of crash. The put()
// operation returned null when this package was not already
@@ -2386,7 +2634,10 @@ public class UserBackupManagerService {
if (mJournal == null) mJournal = DataChangedJournal.newJournal(mJournalDir);
mJournal.addPackage(str);
} catch (IOException e) {
- Slog.e(TAG, "Can't write " + str + " to backup journal", e);
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Can't write " + str + " to backup journal"),
+ e);
mJournal = null;
}
}
@@ -2397,8 +2648,15 @@ public class UserBackupManagerService {
public void dataChanged(final String packageName) {
final HashSet<String> targets = dataChangedTargets(packageName);
if (targets == null) {
- Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
- + " uid=" + Binder.getCallingUid());
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "dataChanged but no participant pkg='"
+ + packageName
+ + "'"
+ + " uid="
+ + Binder.getCallingUid()));
return;
}
@@ -2411,9 +2669,12 @@ public class UserBackupManagerService {
/** Run an initialize operation for the given transport. */
public void initializeTransports(String[] transportNames, IBackupObserver observer) {
- mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"initializeTransport");
- Slog.v(TAG, "initializeTransport(): " + Arrays.asList(transportNames));
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "initializeTransport(): " + Arrays.asList(transportNames)));
final long oldId = Binder.clearCallingIdentity();
try {
@@ -2432,12 +2693,18 @@ public class UserBackupManagerService {
public void setAncestralSerialNumber(long ancestralSerialNumber) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
"setAncestralSerialNumber");
- Slog.v(TAG, "Setting ancestral work profile id to " + ancestralSerialNumber);
- // TODO (b/124359804)
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Setting ancestral work profile id to " + ancestralSerialNumber));
try (RandomAccessFile af = getAncestralSerialNumberFile()) {
af.writeLong(ancestralSerialNumber);
} catch (IOException e) {
- Slog.w(TAG, "Unable to write to work profile serial mapping file:", e);
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Unable to write to work profile serial mapping file:"),
+ e);
}
}
@@ -2446,11 +2713,14 @@ public class UserBackupManagerService {
* {@link #setAncestralSerialNumber(long)}. Will return {@code -1} if not set.
*/
public long getAncestralSerialNumber() {
- // TODO (b/124359804)
try (RandomAccessFile af = getAncestralSerialNumberFile()) {
return af.readLong();
} catch (IOException e) {
- Slog.w(TAG, "Unable to write to work profile serial number file:", e);
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Unable to write to work profile serial number file:"),
+ e);
return -1;
}
}
@@ -2473,13 +2743,24 @@ public class UserBackupManagerService {
/** Clear the given package's backup data from the current transport. */
public void clearBackupData(String transportName, String packageName) {
- if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName);
+ if (DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "clearBackupData() of " + packageName + " on " + transportName));
+ }
+
PackageInfo info;
try {
info = mPackageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
} catch (NameNotFoundException e) {
- Slog.d(TAG, "No such package '" + packageName + "' - not clearing backup data");
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "No such package '" + packageName + "' - not clearing backup data"));
return;
}
@@ -2492,13 +2773,22 @@ public class UserBackupManagerService {
} else {
// a caller with full permission can ask to back up any participating app
// !!! TODO: allow data-clear of ANY app?
- if (MORE_DEBUG) Slog.v(TAG, "Privileged caller, allowing clear of other apps");
+ if (MORE_DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Privileged caller, allowing clear of other apps"));
+ }
apps = mProcessedPackagesJournal.getPackagesCopy();
}
if (apps.contains(packageName)) {
// found it; fire off the clear request
- if (MORE_DEBUG) Slog.v(TAG, "Found the app - running clear process");
+ if (MORE_DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Found the app - running clear process"));
+ }
mBackupHandler.removeMessages(MSG_RETRY_CLEAR);
synchronized (mQueueLock) {
TransportClient transportClient =
@@ -2537,23 +2827,55 @@ public class UserBackupManagerService {
final PowerSaveState result =
mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
if (result.batterySaverEnabled) {
- if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode");
+ if (DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Not running backup while in battery save mode"));
+ }
// Try again in several hours.
KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
} else {
- if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
- synchronized (mQueueLock) {
- // Fire the intent that kicks off the whole shebang...
- try {
- mRunBackupIntent.send();
- } catch (PendingIntent.CanceledException e) {
- // should never happen
- Slog.e(TAG, "run-backup intent cancelled!");
+ if (DEBUG) {
+ Slog.v(TAG, addUserIdToLogMessage(mUserId, "Scheduling immediate backup pass"));
+ }
+
+ synchronized (getQueueLock()) {
+ if (getPendingInits().size() > 0) {
+ // If there are pending init operations, we process those and then settle
+ // into the usual periodic backup schedule.
+ if (MORE_DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Init pending at scheduled backup"));
+ }
+ try {
+ getAlarmManager().cancel(mRunInitIntent);
+ mRunInitIntent.send();
+ } catch (PendingIntent.CanceledException ce) {
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Run init intent cancelled"));
+ }
+ return;
}
+ }
- // ...and cancel any pending scheduled job, because we've just superseded it
- KeyValueBackupJob.cancel(mUserId, mContext);
+ // Don't run backups if we're disabled or not yet set up.
+ if (!isEnabled() || !isSetupComplete()) {
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Backup pass but enabled=" + isEnabled()
+ + " setupComplete=" + isSetupComplete()));
+ return;
}
+
+ // Fire the msg that kicks off the whole shebang...
+ Message message = mBackupHandler.obtainMessage(MSG_RUN_BACKUP);
+ mBackupHandler.sendMessage(message);
+ // ...and cancel any pending scheduled job, because we've just superseded it
+ KeyValueBackupJob.cancel(mUserId, mContext);
}
} finally {
Binder.restoreCallingIdentity(oldId);
@@ -2572,7 +2894,6 @@ public class UserBackupManagerService {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
final int callingUserHandle = UserHandle.getCallingUserId();
- // TODO: http://b/22388012
if (callingUserHandle != UserHandle.USER_SYSTEM) {
throw new IllegalStateException("Backup supported only for the device owner");
}
@@ -2593,16 +2914,31 @@ public class UserBackupManagerService {
long oldId = Binder.clearCallingIdentity();
try {
if (!mSetupComplete) {
- Slog.i(TAG, "Backup not supported before setup");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Backup not supported before setup"));
return;
}
if (DEBUG) {
- Slog.v(TAG, "Requesting backup: apks=" + includeApks + " obb=" + includeObbs
- + " shared=" + includeShared + " all=" + doAllApps + " system="
- + includeSystem + " includekeyvalue=" + doKeyValue + " pkgs=" + pkgList);
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Requesting backup: apks="
+ + includeApks
+ + " obb="
+ + includeObbs
+ + " shared="
+ + includeShared
+ + " all="
+ + doAllApps
+ + " system="
+ + includeSystem
+ + " includekeyvalue="
+ + doKeyValue
+ + " pkgs="
+ + pkgList));
}
- Slog.i(TAG, "Beginning adb backup...");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning adb backup..."));
AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
@@ -2613,9 +2949,16 @@ public class UserBackupManagerService {
}
// start up the confirmation UI
- if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token);
+ if (DEBUG) {
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Starting backup confirmation UI, token=" + token));
+ }
if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
- Slog.e(TAG, "Unable to launch backup confirmation UI");
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Unable to launch backup confirmation UI"));
mAdbBackupRestoreConfirmations.delete(token);
return;
}
@@ -2629,16 +2972,22 @@ public class UserBackupManagerService {
startConfirmationTimeout(token, params);
// wait for the backup to be performed
- if (DEBUG) Slog.d(TAG, "Waiting for backup completion...");
+ if (DEBUG) {
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "Waiting for backup completion..."));
+ }
waitForCompletion(params);
} finally {
try {
fd.close();
} catch (IOException e) {
- Slog.e(TAG, "IO error closing output for adb backup: " + e.getMessage());
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "IO error closing output for adb backup: " + e.getMessage()));
}
Binder.restoreCallingIdentity(oldId);
- Slog.d(TAG, "Adb backup processing complete.");
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "Adb backup processing complete."));
}
}
@@ -2655,10 +3004,14 @@ public class UserBackupManagerService {
String transportName = mTransportManager.getCurrentTransportName();
if (!fullBackupAllowable(transportName)) {
- Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Full backup not currently possible -- key/value backup not yet run?"));
} else {
if (DEBUG) {
- Slog.d(TAG, "fullTransportBackup()");
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "fullTransportBackup()"));
}
final long oldId = Binder.clearCallingIdentity();
@@ -2698,7 +3051,7 @@ public class UserBackupManagerService {
}
if (DEBUG) {
- Slog.d(TAG, "Done with full transport backup.");
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "Done with full transport backup."));
}
}
@@ -2710,7 +3063,6 @@ public class UserBackupManagerService {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
final int callingUserHandle = UserHandle.getCallingUserId();
- // TODO: http://b/22388012
if (callingUserHandle != UserHandle.USER_SYSTEM) {
throw new IllegalStateException("Restore supported only for the device owner");
}
@@ -2719,11 +3071,13 @@ public class UserBackupManagerService {
try {
if (!mSetupComplete) {
- Slog.i(TAG, "Full restore not permitted before setup");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Full restore not permitted before setup"));
return;
}
- Slog.i(TAG, "Beginning restore...");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning restore..."));
AdbRestoreParams params = new AdbRestoreParams(fd);
final int token = generateRandomIntegerToken();
@@ -2732,9 +3086,16 @@ public class UserBackupManagerService {
}
// start up the confirmation UI
- if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token);
+ if (DEBUG) {
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Starting restore confirmation UI, token=" + token));
+ }
if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) {
- Slog.e(TAG, "Unable to launch restore confirmation");
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Unable to launch restore confirmation"));
mAdbBackupRestoreConfirmations.delete(token);
return;
}
@@ -2748,19 +3109,34 @@ public class UserBackupManagerService {
startConfirmationTimeout(token, params);
// wait for the restore to be performed
- if (DEBUG) Slog.d(TAG, "Waiting for restore completion...");
+ if (DEBUG) {
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "Waiting for restore completion..."));
+ }
waitForCompletion(params);
} finally {
try {
fd.close();
} catch (IOException e) {
- Slog.w(TAG, "Error trying to close fd after adb restore: " + e);
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Error trying to close fd after adb restore: " + e));
}
Binder.restoreCallingIdentity(oldId);
- Slog.i(TAG, "adb restore processing complete.");
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "adb restore processing complete."));
}
}
+ /**
+ * Excludes keys from KV restore for a given package. The keys won't be part of the data passed
+ * to the backup agent during restore.
+ */
+ public void excludeKeysFromRestore(String packageName, List<String> keys) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "excludeKeysFromRestore");
+ mBackupPreferences.addExcludedKeys(packageName, keys);
+ }
+
private boolean startConfirmationUi(int token, String action) {
try {
Intent confIntent = new Intent(action);
@@ -2777,8 +3153,8 @@ public class UserBackupManagerService {
private void startConfirmationTimeout(int token, AdbParams params) {
if (MORE_DEBUG) {
- Slog.d(TAG, "Posting conf timeout msg after "
- + TIMEOUT_FULL_CONFIRMATION + " millis");
+ Slog.d(TAG, addUserIdToLogMessage(mUserId, "Posting conf timeout msg after "
+ + TIMEOUT_FULL_CONFIRMATION + " millis"));
}
Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
token, 0, params);
@@ -2810,8 +3186,11 @@ public class UserBackupManagerService {
public void acknowledgeAdbBackupOrRestore(int token, boolean allow,
String curPassword, String encPpassword, IFullBackupRestoreObserver observer) {
if (DEBUG) {
- Slog.d(TAG, "acknowledgeAdbBackupOrRestore : token=" + token
- + " allow=" + allow);
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "acknowledgeAdbBackupOrRestore : token=" + token + " allow=" + allow));
}
// TODO: possibly require not just this signature-only permission, but even
@@ -2839,17 +3218,29 @@ public class UserBackupManagerService {
params.encryptPassword = encPpassword;
- if (MORE_DEBUG) Slog.d(TAG, "Sending conf message with verb " + verb);
+ if (MORE_DEBUG) {
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Sending conf message with verb " + verb));
+ }
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(verb, params);
mBackupHandler.sendMessage(msg);
} else {
- Slog.w(TAG, "User rejected full backup/restore operation");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "User rejected full backup/restore operation"));
// indicate completion without having actually transferred any data
signalAdbBackupRestoreCompletion(params);
}
} else {
- Slog.w(TAG, "Attempted to ack full backup/restore with invalid token");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Attempted to ack full backup/restore with invalid token"));
}
}
} finally {
@@ -2862,7 +3253,7 @@ public class UserBackupManagerService {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setBackupEnabled");
- Slog.i(TAG, "Backup enabled => " + enable);
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Backup enabled => " + enable));
long oldId = Binder.clearCallingIdentity();
try {
@@ -2879,7 +3270,9 @@ public class UserBackupManagerService {
scheduleNextFullBackupJob(0);
} else if (!enable) {
// No longer enabled, so stop running backups
- if (MORE_DEBUG) Slog.i(TAG, "Opting out of backup");
+ if (MORE_DEBUG) {
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Opting out of backup"));
+ }
KeyValueBackupJob.cancel(mUserId, mContext);
@@ -2895,12 +3288,15 @@ public class UserBackupManagerService {
name -> {
final String dirName;
try {
- dirName =
- mTransportManager
- .getTransportDirName(name);
+ dirName = mTransportManager.getTransportDirName(name);
} catch (TransportNotRegisteredException e) {
// Should never happen
- Slog.e(TAG, "Unexpected unregistered transport", e);
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Unexpected unregistered transport"),
+ e);
return;
}
transportNames.add(name);
@@ -2929,7 +3325,7 @@ public class UserBackupManagerService {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setAutoRestore");
- Slog.i(TAG, "Auto restore => " + doAutoRestore);
+ Slog.i(TAG, addUserIdToLogMessage(mUserId, "Auto restore => " + doAutoRestore));
final long oldId = Binder.clearCallingIdentity();
try {
@@ -2955,7 +3351,12 @@ public class UserBackupManagerService {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getCurrentTransport");
String currentTransport = mTransportManager.getCurrentTransportName();
- if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + currentTransport);
+ if (MORE_DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "... getCurrentTransport() returning " + currentTransport));
+ }
return currentTransport;
}
@@ -3045,9 +3446,9 @@ public class UserBackupManagerService {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP, "updateTransportAttributes");
- Preconditions.checkNotNull(transportComponent, "transportComponent can't be null");
- Preconditions.checkNotNull(name, "name can't be null");
- Preconditions.checkNotNull(
+ Objects.requireNonNull(transportComponent, "transportComponent can't be null");
+ Objects.requireNonNull(name, "name can't be null");
+ Objects.requireNonNull(
currentDestinationString, "currentDestinationString can't be null");
Preconditions.checkArgument(
(dataManagementIntent == null) == (dataManagementLabel == null),
@@ -3094,8 +3495,14 @@ public class UserBackupManagerService {
try {
String previousTransportName = mTransportManager.selectTransport(transportName);
updateStateForTransport(transportName);
- Slog.v(TAG, "selectBackupTransport(transport = " + transportName
- + "): previous transport = " + previousTransportName);
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "selectBackupTransport(transport = "
+ + transportName
+ + "): previous transport = "
+ + previousTransportName));
return previousTransportName;
} finally {
Binder.restoreCallingIdentity(oldId);
@@ -3114,7 +3521,11 @@ public class UserBackupManagerService {
final long oldId = Binder.clearCallingIdentity();
try {
String transportString = transportComponent.flattenToShortString();
- Slog.v(TAG, "selectBackupTransportAsync(transport = " + transportString + ")");
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "selectBackupTransportAsync(transport = " + transportString + ")"));
mBackupHandler.post(
() -> {
String transportName = null;
@@ -3126,7 +3537,10 @@ public class UserBackupManagerService {
mTransportManager.getTransportName(transportComponent);
updateStateForTransport(transportName);
} catch (TransportNotRegisteredException e) {
- Slog.e(TAG, "Transport got unregistered");
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Transport got unregistered"));
result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
}
}
@@ -3138,7 +3552,12 @@ public class UserBackupManagerService {
listener.onFailure(result);
}
} catch (RemoteException e) {
- Slog.e(TAG, "ISelectBackupTransportCallback listener not available");
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "ISelectBackupTransportCallback listener not"
+ + " available"));
}
});
} finally {
@@ -3146,6 +3565,40 @@ public class UserBackupManagerService {
}
}
+ /**
+ * We want to skip backup/restore of certain packages if 'backup_skip_user_facing_packages' is
+ * set to true in secure settings. See b/153940088 for details.
+ *
+ * TODO(b/154822946): Remove this logic in the next release.
+ */
+ public List<PackageInfo> filterUserFacingPackages(List<PackageInfo> packages) {
+ if (!shouldSkipUserFacingData()) {
+ return packages;
+ }
+
+ List<PackageInfo> filteredPackages = new ArrayList<>(packages.size());
+ for (PackageInfo packageInfo : packages) {
+ if (!shouldSkipPackage(packageInfo.packageName)) {
+ filteredPackages.add(packageInfo);
+ } else {
+ Slog.i(TAG, "Will skip backup/restore for " + packageInfo.packageName);
+ }
+ }
+
+ return filteredPackages;
+ }
+
+ @VisibleForTesting
+ public boolean shouldSkipUserFacingData() {
+ return Settings.Secure.getInt(mContext.getContentResolver(), SKIP_USER_FACING_PACKAGES,
+ /* def */ 0) != 0;
+ }
+
+ @VisibleForTesting
+ public boolean shouldSkipPackage(String packageName) {
+ return WALLPAPER_PACKAGE.equals(packageName);
+ }
+
private void updateStateForTransport(String newTransportName) {
// Publish the name change
Settings.Secure.putStringForUser(mContext.getContentResolver(),
@@ -3163,11 +3616,23 @@ public class UserBackupManagerService {
// Oops. We can't know the current dataset token, so reset and figure it out
// when we do the next k/v backup operation on this transport.
mCurrentToken = 0;
- Slog.w(TAG, "Transport " + newTransportName + " not available: current token = 0");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Transport "
+ + newTransportName
+ + " not available: current token = 0"));
}
mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
} else {
- Slog.w(TAG, "Transport " + newTransportName + " not registered: current token = 0");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Transport "
+ + newTransportName
+ + " not registered: current token = 0"));
// The named transport isn't registered, so we can't know what its current dataset token
// is. Reset as above.
mCurrentToken = 0;
@@ -3185,11 +3650,19 @@ public class UserBackupManagerService {
try {
Intent intent = mTransportManager.getTransportConfigurationIntent(transportName);
if (MORE_DEBUG) {
- Slog.d(TAG, "getConfigurationIntent() returning intent " + intent);
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "getConfigurationIntent() returning intent " + intent));
}
return intent;
} catch (TransportNotRegisteredException e) {
- Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage());
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Unable to get configuration intent from transport: "
+ + e.getMessage()));
return null;
}
}
@@ -3210,11 +3683,18 @@ public class UserBackupManagerService {
try {
String string = mTransportManager.getTransportCurrentDestinationString(transportName);
if (MORE_DEBUG) {
- Slog.d(TAG, "getDestinationString() returning " + string);
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "getDestinationString() returning " + string));
}
return string;
} catch (TransportNotRegisteredException e) {
- Slog.e(TAG, "Unable to get destination string from transport: " + e.getMessage());
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Unable to get destination string from transport: " + e.getMessage()));
return null;
}
}
@@ -3227,11 +3707,18 @@ public class UserBackupManagerService {
try {
Intent intent = mTransportManager.getTransportDataManagementIntent(transportName);
if (MORE_DEBUG) {
- Slog.d(TAG, "getDataManagementIntent() returning intent " + intent);
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "getDataManagementIntent() returning intent " + intent));
}
return intent;
} catch (TransportNotRegisteredException e) {
- Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage());
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Unable to get management intent from transport: " + e.getMessage()));
return null;
}
}
@@ -3247,11 +3734,18 @@ public class UserBackupManagerService {
try {
CharSequence label = mTransportManager.getTransportDataManagementLabel(transportName);
if (MORE_DEBUG) {
- Slog.d(TAG, "getDataManagementLabel() returning " + label);
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "getDataManagementLabel() returning " + label));
}
return label;
} catch (TransportNotRegisteredException e) {
- Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage());
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Unable to get management label from transport: " + e.getMessage()));
return null;
}
}
@@ -3263,12 +3757,21 @@ public class UserBackupManagerService {
public void agentConnected(String packageName, IBinder agentBinder) {
synchronized (mAgentConnectLock) {
if (Binder.getCallingUid() == Process.SYSTEM_UID) {
- Slog.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "agentConnected pkg=" + packageName + " agent=" + agentBinder));
mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder);
mConnecting = false;
} else {
- Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
- + " claiming agent connected");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Non-system process uid="
+ + Binder.getCallingUid()
+ + " claiming agent connected"));
}
mAgentConnectLock.notifyAll();
}
@@ -3286,8 +3789,13 @@ public class UserBackupManagerService {
mConnectedAgent = null;
mConnecting = false;
} else {
- Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
- + " claiming agent disconnected");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Non-system process uid="
+ + Binder.getCallingUid()
+ + " claiming agent disconnected"));
}
mAgentConnectLock.notifyAll();
}
@@ -3299,8 +3807,13 @@ public class UserBackupManagerService {
*/
public void restoreAtInstall(String packageName, int token) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
- + " attemping install-time restore");
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Non-system process uid="
+ + Binder.getCallingUid()
+ + " attemping install-time restore"));
return;
}
@@ -3308,25 +3821,35 @@ public class UserBackupManagerService {
long restoreSet = getAvailableRestoreToken(packageName);
if (DEBUG) {
- Slog.v(TAG, "restoreAtInstall pkg=" + packageName
- + " token=" + Integer.toHexString(token)
- + " restoreSet=" + Long.toHexString(restoreSet));
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "restoreAtInstall pkg="
+ + packageName
+ + " token="
+ + Integer.toHexString(token)
+ + " restoreSet="
+ + Long.toHexString(restoreSet)));
}
if (restoreSet == 0) {
- if (MORE_DEBUG) Slog.i(TAG, "No restore set");
+ if (MORE_DEBUG) Slog.i(TAG, addUserIdToLogMessage(mUserId, "No restore set"));
skip = true;
}
TransportClient transportClient =
mTransportManager.getCurrentTransportClient("BMS.restoreAtInstall()");
if (transportClient == null) {
- if (DEBUG) Slog.w(TAG, "No transport client");
+ if (DEBUG) Slog.w(TAG, addUserIdToLogMessage(mUserId, "No transport client"));
skip = true;
}
if (!mAutoRestore) {
if (DEBUG) {
- Slog.w(TAG, "Non-restorable state: auto=" + mAutoRestore);
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Non-restorable state: auto=" + mAutoRestore));
}
skip = true;
}
@@ -3345,7 +3868,9 @@ public class UserBackupManagerService {
};
if (MORE_DEBUG) {
- Slog.d(TAG, "Restore at install of " + packageName);
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(mUserId, "Restore at install of " + packageName));
}
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj =
@@ -3360,7 +3885,10 @@ public class UserBackupManagerService {
mBackupHandler.sendMessage(msg);
} catch (Exception e) {
// Calling into the transport broke; back off and proceed with the installation.
- Slog.e(TAG, "Unable to contact transport: " + e.getMessage());
+ Slog.e(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Unable to contact transport: " + e.getMessage()));
skip = true;
}
}
@@ -3374,7 +3902,7 @@ public class UserBackupManagerService {
}
// Tell the PackageManager to proceed with the post-install handling for this package.
- if (DEBUG) Slog.v(TAG, "Finishing install immediately");
+ if (DEBUG) Slog.v(TAG, addUserIdToLogMessage(mUserId, "Finishing install immediately"));
try {
mPackageManagerBinder.finishPackageInstall(token, false);
} catch (RemoteException e) { /* can't happen */ }
@@ -3384,8 +3912,11 @@ public class UserBackupManagerService {
/** Hand off a restore session. */
public IRestoreSession beginRestoreSession(String packageName, String transport) {
if (DEBUG) {
- Slog.v(TAG, "beginRestoreSession: pkg=" + packageName
- + " transport=" + transport);
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "beginRestoreSession: pkg=" + packageName + " transport=" + transport));
}
boolean needPermission = true;
@@ -3397,7 +3928,10 @@ public class UserBackupManagerService {
try {
app = mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId);
} catch (NameNotFoundException nnf) {
- Slog.w(TAG, "Asked to restore nonexistent pkg " + packageName);
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Asked to restore nonexistent pkg " + packageName));
throw new IllegalArgumentException("Package " + packageName + " not found");
}
@@ -3411,19 +3945,32 @@ public class UserBackupManagerService {
}
if (needPermission) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
- "beginRestoreSession");
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BACKUP, "beginRestoreSession");
} else {
- if (DEBUG) Slog.d(TAG, "restoring self on current transport; no permission needed");
+ if (DEBUG) {
+ Slog.d(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "restoring self on current transport; no permission needed"));
+ }
}
synchronized (this) {
if (mActiveRestoreSession != null) {
- Slog.i(TAG, "Restore session requested but one already active");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Restore session requested but one already active"));
return null;
}
if (mBackupRunning) {
- Slog.i(TAG, "Restore session requested but currently running backups");
+ Slog.i(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Restore session requested but currently running backups"));
return null;
}
mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport);
@@ -3437,9 +3984,14 @@ public class UserBackupManagerService {
public void clearRestoreSession(ActiveRestoreSession currentSession) {
synchronized (this) {
if (currentSession != mActiveRestoreSession) {
- Slog.e(TAG, "ending non-current restore session");
+ Slog.e(TAG, addUserIdToLogMessage(mUserId, "ending non-current restore session"));
} else {
- if (DEBUG) Slog.v(TAG, "Clearing restore session and halting timeout");
+ if (DEBUG) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId, "Clearing restore session and halting timeout"));
+ }
mActiveRestoreSession = null;
mBackupHandler.removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
}
@@ -3452,7 +4004,11 @@ public class UserBackupManagerService {
*/
public void opComplete(int token, long result) {
if (MORE_DEBUG) {
- Slog.v(TAG, "opComplete: " + Integer.toHexString(token) + " result=" + result);
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "opComplete: " + Integer.toHexString(token) + " result=" + result));
}
Operation op = null;
synchronized (mCurrentOpLock) {
@@ -3465,8 +4021,12 @@ public class UserBackupManagerService {
mCurrentOperations.delete(token);
} else if (op.state == OP_ACKNOWLEDGED) {
if (DEBUG) {
- Slog.w(TAG, "Received duplicate ack for token="
- + Integer.toHexString(token));
+ Slog.w(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Received duplicate ack for token="
+ + Integer.toHexString(token)));
}
op = null;
mCurrentOperations.remove(token);
@@ -3542,14 +4102,7 @@ public class UserBackupManagerService {
try {
if (args != null) {
for (String arg : args) {
- if ("-h".equals(arg)) {
- pw.println("'dumpsys backup' optional arguments:");
- pw.println(" -h : this help text");
- pw.println(" a[gents] : dump information about defined backup agents");
- pw.println(" users : dump the list of users for which backup service "
- + "is running");
- return;
- } else if ("agents".startsWith(arg)) {
+ if ("agents".startsWith(arg)) {
dumpAgents(pw);
return;
} else if ("transportclients".equals(arg.toLowerCase())) {
@@ -3580,8 +4133,10 @@ public class UserBackupManagerService {
}
private void dumpInternal(PrintWriter pw) {
+ // Add prefix for only non-system users so that system user dumpsys is the same as before
+ String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":";
synchronized (mQueueLock) {
- pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+ pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+ " / " + (!mSetupComplete ? "not " : "") + "setup complete / "
+ (this.mPendingInits.size() == 0 ? "not " : "") + "pending init");
pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
@@ -3591,13 +4146,13 @@ public class UserBackupManagerService {
+ " (now = " + System.currentTimeMillis() + ')');
pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
- pw.println("Transport whitelist:");
+ pw.println(userPrefix + "Transport whitelist:");
for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
pw.print(" ");
pw.println(transport.flattenToShortString());
}
- pw.println("Available transports:");
+ pw.println(userPrefix + "Available transports:");
final String[] transports = listAllTransports();
if (transports != null) {
for (String t : transports) {
@@ -3615,7 +4170,7 @@ public class UserBackupManagerService {
" " + f.getName() + " - " + f.length() + " state bytes");
}
} catch (Exception e) {
- Slog.e(TAG, "Error in transport", e);
+ Slog.e(TAG, addUserIdToLogMessage(mUserId, "Error in transport"), e);
pw.println(" Error: " + e);
}
}
@@ -3623,18 +4178,18 @@ public class UserBackupManagerService {
mTransportManager.dumpTransportClients(pw);
- pw.println("Pending init: " + mPendingInits.size());
+ pw.println(userPrefix + "Pending init: " + mPendingInits.size());
for (String s : mPendingInits) {
pw.println(" " + s);
}
- pw.print("Ancestral: ");
+ pw.print(userPrefix + "Ancestral: ");
pw.println(Long.toHexString(mAncestralToken));
- pw.print("Current: ");
+ pw.print(userPrefix + "Current: ");
pw.println(Long.toHexString(mCurrentToken));
int numPackages = mBackupParticipants.size();
- pw.println("Participants:");
+ pw.println(userPrefix + "Participants:");
for (int i = 0; i < numPackages; i++) {
int uid = mBackupParticipants.keyAt(i);
pw.print(" uid: ");
@@ -3645,7 +4200,7 @@ public class UserBackupManagerService {
}
}
- pw.println("Ancestral packages: "
+ pw.println(userPrefix + "Ancestral packages: "
+ (mAncestralPackages == null ? "none" : mAncestralPackages.size()));
if (mAncestralPackages != null) {
for (String pkg : mAncestralPackages) {
@@ -3654,17 +4209,17 @@ public class UserBackupManagerService {
}
Set<String> processedPackages = mProcessedPackagesJournal.getPackagesCopy();
- pw.println("Ever backed up: " + processedPackages.size());
+ pw.println(userPrefix + "Ever backed up: " + processedPackages.size());
for (String pkg : processedPackages) {
pw.println(" " + pkg);
}
- pw.println("Pending key/value backup: " + mPendingBackups.size());
+ pw.println(userPrefix + "Pending key/value backup: " + mPendingBackups.size());
for (BackupRequest req : mPendingBackups.values()) {
pw.println(" " + req);
}
- pw.println("Full backup queue:" + mFullBackupQueue.size());
+ pw.println(userPrefix + "Full backup queue:" + mFullBackupQueue.size());
for (FullBackupEntry entry : mFullBackupQueue) {
pw.print(" ");
pw.print(entry.lastBackup);
@@ -3674,6 +4229,10 @@ public class UserBackupManagerService {
}
}
+ private static String addUserIdToLogMessage(int userId, String message) {
+ return "[UserID:" + userId + "] " + message;
+ }
+
public IBackupManager getBackupManagerBinder() {
return mBackupManagerBinder;
diff --git a/services/backup/java/com/android/server/backup/UserBackupPreferences.java b/services/backup/java/com/android/server/backup/UserBackupPreferences.java
new file mode 100644
index 000000000000..bb8bf52187c5
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/UserBackupPreferences.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** Manages the persisted backup preferences per user. */
+public class UserBackupPreferences {
+ private static final String PREFERENCES_FILE = "backup_preferences";
+
+ private final SharedPreferences mPreferences;
+ private final SharedPreferences.Editor mEditor;
+
+ UserBackupPreferences(Context conext, File storageDir) {
+ File excludedKeysFile = new File(storageDir, PREFERENCES_FILE);
+ mPreferences = conext.getSharedPreferences(excludedKeysFile, Context.MODE_PRIVATE);
+ mEditor = mPreferences.edit();
+ }
+
+ void addExcludedKeys(String packageName, List<String> keys) {
+ Set<String> existingKeys =
+ new HashSet<>(mPreferences.getStringSet(packageName, Collections.emptySet()));
+ existingKeys.addAll(keys);
+ mEditor.putStringSet(packageName, existingKeys);
+ mEditor.commit();
+ }
+
+ Set<String> getExcludedRestoreKeysForPackage(String packageName) {
+ return mPreferences.getStringSet(packageName, Collections.emptySet());
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java b/services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java
deleted file mode 100644
index 5bec1a94e915..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.android.server.backup.encryption.chunk;
-
-import android.util.proto.ProtoInputStream;
-
-import java.io.IOException;
-
-/**
- * Information about a chunk entry in a protobuf. Only used for reading from a {@link
- * ProtoInputStream}.
- */
-public class Chunk {
- /**
- * Reads a Chunk from a {@link ProtoInputStream}. Expects the message to be of format {@link
- * ChunksMetadataProto.Chunk}.
- *
- * @param inputStream currently at a {@link ChunksMetadataProto.Chunk} message.
- * @throws IOException when the message is not structured as expected or a field can not be
- * read.
- */
- static Chunk readFromProto(ProtoInputStream inputStream) throws IOException {
- Chunk result = new Chunk();
-
- while (inputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (inputStream.getFieldNumber()) {
- case (int) ChunksMetadataProto.Chunk.HASH:
- result.mHash = inputStream.readBytes(ChunksMetadataProto.Chunk.HASH);
- break;
- case (int) ChunksMetadataProto.Chunk.LENGTH:
- result.mLength = inputStream.readInt(ChunksMetadataProto.Chunk.LENGTH);
- break;
- }
- }
-
- return result;
- }
-
- private int mLength;
- private byte[] mHash;
-
- /** Private constructor. This class should only be instantiated by calling readFromProto. */
- private Chunk() {
- // Set default values for fields in case they are not available in the proto.
- mHash = new byte[]{};
- mLength = 0;
- }
-
- public int getLength() {
- return mLength;
- }
-
- public byte[] getHash() {
- return mHash;
- }
-} \ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java
deleted file mode 100644
index 1ae598ec9920..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import com.android.internal.util.Preconditions;
-import java.util.Arrays;
-import java.util.Base64;
-
-/**
- * Represents the SHA-256 hash of the plaintext of a chunk, which is frequently used as a key.
- *
- * <p>This class is {@link Comparable} and implements {@link #equals(Object)} and {@link
- * #hashCode()}.
- */
-public class ChunkHash implements Comparable<ChunkHash> {
- /** The length of the hash in bytes. The hash is a SHA-256, so this is 256 bits. */
- public static final int HASH_LENGTH_BYTES = 256 / 8;
-
- private static final int UNSIGNED_MASK = 0xFF;
-
- private final byte[] mHash;
-
- /** Constructs a new instance which wraps the given SHA-256 hash bytes. */
- public ChunkHash(byte[] hash) {
- Preconditions.checkArgument(hash.length == HASH_LENGTH_BYTES, "Hash must have 256 bits");
- mHash = hash;
- }
-
- public byte[] getHash() {
- return mHash;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof ChunkHash)) {
- return false;
- }
-
- ChunkHash chunkHash = (ChunkHash) o;
- return Arrays.equals(mHash, chunkHash.mHash);
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mHash);
- }
-
- @Override
- public int compareTo(ChunkHash other) {
- return lexicographicalCompareUnsignedBytes(getHash(), other.getHash());
- }
-
- @Override
- public String toString() {
- return Base64.getEncoder().encodeToString(mHash);
- }
-
- private static int lexicographicalCompareUnsignedBytes(byte[] left, byte[] right) {
- int minLength = Math.min(left.length, right.length);
- for (int i = 0; i < minLength; i++) {
- int result = toInt(left[i]) - toInt(right[i]);
- if (result != 0) {
- return result;
- }
- }
- return left.length - right.length;
- }
-
- private static int toInt(byte value) {
- return value & UNSIGNED_MASK;
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkListingMap.java b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkListingMap.java
deleted file mode 100644
index a44890118717..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkListingMap.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import android.annotation.Nullable;
-import android.util.proto.ProtoInputStream;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Chunk listing in a format optimized for quick look-up of chunks via their hash keys. This is
- * useful when building an incremental backup. After a chunk has been produced, the algorithm can
- * quickly look up whether the chunk existed in the previous backup by checking this chunk listing.
- * It can then tell the server to use that chunk, through telling it the position and length of the
- * chunk in the previous backup's blob.
- */
-public class ChunkListingMap {
- /**
- * Reads a ChunkListingMap from a {@link ProtoInputStream}. Expects the message to be of format
- * {@link ChunksMetadataProto.ChunkListing}.
- *
- * @param inputStream Currently at a {@link ChunksMetadataProto.ChunkListing} message.
- * @throws IOException when the message is not structured as expected or a field can not be
- * read.
- */
- public static ChunkListingMap readFromProto(ProtoInputStream inputStream) throws IOException {
- Map<ChunkHash, Entry> entries = new HashMap();
-
- long start = 0;
-
- while (inputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- if (inputStream.getFieldNumber() == (int) ChunksMetadataProto.ChunkListing.CHUNKS) {
- long chunkToken = inputStream.start(ChunksMetadataProto.ChunkListing.CHUNKS);
- Chunk chunk = Chunk.readFromProto(inputStream);
- entries.put(new ChunkHash(chunk.getHash()), new Entry(start, chunk.getLength()));
- start += chunk.getLength();
- inputStream.end(chunkToken);
- }
- }
-
- return new ChunkListingMap(entries);
- }
-
- private final Map<ChunkHash, Entry> mChunksByHash;
-
- private ChunkListingMap(Map<ChunkHash, Entry> chunksByHash) {
- mChunksByHash = Collections.unmodifiableMap(new HashMap<>(chunksByHash));
- }
-
- /** Returns {@code true} if there is a chunk with the given SHA-256 MAC key in the listing. */
- public boolean hasChunk(ChunkHash hash) {
- return mChunksByHash.containsKey(hash);
- }
-
- /**
- * Returns the entry for the chunk with the given hash.
- *
- * @param hash The SHA-256 MAC of the plaintext of the chunk.
- * @return The entry, containing position and length of the chunk in the backup blob, or null if
- * it does not exist.
- */
- @Nullable
- public Entry getChunkEntry(ChunkHash hash) {
- return mChunksByHash.get(hash);
- }
-
- /** Returns the number of chunks in this listing. */
- public int getChunkCount() {
- return mChunksByHash.size();
- }
-
- /** Information about a chunk entry in a backup blob - i.e., its position and length. */
- public static final class Entry {
- private final int mLength;
- private final long mStart;
-
- private Entry(long start, int length) {
- mStart = start;
- mLength = length;
- }
-
- /** Returns the length of the chunk in bytes. */
- public int getLength() {
- return mLength;
- }
-
- /** Returns the start position of the chunk in the backup blob, in bytes. */
- public long getStart() {
- return mStart;
- }
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
deleted file mode 100644
index df36c9409732..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
-import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.EXPLICIT_STARTS;
-import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.INLINE_LENGTHS;
-
-import android.annotation.IntDef;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** IntDef corresponding to the ChunkOrderingType enum in the ChunksMetadataProto protobuf. */
-@IntDef({CHUNK_ORDERING_TYPE_UNSPECIFIED, EXPLICIT_STARTS, INLINE_LENGTHS})
-@Retention(RetentionPolicy.SOURCE)
-public @interface ChunkOrderingType {}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java b/services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
deleted file mode 100644
index 3a6d1f62faaa..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import java.util.Arrays;
-
-/**
- * Holds the bytes of an encrypted {@link ChunksMetadataProto.ChunkOrdering}.
- *
- * <p>TODO(b/116575321): After all code is ported, remove the factory method and rename
- * encryptedChunkOrdering() to getBytes().
- */
-public class EncryptedChunkOrdering {
- /**
- * Constructs a new object holding the given bytes of an encrypted {@link
- * ChunksMetadataProto.ChunkOrdering}.
- *
- * <p>Note that this just holds an ordering which is already encrypted, it does not encrypt the
- * ordering.
- */
- public static EncryptedChunkOrdering create(byte[] encryptedChunkOrdering) {
- return new EncryptedChunkOrdering(encryptedChunkOrdering);
- }
-
- private final byte[] mEncryptedChunkOrdering;
-
- public byte[] encryptedChunkOrdering() {
- return mEncryptedChunkOrdering;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof EncryptedChunkOrdering)) {
- return false;
- }
-
- EncryptedChunkOrdering encryptedChunkOrdering = (EncryptedChunkOrdering) o;
- return Arrays.equals(
- mEncryptedChunkOrdering, encryptedChunkOrdering.mEncryptedChunkOrdering);
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mEncryptedChunkOrdering);
- }
-
- private EncryptedChunkOrdering(byte[] encryptedChunkOrdering) {
- mEncryptedChunkOrdering = encryptedChunkOrdering;
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java
deleted file mode 100644
index 68d9d145139f..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import java.io.IOException;
-
-/** Writes backup data either as a diff script or as raw data, determined by the implementation. */
-public interface BackupWriter {
- /** Writes the given bytes to the output. */
- void writeBytes(byte[] bytes) throws IOException;
-
- /**
- * Writes an existing chunk from the previous backup to the output.
- *
- * <p>Note: not all implementations support this method.
- */
- void writeChunk(long start, int length) throws IOException;
-
- /** Returns the number of bytes written, included bytes copied from the old file. */
- long getBytesWritten();
-
- /**
- * Indicates that no more bytes or chunks will be written.
- *
- * <p>After calling this, you may not call {@link #writeBytes(byte[])} or {@link
- * #writeChunk(long, int)}
- */
- void flush() throws IOException;
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java b/services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java
deleted file mode 100644
index 004d9e3b45f1..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.internal.util.Preconditions;
-
-/** Representation of a range of bytes to be downloaded. */
-final class ByteRange {
- private final long mStart;
- private final long mEnd;
-
- /** Creates a range of bytes which includes {@code mStart} and {@code mEnd}. */
- ByteRange(long start, long end) {
- Preconditions.checkArgument(start >= 0);
- Preconditions.checkArgument(end >= start);
- mStart = start;
- mEnd = end;
- }
-
- /** Returns the start of the {@code ByteRange}. The start is included in the range. */
- long getStart() {
- return mStart;
- }
-
- /** Returns the end of the {@code ByteRange}. The end is included in the range. */
- long getEnd() {
- return mEnd;
- }
-
- /** Returns the number of bytes included in the {@code ByteRange}. */
- int getLength() {
- return (int) (mEnd - mStart + 1);
- }
-
- /** Creates a new {@link ByteRange} from {@code mStart} to {@code mEnd + length}. */
- ByteRange extend(long length) {
- Preconditions.checkArgument(length > 0);
- return new ByteRange(mStart, mEnd + length);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof ByteRange)) {
- return false;
- }
-
- ByteRange byteRange = (ByteRange) o;
- return (mEnd == byteRange.mEnd && mStart == byteRange.mStart);
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + (int) (mStart ^ (mStart >>> 32));
- result = 31 * result + (int) (mEnd ^ (mEnd >>> 32));
- return result;
- }
-
- @Override
- public String toString() {
- return String.format("ByteRange{mStart=%d, mEnd=%d}", mStart, mEnd);
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
deleted file mode 100644
index 812cfbd76e31..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkHash;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-/** Encrypts chunks of a file using AES/GCM. */
-public class ChunkEncryptor {
- private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
- private static final int GCM_NONCE_LENGTH_BYTES = 12;
- private static final int GCM_TAG_LENGTH_BYTES = 16;
-
- private final SecretKey mSecretKey;
- private final SecureRandom mSecureRandom;
-
- /**
- * A new instance using {@code mSecretKey} to encrypt chunks and {@code mSecureRandom} to
- * generate nonces.
- */
- public ChunkEncryptor(SecretKey secretKey, SecureRandom secureRandom) {
- this.mSecretKey = secretKey;
- this.mSecureRandom = secureRandom;
- }
-
- /**
- * Transforms {@code plaintext} into an {@link EncryptedChunk}.
- *
- * @param plaintextHash The hash of the plaintext to encrypt, to attach as the key of the chunk.
- * @param plaintext Bytes to encrypt.
- * @throws InvalidKeyException If the given secret key is not a valid AES key for decryption.
- * @throws IllegalBlockSizeException If the input data cannot be encrypted using
- * AES/GCM/NoPadding. This should never be the case.
- */
- public EncryptedChunk encrypt(ChunkHash plaintextHash, byte[] plaintext)
- throws InvalidKeyException, IllegalBlockSizeException {
- byte[] nonce = generateNonce();
- Cipher cipher;
- try {
- cipher = Cipher.getInstance(CIPHER_ALGORITHM);
- cipher.init(
- Cipher.ENCRYPT_MODE,
- mSecretKey,
- new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, nonce));
- } catch (NoSuchAlgorithmException
- | NoSuchPaddingException
- | InvalidAlgorithmParameterException e) {
- // This can not happen - AES/GCM/NoPadding is supported.
- throw new AssertionError(e);
- }
- byte[] encryptedBytes;
- try {
- encryptedBytes = cipher.doFinal(plaintext);
- } catch (BadPaddingException e) {
- // This can not happen - BadPaddingException can only be thrown in decrypt mode.
- throw new AssertionError("Impossible: threw BadPaddingException in encrypt mode.");
- }
-
- return EncryptedChunk.create(/*key=*/ plaintextHash, nonce, encryptedBytes);
- }
-
- private byte[] generateNonce() {
- byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
- mSecureRandom.nextBytes(nonce);
- return nonce;
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java
deleted file mode 100644
index 145b7bf82517..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkHash;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-
-/** Computes the SHA-256 HMAC of a chunk of bytes. */
-public class ChunkHasher {
- private static final String MAC_ALGORITHM = "HmacSHA256";
-
- private final SecretKey mSecretKey;
-
- /** Constructs a new hasher which computes the HMAC using the given secret key. */
- public ChunkHasher(SecretKey secretKey) {
- this.mSecretKey = secretKey;
- }
-
- /** Returns the SHA-256 over the given bytes. */
- public ChunkHash computeHash(byte[] plaintext) throws InvalidKeyException {
- try {
- Mac mac = Mac.getInstance(MAC_ALGORITHM);
- mac.init(mSecretKey);
- return new ChunkHash(mac.doFinal(plaintext));
- } catch (NoSuchAlgorithmException e) {
- // This can not happen - AES/GCM/NoPadding is available as part of the framework.
- throw new AssertionError(e);
- }
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java b/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java
deleted file mode 100644
index b91913e5fc80..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.GeneralSecurityException;
-
-/** Splits an input stream into chunks, which are to be encrypted separately. */
-public interface Chunker {
- /**
- * Splits the input stream into chunks.
- *
- * @param inputStream The input stream.
- * @param chunkConsumer A function that processes each chunk as it is produced.
- * @throws IOException If there is a problem reading the input stream.
- * @throws GeneralSecurityException if the consumer function throws an error.
- */
- void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer)
- throws IOException, GeneralSecurityException;
-
- /** Function that consumes chunks. */
- interface ChunkConsumer {
- /**
- * Invoked for each chunk.
- *
- * @param chunk Plaintext bytes of chunk.
- * @throws GeneralSecurityException if there is an issue encrypting the chunk.
- */
- void accept(byte[] chunk) throws GeneralSecurityException;
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java
deleted file mode 100644
index 69fb5cbf606d..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/** Writes backup data to a diff script, using a {@link SingleStreamDiffScriptWriter}. */
-public class DiffScriptBackupWriter implements BackupWriter {
- /**
- * The maximum size of a chunk in the diff script. The diff script writer {@code mWriter} will
- * buffer this many bytes in memory.
- */
- private static final int ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES = 1024 * 1024;
-
- private final SingleStreamDiffScriptWriter mWriter;
- private long mBytesWritten;
-
- /**
- * Constructs a new writer which writes the diff script to the given output stream, using the
- * maximum new chunk size {@code ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES}.
- */
- public static DiffScriptBackupWriter newInstance(OutputStream outputStream) {
- SingleStreamDiffScriptWriter writer =
- new SingleStreamDiffScriptWriter(
- outputStream, ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES);
- return new DiffScriptBackupWriter(writer);
- }
-
- @VisibleForTesting
- DiffScriptBackupWriter(SingleStreamDiffScriptWriter writer) {
- mWriter = writer;
- }
-
- @Override
- public void writeBytes(byte[] bytes) throws IOException {
- for (byte b : bytes) {
- mWriter.writeByte(b);
- }
-
- mBytesWritten += bytes.length;
- }
-
- @Override
- public void writeChunk(long start, int length) throws IOException {
- mWriter.writeChunk(start, length);
- mBytesWritten += length;
- }
-
- @Override
- public long getBytesWritten() {
- return mBytesWritten;
- }
-
- @Override
- public void flush() throws IOException {
- mWriter.flush();
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java
deleted file mode 100644
index 49d15712d4cc..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/** Writer that formats a Diff Script and writes it to an output source. */
-interface DiffScriptWriter {
- /** Adds a new byte to the diff script. */
- void writeByte(byte b) throws IOException;
-
- /** Adds a known chunk to the diff script. */
- void writeChunk(long chunkStart, int chunkLength) throws IOException;
-
- /** Indicates that no more bytes or chunks will be added to the diff script. */
- void flush() throws IOException;
-
- interface Factory {
- DiffScriptWriter create(OutputStream outputStream);
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java b/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java
deleted file mode 100644
index 1f936eb2bfa5..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.backup.encryption.chunk.ChunkHash;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * A chunk of a file encrypted using AES/GCM.
- *
- * <p>TODO(b/116575321): After all code is ported, remove the factory method and rename
- * encryptedBytes(), key() and nonce().
- */
-public class EncryptedChunk {
- public static final int KEY_LENGTH_BYTES = ChunkHash.HASH_LENGTH_BYTES;
- public static final int NONCE_LENGTH_BYTES = 12;
-
- /**
- * Constructs a new instance with the given key, nonce, and encrypted bytes.
- *
- * @param key SHA-256 Hmac of the chunk plaintext.
- * @param nonce Nonce with which the bytes of the chunk were encrypted.
- * @param encryptedBytes Encrypted bytes of the chunk.
- */
- public static EncryptedChunk create(ChunkHash key, byte[] nonce, byte[] encryptedBytes) {
- Preconditions.checkArgument(
- nonce.length == NONCE_LENGTH_BYTES, "Nonce does not have the correct length.");
- return new EncryptedChunk(key, nonce, encryptedBytes);
- }
-
- private ChunkHash mKey;
- private byte[] mNonce;
- private byte[] mEncryptedBytes;
-
- private EncryptedChunk(ChunkHash key, byte[] nonce, byte[] encryptedBytes) {
- mKey = key;
- mNonce = nonce;
- mEncryptedBytes = encryptedBytes;
- }
-
- /** The SHA-256 Hmac of the plaintext bytes of the chunk. */
- public ChunkHash key() {
- return mKey;
- }
-
- /** The nonce with which the chunk was encrypted. */
- public byte[] nonce() {
- return mNonce;
- }
-
- /** The encrypted bytes of the chunk. */
- public byte[] encryptedBytes() {
- return mEncryptedBytes;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof EncryptedChunk)) {
- return false;
- }
-
- EncryptedChunk encryptedChunkOrdering = (EncryptedChunk) o;
- return Arrays.equals(mEncryptedBytes, encryptedChunkOrdering.mEncryptedBytes)
- && Arrays.equals(mNonce, encryptedChunkOrdering.mNonce)
- && mKey.equals(encryptedChunkOrdering.mKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mKey, Arrays.hashCode(mNonce), Arrays.hashCode(mEncryptedBytes));
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java b/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
deleted file mode 100644
index eaf701ce0bf5..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import java.io.IOException;
-
-/** Encodes an {@link EncryptedChunk} as bytes to write to the encrypted backup file. */
-public interface EncryptedChunkEncoder {
- /**
- * Encodes the given chunk and asks the writer to write it.
- *
- * <p>The chunk will be encoded in the format [nonce]+[encrypted data].
- *
- * <p>TODO(b/116575321): Choose a more descriptive method name after the code move is done.
- */
- void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException;
-
- /**
- * Returns the length in bytes that this chunk would be if encoded with {@link
- * #writeChunkToWriter}.
- */
- int getEncodedLengthOfChunk(EncryptedChunk chunk);
-
- /**
- * Returns the {@link ChunkOrderingType} that must be included in the backup file, when using
- * this decoder, so that the file may be correctly decoded.
- */
- @ChunkOrderingType
- int getChunkOrderingType();
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java b/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
deleted file mode 100644
index 5c902cad7495..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
-import java.io.IOException;
-
-/**
- * Encodes an {@link EncryptedChunk} as bytes, prepending the length of the chunk.
- *
- * <p>This allows us to decode the backup file during restore without any extra information about
- * the boundaries of the chunks. The backup file should contain a chunk ordering in mode {@link
- * ChunksMetadataProto#INLINE_LENGTHS}.
- *
- * <p>We use this implementation during key value backup.
- */
-public class InlineLengthsEncryptedChunkEncoder implements EncryptedChunkEncoder {
- public static final int BYTES_LENGTH = Integer.SIZE / Byte.SIZE;
-
- private final LengthlessEncryptedChunkEncoder mLengthlessEncryptedChunkEncoder =
- new LengthlessEncryptedChunkEncoder();
-
- @Override
- public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException {
- int length = mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk);
- writer.writeBytes(toByteArray(length));
- mLengthlessEncryptedChunkEncoder.writeChunkToWriter(writer, chunk);
- }
-
- @Override
- public int getEncodedLengthOfChunk(EncryptedChunk chunk) {
- return BYTES_LENGTH + mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk);
- }
-
- @Override
- @ChunkOrderingType
- public int getChunkOrderingType() {
- return ChunksMetadataProto.INLINE_LENGTHS;
- }
-
- /**
- * Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to
- * {@code ByteBuffer.allocate(4).putInt(value).array()}. For example, the input value {@code
- * 0x12131415} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15}}.
- *
- * <p>Equivalent to guava's Ints.toByteArray.
- */
- static byte[] toByteArray(int value) {
- return new byte[] {
- (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value
- };
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java b/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
deleted file mode 100644
index 4b849818f1c3..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
-import java.io.IOException;
-
-/**
- * Encodes an {@link EncryptedChunk} as bytes without including any information about the length of
- * the chunk.
- *
- * <p>In order for us to decode the backup file during restore it must include a chunk ordering in
- * mode {@link ChunksMetadataProto#EXPLICIT_STARTS}, which contains the boundaries of the chunks in
- * the encrypted file. This information allows us to decode the backup file and divide it into
- * chunks without including the length of each chunk inline.
- *
- * <p>We use this implementation during full backup.
- */
-public class LengthlessEncryptedChunkEncoder implements EncryptedChunkEncoder {
- @Override
- public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException {
- writer.writeBytes(chunk.nonce());
- writer.writeBytes(chunk.encryptedBytes());
- }
-
- @Override
- public int getEncodedLengthOfChunk(EncryptedChunk chunk) {
- return chunk.nonce().length + chunk.encryptedBytes().length;
- }
-
- @Override
- @ChunkOrderingType
- public int getChunkOrderingType() {
- return ChunksMetadataProto.EXPLICIT_STARTS;
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java b/services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java
deleted file mode 100644
index 4aea60121810..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import java.io.OutputStream;
-
-/** An interface that wraps one {@link OutputStream} with another for filtration purposes. */
-public interface OutputStreamWrapper {
- /** Wraps a given {@link OutputStream}. */
- OutputStream wrap(OutputStream outputStream);
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java
deleted file mode 100644
index 839dc7c7b5ce..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/** Writes data straight to an output stream. */
-public class RawBackupWriter implements BackupWriter {
- private final OutputStream outputStream;
- private long bytesWritten;
-
- /** Constructs a new writer which writes bytes to the given output stream. */
- public RawBackupWriter(OutputStream outputStream) {
- this.outputStream = outputStream;
- }
-
- @Override
- public void writeBytes(byte[] bytes) throws IOException {
- outputStream.write(bytes);
- bytesWritten += bytes.length;
- }
-
- @Override
- public void writeChunk(long start, int length) throws IOException {
- throw new UnsupportedOperationException("RawBackupWriter cannot write existing chunks");
- }
-
- @Override
- public long getBytesWritten() {
- return bytesWritten;
- }
-
- @Override
- public void flush() throws IOException {
- outputStream.flush();
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java
deleted file mode 100644
index 0e4bd58345d5..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import android.annotation.Nullable;
-
-import com.android.internal.util.Preconditions;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.util.Locale;
-
-/**
- * A {@link DiffScriptWriter} that writes an entire diff script to a single {@link OutputStream}.
- */
-public class SingleStreamDiffScriptWriter implements DiffScriptWriter {
- static final byte LINE_SEPARATOR = 0xA;
- private static final Charset UTF_8 = Charset.forName("UTF-8");
-
- private final int mMaxNewByteChunkSize;
- private final OutputStream mOutputStream;
- private final byte[] mByteBuffer;
- private int mBufferSize = 0;
- // Each chunk could be written immediately to the output stream. However,
- // it is possible that chunks may overlap. We therefore cache the most recent
- // reusable chunk and try to merge it with future chunks.
- private ByteRange mReusableChunk;
-
- public SingleStreamDiffScriptWriter(OutputStream outputStream, int maxNewByteChunkSize) {
- mOutputStream = outputStream;
- mMaxNewByteChunkSize = maxNewByteChunkSize;
- mByteBuffer = new byte[maxNewByteChunkSize];
- }
-
- @Override
- public void writeByte(byte b) throws IOException {
- if (mReusableChunk != null) {
- writeReusableChunk();
- }
- mByteBuffer[mBufferSize++] = b;
- if (mBufferSize == mMaxNewByteChunkSize) {
- writeByteBuffer();
- }
- }
-
- @Override
- public void writeChunk(long chunkStart, int chunkLength) throws IOException {
- Preconditions.checkArgument(chunkStart >= 0);
- Preconditions.checkArgument(chunkLength > 0);
- if (mBufferSize != 0) {
- writeByteBuffer();
- }
-
- if (mReusableChunk != null && mReusableChunk.getEnd() + 1 == chunkStart) {
- // The new chunk overlaps the old, so combine them into a single byte range.
- mReusableChunk = mReusableChunk.extend(chunkLength);
- } else {
- writeReusableChunk();
- mReusableChunk = new ByteRange(chunkStart, chunkStart + chunkLength - 1);
- }
- }
-
- @Override
- public void flush() throws IOException {
- Preconditions.checkState(!(mBufferSize != 0 && mReusableChunk != null));
- if (mBufferSize != 0) {
- writeByteBuffer();
- }
- if (mReusableChunk != null) {
- writeReusableChunk();
- }
- mOutputStream.flush();
- }
-
- private void writeByteBuffer() throws IOException {
- mOutputStream.write(Integer.toString(mBufferSize).getBytes(UTF_8));
- mOutputStream.write(LINE_SEPARATOR);
- mOutputStream.write(mByteBuffer, 0, mBufferSize);
- mOutputStream.write(LINE_SEPARATOR);
- mBufferSize = 0;
- }
-
- private void writeReusableChunk() throws IOException {
- if (mReusableChunk != null) {
- mOutputStream.write(
- String.format(
- Locale.US,
- "%d-%d",
- mReusableChunk.getStart(),
- mReusableChunk.getEnd())
- .getBytes(UTF_8));
- mOutputStream.write(LINE_SEPARATOR);
- mReusableChunk = null;
- }
- }
-
- /** A factory that creates {@link SingleStreamDiffScriptWriter}s. */
- public static class Factory implements DiffScriptWriter.Factory {
- private final int mMaxNewByteChunkSize;
- private final OutputStreamWrapper mOutputStreamWrapper;
-
- public Factory(int maxNewByteChunkSize, @Nullable OutputStreamWrapper outputStreamWrapper) {
- mMaxNewByteChunkSize = maxNewByteChunkSize;
- mOutputStreamWrapper = outputStreamWrapper;
- }
-
- @Override
- public SingleStreamDiffScriptWriter create(OutputStream outputStream) {
- if (mOutputStreamWrapper != null) {
- outputStream = mOutputStreamWrapper.wrap(outputStream);
- }
- return new SingleStreamDiffScriptWriter(outputStream, mMaxNewByteChunkSize);
- }
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
deleted file mode 100644
index 18011f620b24..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking.cdc;
-
-import static com.android.internal.util.Preconditions.checkArgument;
-
-import com.android.server.backup.encryption.chunking.Chunker;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-
-/** Splits a stream of bytes into variable-sized chunks, using content-defined chunking. */
-public class ContentDefinedChunker implements Chunker {
- private static final int WINDOW_SIZE = 31;
- private static final byte DEFAULT_OUT_BYTE = (byte) 0;
-
- private final byte[] mChunkBuffer;
- private final RabinFingerprint64 mRabinFingerprint64;
- private final FingerprintMixer mFingerprintMixer;
- private final BreakpointPredicate mBreakpointPredicate;
- private final int mMinChunkSize;
- private final int mMaxChunkSize;
-
- /**
- * Constructor.
- *
- * @param minChunkSize The minimum size of a chunk. No chunk will be produced of a size smaller
- * than this except possibly at the very end of the stream.
- * @param maxChunkSize The maximum size of a chunk. No chunk will be produced of a larger size.
- * @param rabinFingerprint64 Calculates fingerprints, with which to determine breakpoints.
- * @param breakpointPredicate Given a Rabin fingerprint, returns whether this ought to be a
- * breakpoint.
- */
- public ContentDefinedChunker(
- int minChunkSize,
- int maxChunkSize,
- RabinFingerprint64 rabinFingerprint64,
- FingerprintMixer fingerprintMixer,
- BreakpointPredicate breakpointPredicate) {
- checkArgument(
- minChunkSize >= WINDOW_SIZE,
- "Minimum chunk size must be greater than window size.");
- checkArgument(
- maxChunkSize >= minChunkSize,
- "Maximum chunk size cannot be smaller than minimum chunk size.");
- mChunkBuffer = new byte[maxChunkSize];
- mRabinFingerprint64 = rabinFingerprint64;
- mBreakpointPredicate = breakpointPredicate;
- mFingerprintMixer = fingerprintMixer;
- mMinChunkSize = minChunkSize;
- mMaxChunkSize = maxChunkSize;
- }
-
- /**
- * Breaks the input stream into variable-sized chunks.
- *
- * @param inputStream The input bytes to break into chunks.
- * @param chunkConsumer A function to process each chunk as it's generated.
- * @throws IOException Thrown if there is an issue reading from the input stream.
- * @throws GeneralSecurityException Thrown if the {@link ChunkConsumer} throws it.
- */
- @Override
- public void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer)
- throws IOException, GeneralSecurityException {
- int chunkLength;
- int initialReadLength = mMinChunkSize - WINDOW_SIZE;
-
- // Performance optimization - there is no reason to calculate fingerprints for windows
- // ending before the minimum chunk size.
- while ((chunkLength =
- inputStream.read(mChunkBuffer, /*off=*/ 0, /*len=*/ initialReadLength))
- != -1) {
- int b;
- long fingerprint = 0L;
-
- while ((b = inputStream.read()) != -1) {
- byte inByte = (byte) b;
- byte outByte = getCurrentWindowStartByte(chunkLength);
- mChunkBuffer[chunkLength++] = inByte;
-
- fingerprint =
- mRabinFingerprint64.computeFingerprint64(inByte, outByte, fingerprint);
-
- if (chunkLength >= mMaxChunkSize
- || (chunkLength >= mMinChunkSize
- && mBreakpointPredicate.isBreakpoint(
- mFingerprintMixer.mix(fingerprint)))) {
- chunkConsumer.accept(Arrays.copyOf(mChunkBuffer, chunkLength));
- chunkLength = 0;
- break;
- }
- }
-
- if (chunkLength > 0) {
- chunkConsumer.accept(Arrays.copyOf(mChunkBuffer, chunkLength));
- }
- }
- }
-
- private byte getCurrentWindowStartByte(int chunkLength) {
- if (chunkLength < mMinChunkSize) {
- return DEFAULT_OUT_BYTE;
- } else {
- return mChunkBuffer[chunkLength - WINDOW_SIZE];
- }
- }
-
- /** Whether the current fingerprint indicates the end of a chunk. */
- public interface BreakpointPredicate {
-
- /**
- * Returns {@code true} if the fingerprint of the last {@code WINDOW_SIZE} bytes indicates
- * the chunk ought to end at this position.
- *
- * @param fingerprint Fingerprint of the last {@code WINDOW_SIZE} bytes.
- * @return Whether this ought to be a chunk breakpoint.
- */
- boolean isBreakpoint(long fingerprint);
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
deleted file mode 100644
index e9f30505c112..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking.cdc;
-
-import static com.android.internal.util.Preconditions.checkArgument;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Helper for mixing fingerprint with key material.
- *
- * <p>We do this as otherwise the Rabin fingerprint leaks information about the plaintext. i.e., if
- * two users have the same file, it will be partitioned by Rabin in the same way, allowing us to
- * infer that it is the same as another user's file.
- *
- * <p>By mixing the fingerprint with the user's secret key, the chunking method is different on a
- * per key basis. Each application has its own {@link SecretKey}, so we cannot infer that a file is
- * the same even across multiple applications owned by the same user, never mind across multiple
- * users.
- *
- * <p>Instead of directly mixing the fingerprint with the user's secret, we first securely and
- * deterministically derive a secondary chunking key. As Rabin is not a cryptographically secure
- * hash, it might otherwise leak information about the user's secret. This prevents that from
- * happening.
- */
-public class FingerprintMixer {
- public static final int SALT_LENGTH_BYTES = 256 / Byte.SIZE;
- private static final String DERIVED_KEY_NAME = "RabinFingerprint64Mixer";
-
- private final long mAddend;
- private final long mMultiplicand;
-
- /**
- * A new instance from a given secret key and salt. Salt must be the same across incremental
- * backups, or a different chunking strategy will be used each time, defeating the dedup.
- *
- * @param secretKey The application-specific secret.
- * @param salt The salt.
- * @throws InvalidKeyException If the encoded form of {@code secretKey} is inaccessible.
- */
- public FingerprintMixer(SecretKey secretKey, byte[] salt) throws InvalidKeyException {
- checkArgument(salt.length == SALT_LENGTH_BYTES, "Requires a 256-bit salt.");
- byte[] keyBytes = secretKey.getEncoded();
- if (keyBytes == null) {
- throw new InvalidKeyException("SecretKey must support encoding for FingerprintMixer.");
- }
- byte[] derivedKey =
- Hkdf.hkdf(keyBytes, salt, DERIVED_KEY_NAME.getBytes(StandardCharsets.UTF_8));
- ByteBuffer buffer = ByteBuffer.wrap(derivedKey);
- mAddend = buffer.getLong();
- // Multiplicand must be odd - otherwise we lose some bits of the Rabin fingerprint when
- // mixing
- mMultiplicand = buffer.getLong() | 1;
- }
-
- /**
- * Mixes the fingerprint with the derived key material. This is performed by adding part of the
- * derived key and multiplying by another part of the derived key (which is forced to be odd, so
- * that the operation is reversible).
- *
- * @param fingerprint A 64-bit Rabin fingerprint.
- * @return The mixed fingerprint.
- */
- long mix(long fingerprint) {
- return ((fingerprint + mAddend) * mMultiplicand);
- }
-
- /** The addend part of the derived key. */
- long getAddend() {
- return mAddend;
- }
-
- /** The multiplicand part of the derived key. */
- long getMultiplicand() {
- return mMultiplicand;
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
deleted file mode 100644
index 6f4f549ab2d7..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking.cdc;
-
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * Secure HKDF utils. Allows client to deterministically derive additional key material from a base
- * secret. If the derived key material is compromised, this does not in of itself compromise the
- * root secret.
- *
- * <p>TODO(b/116575321): After all code is ported, rename this class to HkdfUtils.
- */
-public final class Hkdf {
- private static final byte[] CONSTANT_01 = {0x01};
- private static final String HmacSHA256 = "HmacSHA256";
- private static final String AES = "AES";
-
- /**
- * Implements HKDF (RFC 5869) with the SHA-256 hash and a 256-bit output key length.
- *
- * <p>IMPORTANT: The use or edit of this method requires a security review.
- *
- * @param masterKey Master key from which to derive sub-keys.
- * @param salt A randomly generated 256-bit byte string.
- * @param data Arbitrary information that is bound to the derived key (i.e., used in its
- * creation).
- * @return Raw derived key bytes = HKDF-SHA256(masterKey, salt, data).
- * @throws InvalidKeyException If the salt can not be used as a valid key.
- */
- static byte[] hkdf(byte[] masterKey, byte[] salt, byte[] data) throws InvalidKeyException {
- checkNotNull(masterKey, "HKDF requires master key to be set.");
- checkNotNull(salt, "HKDF requires a salt.");
- checkNotNull(data, "No data provided to HKDF.");
- return hkdfSha256Expand(hkdfSha256Extract(masterKey, salt), data);
- }
-
- private Hkdf() {}
-
- /**
- * The HKDF (RFC 5869) extraction function, using the SHA-256 hash function. This function is
- * used to pre-process the {@code inputKeyMaterial} and mix it with the {@code salt}, producing
- * output suitable for use with HKDF expansion function (which produces the actual derived key).
- *
- * <p>IMPORTANT: The use or edit of this method requires a security review.
- *
- * @see #hkdfSha256Expand(byte[], byte[])
- * @return HMAC-SHA256(salt, inputKeyMaterial) (salt is the "key" for the HMAC)
- * @throws InvalidKeyException If the salt can not be used as a valid key.
- */
- private static byte[] hkdfSha256Extract(byte[] inputKeyMaterial, byte[] salt)
- throws InvalidKeyException {
- // Note that the SecretKey encoding format is defined to be RAW, so the encoded form should
- // be consistent across implementations.
- Mac sha256;
- try {
- sha256 = Mac.getInstance(HmacSHA256);
- } catch (NoSuchAlgorithmException e) {
- // This can not happen - HmacSHA256 is supported by the platform.
- throw new AssertionError(e);
- }
- sha256.init(new SecretKeySpec(salt, AES));
-
- return sha256.doFinal(inputKeyMaterial);
- }
-
- /**
- * Special case of HKDF (RFC 5869) expansion function, using the SHA-256 hash function and
- * allowing for a maximum output length of 256 bits.
- *
- * <p>IMPORTANT: The use or edit of this method requires a security review.
- *
- * @param pseudoRandomKey Generated by {@link #hkdfSha256Extract(byte[], byte[])}.
- * @param info Arbitrary information the derived key should be bound to.
- * @return Raw derived key bytes = HMAC-SHA256(pseudoRandomKey, info | 0x01).
- * @throws InvalidKeyException If the salt can not be used as a valid key.
- */
- private static byte[] hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info)
- throws InvalidKeyException {
- // Note that RFC 5869 computes number of blocks N = ceil(hash length / output length), but
- // here we only deal with a 256 bit hash up to a 256 bit output, yielding N=1.
- Mac sha256;
- try {
- sha256 = Mac.getInstance(HmacSHA256);
- } catch (NoSuchAlgorithmException e) {
- // This can not happen - HmacSHA256 is supported by the platform.
- throw new AssertionError(e);
- }
- sha256.init(new SecretKeySpec(pseudoRandomKey, AES));
-
- sha256.update(info);
- sha256.update(CONSTANT_01);
- return sha256.doFinal();
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
deleted file mode 100644
index e867e7c1b801..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking.cdc;
-
-import static com.android.internal.util.Preconditions.checkArgument;
-
-import com.android.server.backup.encryption.chunking.cdc.ContentDefinedChunker.BreakpointPredicate;
-
-/**
- * Function to determine whether a 64-bit fingerprint ought to be a chunk breakpoint.
- *
- * <p>This works by checking whether there are at least n leading zeros in the fingerprint. n is
- * calculated to on average cause a breakpoint after a given number of trials (provided in the
- * constructor). This allows us to choose a number of trials that gives a desired average chunk
- * size. This works because the fingerprint is pseudo-randomly distributed.
- */
-public class IsChunkBreakpoint implements BreakpointPredicate {
- private final int mLeadingZeros;
- private final long mBitmask;
-
- /**
- * A new instance that causes a breakpoint after a given number of trials on average.
- *
- * @param averageNumberOfTrialsUntilBreakpoint The number of trials after which on average to
- * create a new chunk. If this is not a power of 2, some precision is sacrificed (i.e., on
- * average, breaks will actually happen after the nearest power of 2 to the average number
- * of trials passed in).
- */
- public IsChunkBreakpoint(long averageNumberOfTrialsUntilBreakpoint) {
- checkArgument(
- averageNumberOfTrialsUntilBreakpoint >= 0,
- "Average number of trials must be non-negative");
-
- // Want n leading zeros after t trials.
- // P(leading zeros = n) = 1/2^n
- // Expected num trials to get n leading zeros = 1/2^-n
- // t = 1/2^-n
- // n = log2(t)
- mLeadingZeros = (int) Math.round(log2(averageNumberOfTrialsUntilBreakpoint));
- mBitmask = ~(~0L >>> mLeadingZeros);
- }
-
- /**
- * Returns {@code true} if {@code fingerprint} indicates that there should be a chunk
- * breakpoint.
- */
- @Override
- public boolean isBreakpoint(long fingerprint) {
- return (fingerprint & mBitmask) == 0;
- }
-
- /** Returns the number of leading zeros in the fingerprint that causes a breakpoint. */
- public int getLeadingZeros() {
- return mLeadingZeros;
- }
-
- /**
- * Calculates log base 2 of x. Not the most efficient possible implementation, but it's simple,
- * obviously correct, and is only invoked on object construction.
- */
- private static double log2(double x) {
- return Math.log(x) / Math.log(2);
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
deleted file mode 100644
index 1e14ffa5ad77..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunking.cdc;
-
-/** Helper to calculate a 64-bit Rabin fingerprint over a 31-byte window. */
-public class RabinFingerprint64 {
- private static final long DEFAULT_IRREDUCIBLE_POLYNOMIAL_64 = 0x000000000000001BL;
- private static final int POLYNOMIAL_DEGREE = 64;
- private static final int SLIDING_WINDOW_SIZE_BYTES = 31;
-
- private final long mPoly64;
- // Auxiliary tables to speed up the computation of Rabin fingerprints.
- private final long[] mTableFP64 = new long[256];
- private final long[] mTableOutByte = new long[256];
-
- /**
- * Constructs a new instance over the given irreducible 64-degree polynomial. It is up to the
- * caller to determine that the polynomial is irreducible. If it is not the fingerprinting will
- * not behave as expected.
- *
- * @param poly64 The polynomial.
- */
- public RabinFingerprint64(long poly64) {
- mPoly64 = poly64;
- }
-
- /** Constructs a new instance using {@code x^64 + x^4 + x + 1} as the irreducible polynomial. */
- public RabinFingerprint64() {
- this(DEFAULT_IRREDUCIBLE_POLYNOMIAL_64);
- computeFingerprintTables64();
- computeFingerprintTables64Windowed();
- }
-
- /**
- * Computes the fingerprint for the new sliding window given the fingerprint of the previous
- * sliding window, the byte sliding in, and the byte sliding out.
- *
- * @param inChar The new char coming into the sliding window.
- * @param outChar The left most char sliding out of the window.
- * @param fingerPrint Fingerprint for previous window.
- * @return New fingerprint for the new sliding window.
- */
- public long computeFingerprint64(byte inChar, byte outChar, long fingerPrint) {
- return (fingerPrint << 8)
- ^ (inChar & 0xFF)
- ^ mTableFP64[(int) (fingerPrint >>> 56)]
- ^ mTableOutByte[outChar & 0xFF];
- }
-
- /** Compute auxiliary tables to speed up the fingerprint computation. */
- private void computeFingerprintTables64() {
- long[] degreesRes64 = new long[POLYNOMIAL_DEGREE];
- degreesRes64[0] = mPoly64;
- for (int i = 1; i < POLYNOMIAL_DEGREE; i++) {
- if ((degreesRes64[i - 1] & (1L << 63)) == 0) {
- degreesRes64[i] = degreesRes64[i - 1] << 1;
- } else {
- degreesRes64[i] = (degreesRes64[i - 1] << 1) ^ mPoly64;
- }
- }
- for (int i = 0; i < 256; i++) {
- int currIndex = i;
- for (int j = 0; (currIndex > 0) && (j < 8); j++) {
- if ((currIndex & 0x1) == 1) {
- mTableFP64[i] ^= degreesRes64[j];
- }
- currIndex >>>= 1;
- }
- }
- }
-
- /**
- * Compute auxiliary table {@code mTableOutByte} to facilitate the computing of fingerprints for
- * sliding windows. This table is to take care of the effect on the fingerprint when the
- * leftmost byte in the window slides out.
- */
- private void computeFingerprintTables64Windowed() {
- // Auxiliary array degsRes64[8] defined by: <code>degsRes64[i] = x^(8 *
- // SLIDING_WINDOW_SIZE_BYTES + i) mod this.mPoly64.</code>
- long[] degsRes64 = new long[8];
- degsRes64[0] = mPoly64;
- for (int i = 65; i < 8 * (SLIDING_WINDOW_SIZE_BYTES + 1); i++) {
- if ((degsRes64[(i - 1) % 8] & (1L << 63)) == 0) {
- degsRes64[i % 8] = degsRes64[(i - 1) % 8] << 1;
- } else {
- degsRes64[i % 8] = (degsRes64[(i - 1) % 8] << 1) ^ mPoly64;
- }
- }
- for (int i = 0; i < 256; i++) {
- int currIndex = i;
- for (int j = 0; (currIndex > 0) && (j < 8); j++) {
- if ((currIndex & 0x1) == 1) {
- mTableOutByte[i] ^= degsRes64[j];
- }
- currIndex >>>= 1;
- }
- }
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java b/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
deleted file mode 100644
index f356b4f102e2..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.keys;
-
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import android.annotation.IntDef;
-import android.content.Context;
-import android.security.keystore.recovery.InternalRecoveryServiceException;
-import android.security.keystore.recovery.RecoveryController;
-import android.util.Slog;
-
-import javax.crypto.SecretKey;
-
-/**
- * Wraps a {@link RecoveryController}'s {@link SecretKey}. These are kept in "AndroidKeyStore" (a
- * provider for {@link java.security.KeyStore} and {@link javax.crypto.KeyGenerator}. They are also
- * synced with the recoverable key store, wrapped by the primary key. This allows them to be
- * recovered on a user's subsequent device through providing their lock screen secret.
- */
-public class RecoverableKeyStoreSecondaryKey {
- private static final String TAG = "RecoverableKeyStoreSecondaryKey";
-
- private final String mAlias;
- private final SecretKey mSecretKey;
-
- /**
- * A new instance.
- *
- * @param alias The alias. It is keyed with this in AndroidKeyStore and the recoverable key
- * store.
- * @param secretKey The key.
- */
- public RecoverableKeyStoreSecondaryKey(String alias, SecretKey secretKey) {
- mAlias = checkNotNull(alias);
- mSecretKey = checkNotNull(secretKey);
- }
-
- /**
- * The ID, as stored in the recoverable {@link java.security.KeyStore}, and as used to identify
- * wrapped tertiary keys on the backup server.
- */
- public String getAlias() {
- return mAlias;
- }
-
- /** The secret key, to be used to wrap tertiary keys. */
- public SecretKey getSecretKey() {
- return mSecretKey;
- }
-
- /**
- * The status of the key. i.e., whether it's been synced to remote trusted hardware.
- *
- * @param context The application context.
- * @return One of {@link Status#SYNCED}, {@link Status#NOT_SYNCED} or {@link Status#DESTROYED}.
- */
- public @Status int getStatus(Context context) {
- try {
- return getStatusInternal(context);
- } catch (InternalRecoveryServiceException e) {
- Slog.wtf(TAG, "Internal error getting recovery status", e);
- // Return NOT_SYNCED by default, as we do not want the backups to fail or to repeatedly
- // attempt to reinitialize.
- return Status.NOT_SYNCED;
- }
- }
-
- private @Status int getStatusInternal(Context context) throws InternalRecoveryServiceException {
- int status = RecoveryController.getInstance(context).getRecoveryStatus(mAlias);
- switch (status) {
- case RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE:
- return Status.DESTROYED;
- case RecoveryController.RECOVERY_STATUS_SYNCED:
- return Status.SYNCED;
- case RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS:
- return Status.NOT_SYNCED;
- default:
- // Throw an exception if we encounter a status that doesn't match any of the above.
- throw new InternalRecoveryServiceException(
- "Unexpected status from getRecoveryStatus: " + status);
- }
- }
-
- /** Status of a key in the recoverable key store. */
- @IntDef({Status.NOT_SYNCED, Status.SYNCED, Status.DESTROYED})
- public @interface Status {
- /**
- * The key has not yet been synced to remote trusted hardware. This may be because the user
- * has not yet unlocked their device.
- */
- int NOT_SYNCED = 1;
-
- /**
- * The key has been synced with remote trusted hardware. It should now be recoverable on
- * another device.
- */
- int SYNCED = 2;
-
- /** The key has been lost forever. This can occur if the user disables their lock screen. */
- int DESTROYED = 3;
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java b/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java
deleted file mode 100644
index c89076b9928f..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.keys;
-
-import android.content.Context;
-import android.security.keystore.recovery.InternalRecoveryServiceException;
-import android.security.keystore.recovery.LockScreenRequiredException;
-import android.security.keystore.recovery.RecoveryController;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import libcore.util.HexEncoding;
-
-import java.security.SecureRandom;
-import java.security.UnrecoverableKeyException;
-import java.util.Optional;
-
-import javax.crypto.SecretKey;
-
-/**
- * Manages generating, deleting, and retrieving secondary keys through {@link RecoveryController}.
- *
- * <p>The recoverable key store will be synced remotely via the {@link RecoveryController}, allowing
- * recovery of keys on other devices owned by the user.
- */
-public class RecoverableKeyStoreSecondaryKeyManager {
- private static final String BACKUP_KEY_ALIAS_PREFIX =
- "com.android.server.backup/recoverablekeystore/";
- private static final int BACKUP_KEY_SUFFIX_LENGTH_BITS = 128;
- private static final int BITS_PER_BYTE = 8;
-
- /** A new instance. */
- public static RecoverableKeyStoreSecondaryKeyManager getInstance(Context context) {
- return new RecoverableKeyStoreSecondaryKeyManager(
- RecoveryController.getInstance(context), new SecureRandom());
- }
-
- private final RecoveryController mRecoveryController;
- private final SecureRandom mSecureRandom;
-
- @VisibleForTesting
- public RecoverableKeyStoreSecondaryKeyManager(
- RecoveryController recoveryController, SecureRandom secureRandom) {
- mRecoveryController = recoveryController;
- mSecureRandom = secureRandom;
- }
-
- /**
- * Generates a new recoverable key using the {@link RecoveryController}.
- *
- * @throws InternalRecoveryServiceException if an unexpected error occurred generating the key.
- * @throws LockScreenRequiredException if the user does not have a lock screen. A lock screen is
- * required to generate a recoverable key.
- */
- public RecoverableKeyStoreSecondaryKey generate()
- throws InternalRecoveryServiceException, LockScreenRequiredException,
- UnrecoverableKeyException {
- String alias = generateId();
- mRecoveryController.generateKey(alias);
- SecretKey key = (SecretKey) mRecoveryController.getKey(alias);
- if (key == null) {
- throw new InternalRecoveryServiceException(
- String.format(
- "Generated key %s but could not get it back immediately afterwards.",
- alias));
- }
- return new RecoverableKeyStoreSecondaryKey(alias, key);
- }
-
- /**
- * Removes the secondary key. This means the key will no longer be recoverable.
- *
- * @param alias The alias of the key.
- * @throws InternalRecoveryServiceException if there was a {@link RecoveryController} error.
- */
- public void remove(String alias) throws InternalRecoveryServiceException {
- mRecoveryController.removeKey(alias);
- }
-
- /**
- * Returns the {@link RecoverableKeyStoreSecondaryKey} with {@code alias} if it is in the {@link
- * RecoveryController}. Otherwise, {@link Optional#empty()}.
- */
- public Optional<RecoverableKeyStoreSecondaryKey> get(String alias)
- throws InternalRecoveryServiceException, UnrecoverableKeyException {
- SecretKey secretKey = (SecretKey) mRecoveryController.getKey(alias);
- return Optional.ofNullable(secretKey)
- .map(key -> new RecoverableKeyStoreSecondaryKey(alias, key));
- }
-
- /**
- * Generates a new key alias. This has more entropy than a UUID - it can be considered
- * universally unique.
- */
- private String generateId() {
- byte[] id = new byte[BACKUP_KEY_SUFFIX_LENGTH_BITS / BITS_PER_BYTE];
- mSecureRandom.nextBytes(id);
- return BACKUP_KEY_ALIAS_PREFIX + HexEncoding.encodeToString(id);
- }
-
- /** Constructs a {@link RecoverableKeyStoreSecondaryKeyManager}. */
- public interface RecoverableKeyStoreSecondaryKeyManagerProvider {
- /** Returns a newly constructed {@link RecoverableKeyStoreSecondaryKeyManager}. */
- RecoverableKeyStoreSecondaryKeyManager get();
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java b/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
deleted file mode 100644
index ebf09dfd6ba6..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.keys;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-
-/** 256-bit AES key generator. Each app should have its own separate AES key. */
-public class TertiaryKeyGenerator {
- private static final int KEY_SIZE_BITS = 256;
- private static final String KEY_ALGORITHM = "AES";
-
- private final KeyGenerator mKeyGenerator;
-
- /** New instance generating keys using {@code secureRandom}. */
- public TertiaryKeyGenerator(SecureRandom secureRandom) {
- try {
- mKeyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
- mKeyGenerator.init(KEY_SIZE_BITS, secureRandom);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(
- "Impossible condition: JCE thinks it does not support AES.", e);
- }
- }
-
- /** Generates a new random AES key. */
- public SecretKey generate() {
- return mKeyGenerator.generateKey();
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java b/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
deleted file mode 100644
index ec90f6c8c95e..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.keys;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Locale;
-
-/**
- * Tracks when a tertiary key rotation is due.
- *
- * <p>After a certain number of incremental backups, the device schedules a full backup, which will
- * generate a new encryption key, effecting a key rotation. We should do this on a regular basis so
- * that if a key does become compromised it has limited value to the attacker.
- *
- * <p>No additional synchronization of this class is provided. Only one instance should be used at
- * any time. This should be fine as there should be no parallelism in backups.
- */
-public class TertiaryKeyRotationTracker {
- private static final int MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION = 31;
- private static final String SHARED_PREFERENCES_NAME = "tertiary_key_rotation_tracker";
-
- private static final String TAG = "TertiaryKeyRotationTracker";
- private static final boolean DEBUG = false;
-
- /**
- * A new instance, using {@code context} to commit data to disk via {@link SharedPreferences}.
- */
- public static TertiaryKeyRotationTracker getInstance(Context context) {
- return new TertiaryKeyRotationTracker(
- context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE));
- }
-
- private final SharedPreferences mSharedPreferences;
-
- /** New instance, storing data in {@code mSharedPreferences}. */
- @VisibleForTesting
- TertiaryKeyRotationTracker(SharedPreferences sharedPreferences) {
- mSharedPreferences = sharedPreferences;
- }
-
- /**
- * Returns {@code true} if the given app is due having its key rotated.
- *
- * @param packageName The package name of the app.
- */
- public boolean isKeyRotationDue(String packageName) {
- return getBackupsSinceRotation(packageName) >= MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION;
- }
-
- /**
- * Records that an incremental backup has occurred. Each incremental backup brings the app
- * closer to the time when its key should be rotated.
- *
- * @param packageName The package name of the app for which the backup occurred.
- */
- public void recordBackup(String packageName) {
- int backupsSinceRotation = getBackupsSinceRotation(packageName) + 1;
- mSharedPreferences.edit().putInt(packageName, backupsSinceRotation).apply();
- if (DEBUG) {
- Slog.d(
- TAG,
- String.format(
- Locale.US,
- "Incremental backup for %s. %d backups until key rotation.",
- packageName,
- Math.max(
- 0,
- MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION
- - backupsSinceRotation)));
- }
- }
-
- /**
- * Resets the rotation delay for the given app. Should be invoked after a key rotation.
- *
- * @param packageName Package name of the app whose key has rotated.
- */
- public void resetCountdown(String packageName) {
- mSharedPreferences.edit().putInt(packageName, 0).apply();
- }
-
- /** Marks all enrolled packages for key rotation. */
- public void markAllForRotation() {
- SharedPreferences.Editor editor = mSharedPreferences.edit();
- for (String packageName : mSharedPreferences.getAll().keySet()) {
- editor.putInt(packageName, MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION);
- }
- editor.apply();
- }
-
- private int getBackupsSinceRotation(String packageName) {
- return mSharedPreferences.getInt(packageName, 0);
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDb.java b/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDb.java
deleted file mode 100644
index 9f6c03a6f393..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDb.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.storage;
-
-import android.content.Context;
-
-/**
- * Backup encryption SQLite database. All instances are threadsafe.
- *
- * <p>The database is automatically opened when accessing one of the tables. After the caller is
- * done they must call {@link #close()}.
- */
-public class BackupEncryptionDb {
- private final BackupEncryptionDbHelper mHelper;
-
- /** A new instance, using the storage defined by {@code context}. */
- public static BackupEncryptionDb newInstance(Context context) {
- BackupEncryptionDbHelper helper = new BackupEncryptionDbHelper(context);
- helper.setWriteAheadLoggingEnabled(true);
- return new BackupEncryptionDb(helper);
- }
-
- private BackupEncryptionDb(BackupEncryptionDbHelper helper) {
- mHelper = helper;
- }
-
- public TertiaryKeysTable getTertiaryKeysTable() {
- return new TertiaryKeysTable(mHelper);
- }
-
- /** Deletes the database. */
- public void clear() throws EncryptionDbException {
- mHelper.resetDatabase();
- }
-
- /**
- * Closes the database if it is open.
- *
- * <p>After calling this, the caller may access one of the tables again which will automatically
- * reopen the database.
- */
- public void close() {
- mHelper.close();
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java b/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java
deleted file mode 100644
index 5e8a8d9fc2ae..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.storage;
-
-import android.provider.BaseColumns;
-
-/** Contract for the backup encryption database. Describes tables present. */
-class BackupEncryptionDbContract {
- /**
- * Table containing tertiary keys belonging to the user. Tertiary keys are wrapped by a
- * secondary key, which never leaves {@code AndroidKeyStore} (a provider for {@link
- * java.security.KeyStore}). Each application has a tertiary key, which is used to encrypt the
- * backup data.
- */
- static class TertiaryKeysEntry implements BaseColumns {
- static final String TABLE_NAME = "tertiary_keys";
-
- /** Alias of the secondary key used to wrap the tertiary key. */
- static final String COLUMN_NAME_SECONDARY_KEY_ALIAS = "secondary_key_alias";
-
- /** Name of the package to which the tertiary key belongs. */
- static final String COLUMN_NAME_PACKAGE_NAME = "package_name";
-
- /** Encrypted bytes of the tertiary key. */
- static final String COLUMN_NAME_WRAPPED_KEY_BYTES = "wrapped_key_bytes";
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java b/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java
deleted file mode 100644
index c70634248dca..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.storage;
-
-import static com.android.server.backup.encryption.storage.BackupEncryptionDbContract.TertiaryKeysEntry;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-
-/** Helper for creating an instance of the backup encryption database. */
-class BackupEncryptionDbHelper extends SQLiteOpenHelper {
- private static final int DATABASE_VERSION = 1;
- static final String DATABASE_NAME = "backupencryption.db";
-
- private static final String SQL_CREATE_TERTIARY_KEYS_ENTRY =
- "CREATE TABLE "
- + TertiaryKeysEntry.TABLE_NAME
- + " ( "
- + TertiaryKeysEntry._ID
- + " INTEGER PRIMARY KEY,"
- + TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS
- + " TEXT,"
- + TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME
- + " TEXT,"
- + TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES
- + " BLOB,"
- + "UNIQUE("
- + TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS
- + ","
- + TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME
- + "))";
-
- private static final String SQL_DROP_TERTIARY_KEYS_ENTRY =
- "DROP TABLE IF EXISTS " + TertiaryKeysEntry.TABLE_NAME;
-
- BackupEncryptionDbHelper(Context context) {
- super(context, DATABASE_NAME, /*factory=*/ null, DATABASE_VERSION);
- }
-
- public void resetDatabase() throws EncryptionDbException {
- SQLiteDatabase db = getWritableDatabaseSafe();
- db.execSQL(SQL_DROP_TERTIARY_KEYS_ENTRY);
- onCreate(db);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(SQL_CREATE_TERTIARY_KEYS_ENTRY);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL(SQL_DROP_TERTIARY_KEYS_ENTRY);
- onCreate(db);
- }
-
- @Override
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL(SQL_DROP_TERTIARY_KEYS_ENTRY);
- onCreate(db);
- }
-
- /**
- * Calls {@link #getWritableDatabase()}, but catches the unchecked {@link SQLiteException} and
- * rethrows {@link EncryptionDbException}.
- */
- public SQLiteDatabase getWritableDatabaseSafe() throws EncryptionDbException {
- try {
- return super.getWritableDatabase();
- } catch (SQLiteException e) {
- throw new EncryptionDbException(e);
- }
- }
-
- /**
- * Calls {@link #getReadableDatabase()}, but catches the unchecked {@link SQLiteException} and
- * rethrows {@link EncryptionDbException}.
- */
- public SQLiteDatabase getReadableDatabaseSafe() throws EncryptionDbException {
- try {
- return super.getReadableDatabase();
- } catch (SQLiteException e) {
- throw new EncryptionDbException(e);
- }
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/EncryptionDbException.java b/services/backup/java/com/android/server/backup/encryption/storage/EncryptionDbException.java
deleted file mode 100644
index 82f7dead1b50..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/storage/EncryptionDbException.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.storage;
-
-import java.io.IOException;
-
-/** Thrown when there is a problem reading or writing the encryption database. */
-public class EncryptionDbException extends IOException {
- public EncryptionDbException(Throwable cause) {
- super(cause);
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKey.java b/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKey.java
deleted file mode 100644
index 39a2c6ebb9c3..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKey.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.storage;
-
-/** Wrapped bytes of a tertiary key. */
-public class TertiaryKey {
- private final String mSecondaryKeyAlias;
- private final String mPackageName;
- private final byte[] mWrappedKeyBytes;
-
- /**
- * Creates a new instance.
- *
- * @param secondaryKeyAlias Alias of the secondary used to wrap the key.
- * @param packageName The package name of the app to which the key belongs.
- * @param wrappedKeyBytes The wrapped key bytes.
- */
- public TertiaryKey(String secondaryKeyAlias, String packageName, byte[] wrappedKeyBytes) {
- mSecondaryKeyAlias = secondaryKeyAlias;
- mPackageName = packageName;
- mWrappedKeyBytes = wrappedKeyBytes;
- }
-
- /** Returns the alias of the secondary key used to wrap this tertiary key. */
- public String getSecondaryKeyAlias() {
- return mSecondaryKeyAlias;
- }
-
- /** Returns the package name of the application this key relates to. */
- public String getPackageName() {
- return mPackageName;
- }
-
- /** Returns the wrapped bytes of the key. */
- public byte[] getWrappedKeyBytes() {
- return mWrappedKeyBytes;
- }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKeysTable.java b/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKeysTable.java
deleted file mode 100644
index d8d40c402a84..000000000000
--- a/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKeysTable.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.storage;
-
-import static com.android.server.backup.encryption.storage.BackupEncryptionDbContract.TertiaryKeysEntry;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.util.ArrayMap;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Optional;
-
-/** Database table for storing and retrieving tertiary keys. */
-public class TertiaryKeysTable {
- private final BackupEncryptionDbHelper mHelper;
-
- TertiaryKeysTable(BackupEncryptionDbHelper helper) {
- mHelper = helper;
- }
-
- /**
- * Adds the {@code tertiaryKey} to the database.
- *
- * @return The primary key of the inserted row if successful, -1 otherwise.
- */
- public long addKey(TertiaryKey tertiaryKey) throws EncryptionDbException {
- SQLiteDatabase db = mHelper.getWritableDatabaseSafe();
- ContentValues values = new ContentValues();
- values.put(
- TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS,
- tertiaryKey.getSecondaryKeyAlias());
- values.put(TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME, tertiaryKey.getPackageName());
- values.put(
- TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES, tertiaryKey.getWrappedKeyBytes());
- return db.replace(TertiaryKeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
- }
-
- /** Gets the key wrapped by {@code secondaryKeyAlias} for app with {@code packageName}. */
- public Optional<TertiaryKey> getKey(String secondaryKeyAlias, String packageName)
- throws EncryptionDbException {
- SQLiteDatabase db = mHelper.getReadableDatabaseSafe();
- String[] projection = {
- TertiaryKeysEntry._ID,
- TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS,
- TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME,
- TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES
- };
- String selection =
- TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS
- + " = ? AND "
- + TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME
- + " = ?";
- String[] selectionArguments = {secondaryKeyAlias, packageName};
-
- try (Cursor cursor =
- db.query(
- TertiaryKeysEntry.TABLE_NAME,
- projection,
- selection,
- selectionArguments,
- /*groupBy=*/ null,
- /*having=*/ null,
- /*orderBy=*/ null)) {
- int count = cursor.getCount();
- if (count == 0) {
- return Optional.empty();
- }
-
- cursor.moveToFirst();
- byte[] wrappedKeyBytes =
- cursor.getBlob(
- cursor.getColumnIndexOrThrow(
- TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES));
- return Optional.of(new TertiaryKey(secondaryKeyAlias, packageName, wrappedKeyBytes));
- }
- }
-
- /** Returns all keys wrapped with {@code tertiaryKeyAlias} as an unmodifiable map. */
- public Map<String, TertiaryKey> getAllKeys(String secondaryKeyAlias)
- throws EncryptionDbException {
- SQLiteDatabase db = mHelper.getReadableDatabaseSafe();
- String[] projection = {
- TertiaryKeysEntry._ID,
- TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS,
- TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME,
- TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES
- };
- String selection = TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS + " = ?";
- String[] selectionArguments = {secondaryKeyAlias};
-
- Map<String, TertiaryKey> keysByPackageName = new ArrayMap<>();
- try (Cursor cursor =
- db.query(
- TertiaryKeysEntry.TABLE_NAME,
- projection,
- selection,
- selectionArguments,
- /*groupBy=*/ null,
- /*having=*/ null,
- /*orderBy=*/ null)) {
- while (cursor.moveToNext()) {
- String packageName =
- cursor.getString(
- cursor.getColumnIndexOrThrow(
- TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME));
- byte[] wrappedKeyBytes =
- cursor.getBlob(
- cursor.getColumnIndexOrThrow(
- TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES));
- keysByPackageName.put(
- packageName,
- new TertiaryKey(secondaryKeyAlias, packageName, wrappedKeyBytes));
- }
- }
- return Collections.unmodifiableMap(keysByPackageName);
- }
-}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 7ea1892727e3..846c6a23d394 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -36,7 +36,6 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.internal.util.Preconditions;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
@@ -47,6 +46,7 @@ import com.android.server.backup.utils.FullBackupUtils;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.Objects;
/**
* Core logic for performing one package's full backup, gathering the tarball from the application
@@ -201,7 +201,7 @@ public class FullBackupEngine {
mOpToken = opToken;
mTransportFlags = transportFlags;
mAgentTimeoutParameters =
- Preconditions.checkNotNull(
+ Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
index e14253702d55..aaf1f0a65dc8 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
@@ -32,13 +32,13 @@ import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.backup.IObbBackupService;
-import com.android.internal.util.Preconditions;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.utils.FullBackupUtils;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.Objects;
/**
* Full backup/restore to a file/socket.
@@ -52,7 +52,7 @@ public class FullBackupObbConnection implements ServiceConnection {
public FullBackupObbConnection(UserBackupManagerService backupManagerService) {
this.backupManagerService = backupManagerService;
mService = null;
- mAgentTimeoutParameters = Preconditions.checkNotNull(
+ mAgentTimeoutParameters = Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 86e679f16f66..738dd9bf0f0d 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -42,7 +42,6 @@ import android.util.Log;
import android.util.Slog;
import com.android.internal.backup.IBackupTransport;
-import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
@@ -62,6 +61,8 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@@ -128,10 +129,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
private static final String TAG = "PFTBT";
- private UserBackupManagerService backupManagerService;
+ private UserBackupManagerService mUserBackupManagerService;
private final Object mCancelLock = new Object();
- ArrayList<PackageInfo> mPackages;
+ List<PackageInfo> mPackages;
PackageInfo mCurrentPackage;
boolean mUpdateSchedule;
CountDownLatch mLatch;
@@ -159,7 +160,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
@Nullable IBackupManagerMonitor monitor, @Nullable OnTaskFinishedListener listener,
boolean userInitiated) {
super(observer);
- this.backupManagerService = backupManagerService;
+ this.mUserBackupManagerService = backupManagerService;
mTransportClient = transportClient;
mUpdateSchedule = updateSchedule;
mLatch = latch;
@@ -171,7 +172,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
mUserInitiated = userInitiated;
mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken();
- mAgentTimeoutParameters = Preconditions.checkNotNull(
+ mAgentTimeoutParameters = Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
mUserId = backupManagerService.getUserId();
@@ -249,19 +250,21 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
null);
}
}
+
+ mPackages = backupManagerService.filterUserFacingPackages(mPackages);
}
private void registerTask() {
- synchronized (backupManagerService.getCurrentOpLock()) {
+ synchronized (mUserBackupManagerService.getCurrentOpLock()) {
Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
- backupManagerService.getCurrentOperations().put(
+ mUserBackupManagerService.getCurrentOperations().put(
mCurrentOpToken,
new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
}
}
public void unregisterTask() {
- backupManagerService.removeOperation(mCurrentOpToken);
+ mUserBackupManagerService.removeOperation(mCurrentOpToken);
}
@Override
@@ -288,7 +291,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
mCancelAll = true;
if (mIsDoingBackup) {
- backupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
+ mUserBackupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
try {
// If we're running a backup we should be connected to a transport
IBackupTransport transport =
@@ -320,16 +323,17 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
int backupRunStatus = BackupManager.SUCCESS;
try {
- if (!backupManagerService.isEnabled() || !backupManagerService.isSetupComplete()) {
+ if (!mUserBackupManagerService.isEnabled()
+ || !mUserBackupManagerService.isSetupComplete()) {
// Backups are globally disabled, so don't proceed.
if (DEBUG) {
- Slog.i(TAG, "full backup requested but enabled=" + backupManagerService
+ Slog.i(TAG, "full backup requested but enabled=" + mUserBackupManagerService
.isEnabled()
- + " setupComplete=" + backupManagerService.isSetupComplete()
+ + " setupComplete=" + mUserBackupManagerService.isSetupComplete()
+ "; ignoring");
}
int monitoringEvent;
- if (backupManagerService.isSetupComplete()) {
+ if (mUserBackupManagerService.isSetupComplete()) {
monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED;
} else {
monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED;
@@ -516,7 +520,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
- Slog.e(TAG, "Error " + backupPackageStatus + " backing up "
+ Slog.w(TAG, "Error " + backupPackageStatus + " backing up "
+ packageName);
}
@@ -532,7 +536,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
// Roll this package to the end of the backup queue if we're
// in a queue-driven mode (regardless of success/failure)
if (mUpdateSchedule) {
- backupManagerService.enqueueFullBackup(packageName, System.currentTimeMillis());
+ mUserBackupManagerService.enqueueFullBackup(
+ packageName, System.currentTimeMillis());
}
if (backupPackageStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
@@ -549,7 +554,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
// from the preflight pass. If we got as far as preflight, we now need
// to tear down the target process.
if (mBackupRunner != null) {
- backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.tearDownAgentAndKill(
+ currentPackage.applicationInfo);
}
// ... and continue looping.
} else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
@@ -561,7 +567,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
packageName);
}
- backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
BackupObserverUtils
@@ -569,7 +575,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
BackupManager.ERROR_AGENT_FAILURE);
Slog.w(TAG, "Application failure for package: " + packageName);
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
- backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) {
BackupObserverUtils
@@ -578,7 +584,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
Slog.w(TAG, "Backup cancelled. package=" + packageName +
", cancelAll=" + mCancelAll);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
- backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
BackupObserverUtils
@@ -588,7 +594,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
// Abort entire backup pass.
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
- backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
return;
} else {
// Success!
@@ -596,14 +602,14 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
.sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.SUCCESS);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_SUCCESS, packageName);
- backupManagerService.logBackupComplete(packageName);
+ mUserBackupManagerService.logBackupComplete(packageName);
}
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
if (currentPackage.applicationInfo != null) {
Slog.i(TAG, "Unbinding agent in " + packageName);
try {
- backupManagerService.getActivityManager().unbindBackupAgent(
+ mUserBackupManagerService.getActivityManager().unbindBackupAgent(
currentPackage.applicationInfo);
} catch (RemoteException e) { /* can't happen; activity manager is local */ }
}
@@ -639,8 +645,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
mJob.finishBackupPass(mUserId);
}
- synchronized (backupManagerService.getQueueLock()) {
- backupManagerService.setRunningFullBackupTask(null);
+ synchronized (mUserBackupManagerService.getQueueLock()) {
+ mUserBackupManagerService.setRunningFullBackupTask(null);
}
mListener.onFinished("PFTBT.run()");
@@ -650,11 +656,11 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
// Now that we're actually done with schedule-driven work, reschedule
// the next pass based on the new queue state.
if (mUpdateSchedule) {
- backupManagerService.scheduleNextFullBackupJob(backoff);
+ mUserBackupManagerService.scheduleNextFullBackupJob(backoff);
}
Slog.i(TAG, "Full data backup pass finished.");
- backupManagerService.getWakelock().release();
+ mUserBackupManagerService.getWakelock().release();
}
}
@@ -709,13 +715,13 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
long fullBackupAgentTimeoutMillis =
mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
try {
- backupManagerService.prepareOperationTimeout(
+ mUserBackupManagerService.prepareOperationTimeout(
mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT);
if (MORE_DEBUG) {
Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
}
agent.doMeasureFullBackup(mQuota, mCurrentOpToken,
- backupManagerService.getBackupManagerBinder(), mTransportFlags);
+ mUserBackupManagerService.getBackupManagerBinder(), mTransportFlags);
// Now wait to get our result back. If this backstop timeout is reached without
// the latch being thrown, flow will continue as though a result or "normal"
@@ -765,7 +771,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
mResult.set(result);
mLatch.countDown();
- backupManagerService.removeOperation(mCurrentOpToken);
+ mUserBackupManagerService.removeOperation(mCurrentOpToken);
}
@Override
@@ -775,7 +781,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
mResult.set(BackupTransport.AGENT_ERROR);
mLatch.countDown();
- backupManagerService.removeOperation(mCurrentOpToken);
+ mUserBackupManagerService.removeOperation(mCurrentOpToken);
}
@Override
@@ -812,7 +818,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
mCurrentOpToken = currentOpToken;
- mEphemeralToken = backupManagerService.generateRandomIntegerToken();
+ mEphemeralToken = mUserBackupManagerService.generateRandomIntegerToken();
mPreflight = new SinglePackageBackupPreflight(
transportClient, quota, mEphemeralToken, transportFlags);
mPreflightLatch = new CountDownLatch(1);
@@ -825,23 +831,32 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
void registerTask() {
- synchronized (backupManagerService.getCurrentOpLock()) {
- backupManagerService.getCurrentOperations().put(
+ synchronized (mUserBackupManagerService.getCurrentOpLock()) {
+ mUserBackupManagerService.getCurrentOperations().put(
mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP_WAIT));
}
}
void unregisterTask() {
- synchronized (backupManagerService.getCurrentOpLock()) {
- backupManagerService.getCurrentOperations().remove(mCurrentOpToken);
+ synchronized (mUserBackupManagerService.getCurrentOpLock()) {
+ mUserBackupManagerService.getCurrentOperations().remove(mCurrentOpToken);
}
}
@Override
public void run() {
FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
- mEngine = new FullBackupEngine(backupManagerService, out, mPreflight, mTarget, false,
- this, mQuota, mCurrentOpToken, mTransportFlags);
+ mEngine =
+ new FullBackupEngine(
+ mUserBackupManagerService,
+ out,
+ mPreflight,
+ mTarget,
+ false,
+ this,
+ mQuota,
+ mCurrentOpToken,
+ mTransportFlags);
try {
try {
if (!mIsCancelled) {
@@ -857,7 +872,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
}
} catch (Exception e) {
- Slog.e(TAG, "Exception during full package backup of " + mTarget.packageName);
+ Slog.w(TAG, "Exception during full package backup of " + mTarget.packageName,
+ e);
} finally {
unregisterTask();
mBackupLatch.countDown();
@@ -927,13 +943,13 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
mIsCancelled = true;
// Cancel tasks spun off by this task.
- backupManagerService.handleCancel(mEphemeralToken, cancelAll);
- backupManagerService.tearDownAgentAndKill(mTarget.applicationInfo);
+ mUserBackupManagerService.handleCancel(mEphemeralToken, cancelAll);
+ mUserBackupManagerService.tearDownAgentAndKill(mTarget.applicationInfo);
// Free up everyone waiting on this task and its children.
mPreflightLatch.countDown();
mBackupLatch.countDown();
// We are done with this operation.
- backupManagerService.removeOperation(mCurrentOpToken);
+ mUserBackupManagerService.removeOperation(mCurrentOpToken);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 059b1b9379af..87a8e4982529 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -21,18 +21,16 @@ import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import android.app.backup.RestoreSet;
-import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
-import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
@@ -40,7 +38,6 @@ import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.fullbackup.PerformAdbBackupTask;
-import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.keyvalue.BackupRequest;
import com.android.server.backup.keyvalue.KeyValueBackupTask;
import com.android.server.backup.params.AdbBackupParams;
@@ -58,6 +55,7 @@ import com.android.server.backup.transport.TransportClient;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Asynchronous backup/restore handler thread.
@@ -72,10 +70,7 @@ public class BackupHandler extends Handler {
public static final int MSG_RESTORE_SESSION_TIMEOUT = 8;
public static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
public static final int MSG_RUN_ADB_RESTORE = 10;
- public static final int MSG_RETRY_INIT = 11;
public static final int MSG_RETRY_CLEAR = 12;
- public static final int MSG_WIDGET_BROADCAST = 13;
- public static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14;
public static final int MSG_REQUEST_BACKUP = 15;
public static final int MSG_SCHEDULE_BACKUP_PACKAGE = 16;
public static final int MSG_BACKUP_OPERATION_TIMEOUT = 17;
@@ -91,14 +86,16 @@ public class BackupHandler extends Handler {
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
private final HandlerThread mBackupThread;
- private volatile boolean mIsStopping = false;
+
+ @VisibleForTesting
+ volatile boolean mIsStopping = false;
public BackupHandler(
UserBackupManagerService backupManagerService, HandlerThread backupThread) {
super(backupThread.getLooper());
mBackupThread = backupThread;
this.backupManagerService = backupManagerService;
- mAgentTimeoutParameters = Preconditions.checkNotNull(
+ mAgentTimeoutParameters = Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
}
@@ -113,6 +110,24 @@ public class BackupHandler extends Handler {
sendMessage(obtainMessage(BackupHandler.MSG_STOP));
}
+ @Override
+ public void dispatchMessage(Message message) {
+ try {
+ dispatchMessageInternal(message);
+ } catch (Exception e) {
+ // If the backup service is stopping, we'll suppress all exceptions to avoid crashes
+ // caused by code still running after the current user has become unavailable.
+ if (!mIsStopping) {
+ throw e;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void dispatchMessageInternal(Message message) {
+ super.dispatchMessage(message);
+ }
+
public void handleMessage(Message msg) {
if (msg.what == MSG_STOP) {
Slog.v(TAG, "Stopping backup handler");
@@ -143,10 +158,6 @@ public class BackupHandler extends Handler {
.disposeOfTransportClient(transportClient, callerLogString);
}
Slog.v(TAG, "Backup requested but no transport available");
- synchronized (backupManagerService.getQueueLock()) {
- backupManagerService.setBackupRunning(false);
- }
- backupManagerService.getWakelock().release();
break;
}
@@ -154,6 +165,21 @@ public class BackupHandler extends Handler {
List<String> queue = new ArrayList<>();
DataChangedJournal oldJournal = backupManagerService.getJournal();
synchronized (backupManagerService.getQueueLock()) {
+ // Don't run backups if one is already running.
+ if (backupManagerService.isBackupRunning()) {
+ Slog.i(TAG, "Backup time but one already running");
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "Running a backup pass");
+ }
+
+ // Acquire the wakelock and pass it to the backup thread. It will be released
+ // once backup concludes.
+ backupManagerService.setBackupRunning(true);
+ backupManagerService.getWakelock().acquire();
+
// Do we have any work to do? Construct the work queue
// then release the synchronization lock to actually run
// the backup.
@@ -258,12 +284,6 @@ public class BackupHandler extends Handler {
break;
}
- case MSG_RUN_FULL_TRANSPORT_BACKUP: {
- PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj;
- (new Thread(task, "transport-backup")).start();
- break;
- }
-
case MSG_RUN_RESTORE: {
RestoreParams params = (RestoreParams) msg.obj;
Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
@@ -413,7 +433,7 @@ public class BackupHandler extends Handler {
try {
params.observer.onTimeout();
} catch (RemoteException e) {
- /* don't care if the app has gone away */
+ /* don't care if the app has gone away */
}
}
} else {
@@ -423,12 +443,6 @@ public class BackupHandler extends Handler {
break;
}
- case MSG_WIDGET_BROADCAST: {
- final Intent intent = (Intent) msg.obj;
- backupManagerService.getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
- break;
- }
-
case MSG_REQUEST_BACKUP: {
BackupParams params = (BackupParams) msg.obj;
if (MORE_DEBUG) {
diff --git a/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java b/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
deleted file mode 100644
index d37b106c2b26..000000000000
--- a/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.internal;
-
-import static com.android.server.backup.BackupManagerService.DEBUG;
-import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
-import static com.android.server.backup.BackupManagerService.TAG;
-import static com.android.server.backup.UserBackupManagerService.RUN_BACKUP_ACTION;
-import static com.android.server.backup.internal.BackupHandler.MSG_RUN_BACKUP;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Slog;
-
-import com.android.server.backup.UserBackupManagerService;
-
-/**
- * A {@link BroadcastReceiver} for the action {@link UserBackupManagerService#RUN_BACKUP_ACTION}
- * that runs an immediate backup operation if eligible.
- */
-public class RunBackupReceiver extends BroadcastReceiver {
- private final UserBackupManagerService mUserBackupManagerService;
-
- public RunBackupReceiver(UserBackupManagerService userBackupManagerService) {
- mUserBackupManagerService = userBackupManagerService;
- }
-
- /**
- * Run a backup pass if we're eligible. We're eligible if the following conditions are met:
- *
- * <ul>
- * <li>No transports are pending initialization (otherwise we kick off an initialization
- * operation instead).
- * <li>Backup is enabled for the user.
- * <li>The user has completed setup.
- * <li>No backup operation is currently running for the user.
- * </ul>
- */
- public void onReceive(Context context, Intent intent) {
- if (!RUN_BACKUP_ACTION.equals(intent.getAction())) {
- return;
- }
-
- synchronized (mUserBackupManagerService.getQueueLock()) {
- if (mUserBackupManagerService.getPendingInits().size() > 0) {
- // If there are pending init operations, we process those and then settle into the
- // usual periodic backup schedule.
- if (MORE_DEBUG) {
- Slog.v(TAG, "Init pending at scheduled backup");
- }
- try {
- PendingIntent runInitIntent = mUserBackupManagerService.getRunInitIntent();
- mUserBackupManagerService.getAlarmManager().cancel(runInitIntent);
- runInitIntent.send();
- } catch (PendingIntent.CanceledException ce) {
- Slog.w(TAG, "Run init intent cancelled");
- }
- } else {
- // Don't run backups if we're disabled or not yet set up.
- if (!mUserBackupManagerService.isEnabled()
- || !mUserBackupManagerService.isSetupComplete()) {
- Slog.w(
- TAG,
- "Backup pass but enabled="
- + mUserBackupManagerService.isEnabled()
- + " setupComplete="
- + mUserBackupManagerService.isSetupComplete());
- return;
- }
-
- // Don't run backups if one is already running.
- if (mUserBackupManagerService.isBackupRunning()) {
- Slog.i(TAG, "Backup time but one already running");
- return;
- }
-
- if (DEBUG) {
- Slog.v(TAG, "Running a backup pass");
- }
-
- // Acquire the wakelock and pass it to the backup thread. It will be released once
- // backup concludes.
- mUserBackupManagerService.setBackupRunning(true);
- mUserBackupManagerService.getWakelock().acquire();
-
- Handler backupHandler = mUserBackupManagerService.getBackupHandler();
- Message message = backupHandler.obtainMessage(MSG_RUN_BACKUP);
- backupHandler.sendMessage(message);
- }
- }
- }
-}
diff --git a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
index 96d61e5755c8..160124b6f1d3 100644
--- a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
+++ b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
@@ -53,21 +53,8 @@ public class RunInitializeReceiver extends BroadcastReceiver {
if (pendingInits.size() > 0) {
String[] transports = pendingInits.toArray(new String[pendingInits.size()]);
-
mUserBackupManagerService.clearPendingInits();
-
- UserBackupManagerService.BackupWakeLock wakelock =
- mUserBackupManagerService.getWakelock();
- wakelock.acquire();
- OnTaskFinishedListener listener = caller -> wakelock.release();
-
- Runnable task =
- new PerformInitializeTask(
- mUserBackupManagerService,
- transports,
- /* observer */ null,
- listener);
- mUserBackupManagerService.getBackupHandler().post(task);
+ mUserBackupManagerService.initializeTransports(transports, null);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java
index 535c7cb29980..4632cb0e51e2 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java
@@ -395,7 +395,8 @@ public class KeyValueBackupReporter {
Slog.e(TAG, "Transport threw reporting restore set: " + e);
}
- void onTransportNotInitialized() {
+ void onTransportNotInitialized(@Nullable String transportName) {
+ EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName);
if (MORE_DEBUG) {
Slog.d(TAG, "Transport requires initialization, rerunning");
}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 294eb0128b2c..5e10916c4491 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -47,6 +47,7 @@ import android.os.RemoteException;
import android.os.SELinux;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -57,7 +58,6 @@ import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.KeyValueBackupJob;
-import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.internal.OnTaskFinishedListener;
@@ -66,14 +66,18 @@ import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.remote.RemoteCallable;
import com.android.server.backup.remote.RemoteResult;
import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.utils.AppBackupUtils;
+import libcore.io.IoUtils;
+
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -81,8 +85,10 @@ import java.lang.annotation.RetentionPolicy;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
@@ -170,10 +176,14 @@ import java.util.concurrent.atomic.AtomicInteger;
// TODO: Consider having the caller responsible for some clean-up (like resetting state)
// TODO: Distinguish between cancel and time-out where possible for logging/monitoring/observing
public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
+ private static final String TAG = "KVBT";
+
private static final int THREAD_PRIORITY = Process.THREAD_PRIORITY_BACKGROUND;
private static final AtomicInteger THREAD_COUNT = new AtomicInteger();
private static final String BLANK_STATE_FILE_NAME = "blank_state";
private static final String PM_PACKAGE = UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
+ private static final String SUCCESS_STATE_SUBDIR = "backing-up";
+ @VisibleForTesting static final String NO_DATA_END_SENTINEL = "@end@";
@VisibleForTesting public static final String STAGING_FILE_SUFFIX = ".data";
@VisibleForTesting public static final String NEW_STATE_FILE_SUFFIX = ".new";
@@ -233,13 +243,11 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
private final UserBackupManagerService mBackupManagerService;
private final PackageManager mPackageManager;
- private final TransportManager mTransportManager;
private final TransportClient mTransportClient;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
private final KeyValueBackupReporter mReporter;
private final OnTaskFinishedListener mTaskFinishedListener;
private final boolean mUserInitiated;
- private final boolean mNonIncremental;
private final int mCurrentOpToken;
private final int mUserId;
private final File mStateDirectory;
@@ -264,6 +272,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
// and at least one of the packages had data. Used to avoid updating current token for
// empty backups.
private boolean mHasDataToBackup;
+ private boolean mNonIncremental;
/**
* This {@link ConditionVariable} is used to signal that the cancel operation has been
@@ -300,7 +309,6 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
boolean userInitiated,
boolean nonIncremental) {
mBackupManagerService = backupManagerService;
- mTransportManager = backupManagerService.getTransportManager();
mPackageManager = backupManagerService.getPackageManager();
mTransportClient = transportClient;
mOriginalQueue = queue;
@@ -313,7 +321,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
mUserInitiated = userInitiated;
mNonIncremental = nonIncremental;
mAgentTimeoutParameters =
- Preconditions.checkNotNull(
+ Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
mStateDirectory = new File(backupManagerService.getBaseStateDir(), transportDirName);
@@ -339,6 +347,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
mHasDataToBackup = false;
+ Set<String> backedUpApps = new HashSet<>();
int status = BackupTransport.TRANSPORT_OK;
try {
startTask();
@@ -350,13 +359,18 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
} else {
backupPackage(packageName);
}
+ setSuccessState(packageName, true);
+ backedUpApps.add(packageName);
} catch (AgentException e) {
+ setSuccessState(packageName, false);
if (e.isTransitory()) {
// We try again this package in the next backup pass.
mBackupManagerService.dataChangedImpl(packageName);
}
}
}
+
+ informTransportOfUnchangedApps(backedUpApps);
} catch (TaskException e) {
if (e.isStateCompromised()) {
mBackupManagerService.resetBackupState(mStateDirectory);
@@ -367,6 +381,185 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
finishTask(status);
}
+ /**
+ * Tell the transport about all of the packages which have successfully backed up but
+ * have not informed the framework that they have new data. This allows transports to
+ * differentiate between packages which are not backing data up due to an error and
+ * packages which are not backing up data because nothing has changed.
+ *
+ * The current implementation involves creating a state file when a backup succeeds,
+ * on subsequent runs the existence of the file indicates the backup ran successfully
+ * but there was no data. If a backup fails with an error, or if the package is not
+ * eligible for backup by the transport any more, the status file is removed and the
+ * "no data" message will not be sent to the transport until another successful data
+ * changed backup has succeeded.
+ *
+ * @param appsBackedUp The Set of apps backed up during this run so we can exclude them
+ * from the list of successfully backed up apps that we signal to
+ * the transport have no data.
+ */
+ private void informTransportOfUnchangedApps(Set<String> appsBackedUp) {
+ String[] succeedingPackages = getSucceedingPackages();
+ if (succeedingPackages == null) {
+ // Nothing is succeeding, so end early.
+ return;
+ }
+
+ int flags = BackupTransport.FLAG_DATA_NOT_CHANGED;
+ if (mUserInitiated) {
+ flags |= BackupTransport.FLAG_USER_INITIATED;
+ }
+
+ boolean noDataPackageEncountered = false;
+ try {
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("KVBT.informTransportOfEmptyBackups()");
+
+ for (String packageName : succeedingPackages) {
+ if (appsBackedUp.contains(packageName)) {
+ Log.v(TAG, "Skipping package which was backed up this time :" + packageName);
+ // Skip packages we backed up in this run.
+ continue;
+ }
+
+ PackageInfo packageInfo;
+ try {
+ packageInfo = mPackageManager.getPackageInfo(packageName, /* flags */ 0);
+ if (!isEligibleForNoDataCall(packageInfo)) {
+ // If the package isn't eligible any more we can forget about it and move
+ // on.
+ clearStatus(packageName);
+ continue;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // If the package has been uninstalled we can forget about it and move on.
+ clearStatus(packageName);
+ continue;
+ }
+
+ sendNoDataChangedTo(transport, packageInfo, flags);
+ noDataPackageEncountered = true;
+ }
+
+ if (noDataPackageEncountered) {
+ // If we've notified the transport of an unchanged package we need to
+ // tell it that it's seen all of the unchanged packages. We do this by
+ // reporting the end sentinel package as unchanged.
+ PackageInfo endSentinal = new PackageInfo();
+ endSentinal.packageName = NO_DATA_END_SENTINEL;
+ sendNoDataChangedTo(transport, endSentinal, flags);
+ }
+ } catch (TransportNotAvailableException | RemoteException e) {
+ Log.e(TAG, "Could not inform transport of all unchanged apps", e);
+ }
+ }
+
+ /** Determine if a package is eligible to be backed up to the transport */
+ private boolean isEligibleForNoDataCall(PackageInfo packageInfo) {
+ return AppBackupUtils.appIsKeyValueOnly(packageInfo)
+ && AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport(mTransportClient,
+ packageInfo.packageName, mPackageManager, mUserId);
+ }
+
+ /** Send the "no data changed" message to a transport for a specific package */
+ private void sendNoDataChangedTo(IBackupTransport transport, PackageInfo packageInfo, int flags)
+ throws RemoteException {
+ ParcelFileDescriptor pfd;
+ try {
+ pfd = ParcelFileDescriptor.open(mBlankStateFile, MODE_READ_ONLY | MODE_CREATE);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Unable to find blank state file, aborting unchanged apps signal.");
+ return;
+ }
+ try {
+ int result = transport.performBackup(packageInfo, pfd, flags);
+ if (result == BackupTransport.TRANSPORT_ERROR
+ || result == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
+ Log.w(
+ TAG,
+ "Aborting informing transport of unchanged apps, transport" + " errored");
+ return;
+ }
+
+ transport.finishBackup();
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
+ }
+
+ /** Get the list of package names which are marked as having previously succeeded */
+ private String[] getSucceedingPackages() {
+ File stateDirectory = getTopLevelSuccessStateDirectory(/* createIfMissing */ false);
+ if (stateDirectory == null) {
+ // getSuccessStateFileFor logs when we can't use the state area
+ return null;
+ }
+
+ return stateDirectory.list();
+ }
+
+ /** Sets the indicator that a package backup is succeeding */
+ private void setSuccessState(String packageName, boolean success) {
+ File successStateFile = getSuccessStateFileFor(packageName);
+ if (successStateFile == null) {
+ // The error will have been logged by getSuccessStateFileFor().
+ return;
+ }
+
+ if (successStateFile.exists() != success) {
+ // If there's been a change of state
+ if (!success) {
+ // Clear the status if we're now failing
+ clearStatus(packageName, successStateFile);
+ return;
+ }
+
+ // For succeeding packages we want the file
+ try {
+ if (!successStateFile.createNewFile()) {
+ Log.w(TAG, "Unable to permanently record success for " + packageName);
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to permanently record success for " + packageName, e);
+ }
+ }
+ }
+
+ /** Clear the status file for a specific package */
+ private void clearStatus(String packageName) {
+ File successStateFile = getSuccessStateFileFor(packageName);
+ if (successStateFile == null) {
+ // The error will have been logged by getSuccessStateFileFor().
+ return;
+ }
+ clearStatus(packageName, successStateFile);
+ }
+
+ /** Clear the status file for a package once we have the File representation */
+ private void clearStatus(String packageName, File successStateFile) {
+ if (successStateFile.exists()) {
+ if (!successStateFile.delete()) {
+ Log.w(TAG, "Unable to remove status file for " + packageName);
+ }
+ }
+ }
+
+ /** Get the backup state file for a package **/
+ private File getSuccessStateFileFor(String packageName) {
+ File stateDirectory = getTopLevelSuccessStateDirectory(/* createIfMissing */ true);
+ return stateDirectory == null ? null : new File(stateDirectory, packageName);
+ }
+
+ /** The top level directory for success state files */
+ private File getTopLevelSuccessStateDirectory(boolean createIfMissing) {
+ File directory = new File(mStateDirectory, SUCCESS_STATE_SUBDIR);
+ if (!directory.exists() && createIfMissing && !directory.mkdirs()) {
+ Log.e(TAG, "Unable to create backing-up state directory");
+ return null;
+ }
+ return directory;
+ }
+
/** Returns transport status. */
private int sendDataToTransport(@Nullable PackageInfo packageInfo)
throws AgentException, TaskException {
@@ -412,6 +605,11 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
try {
IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.startTask()");
String transportName = transport.name();
+ if (transportName.contains("EncryptedLocalTransport")) {
+ // Temporary code for EiTF POC. Only supports non-incremental backups.
+ mNonIncremental = true;
+ }
+
mReporter.onTransportReady(transportName);
// If we haven't stored PM metadata yet, we must initialize the transport.
@@ -535,6 +733,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
}
String callerLogString = "KVBT.finishTask()";
+ String transportName = null;
// If the backup data was not empty, we succeeded and this is the first time
// we've done a backup, we can record the current backup dataset token.
@@ -542,6 +741,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
if (mHasDataToBackup && (status == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) {
try {
IBackupTransport transport = mTransportClient.connectOrThrow(callerLogString);
+ transportName = transport.name();
mBackupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
mBackupManagerService.writeRestoreTokens();
} catch (Exception e) {
@@ -553,7 +753,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
synchronized (mQueueLock) {
mBackupManagerService.setBackupRunning(false);
if (status == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
- mReporter.onTransportNotInitialized();
+ mReporter.onTransportNotInitialized(transportName);
try {
triggerTransportInitializationLocked();
} catch (Exception e) {
@@ -861,6 +1061,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
status = transport.performBackup(packageInfo, backupData, flags);
if (status == BackupTransport.TRANSPORT_OK) {
status = transport.finishBackup();
+ } else if (status == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
+ mReporter.onTransportNotInitialized(transport.name());
}
} catch (Exception e) {
mReporter.onPackageBackupTransportError(packageName, e);
diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java
index c9a6b6038a93..a6fea6cc75a0 100644
--- a/services/backup/java/com/android/server/backup/params/RestoreParams.java
+++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java
@@ -24,6 +24,9 @@ import android.content.pm.PackageInfo;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.transport.TransportClient;
+import java.util.Map;
+import java.util.Set;
+
public class RestoreParams {
public final TransportClient transportClient;
public final IRestoreObserver observer;
diff --git a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
index e4890e009abb..376b618935c5 100644
--- a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
+++ b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
@@ -21,11 +21,11 @@ import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import android.util.Slog;
-import com.android.internal.util.Preconditions;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.UserBackupManagerService;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -45,7 +45,7 @@ public class AdbRestoreFinishedLatch implements BackupRestoreTask {
this.backupManagerService = backupManagerService;
mLatch = new CountDownLatch(1);
mCurrentOpToken = currentOpToken;
- mAgentTimeoutParameters = Preconditions.checkNotNull(
+ mAgentTimeoutParameters = Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 56eacc017079..82bed3b57f16 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -42,7 +42,6 @@ import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
@@ -62,6 +61,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
/**
* Full restore engine, used by both adb restore and transport-based full restore.
@@ -88,7 +88,6 @@ public class FullRestoreEngine extends RestoreEngine {
final PackageInfo mOnlyPackage;
final boolean mAllowApks;
- private final boolean mAllowObbs;
// Which package are we currently handling data for?
private String mAgentPackage;
@@ -113,9 +112,6 @@ public class FullRestoreEngine extends RestoreEngine {
// Packages we've already wiped data on when restoring their first file
private final HashSet<String> mClearedPackages = new HashSet<>();
- // How much data have we moved?
- private long mBytes;
-
// Working buffer
final byte[] mBuffer;
@@ -130,14 +126,14 @@ public class FullRestoreEngine extends RestoreEngine {
final int mEphemeralOpToken;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
- final boolean mIsAdbRestore;
+ private final boolean mIsAdbRestore;
@GuardedBy("mPipesLock")
private boolean mPipesClosed;
public FullRestoreEngine(UserBackupManagerService backupManagerService,
BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
- boolean allowObbs, int ephemeralOpToken, boolean isAdbRestore) {
+ int ephemeralOpToken, boolean isAdbRestore) {
mBackupManagerService = backupManagerService;
mEphemeralOpToken = ephemeralOpToken;
mMonitorTask = monitorTask;
@@ -145,10 +141,8 @@ public class FullRestoreEngine extends RestoreEngine {
mMonitor = monitor;
mOnlyPackage = onlyPackage;
mAllowApks = allowApks;
- mAllowObbs = allowObbs;
mBuffer = new byte[32 * 1024];
- mBytes = 0;
- mAgentTimeoutParameters = Preconditions.checkNotNull(
+ mAgentTimeoutParameters = Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
mIsAdbRestore = isAdbRestore;
@@ -170,12 +164,7 @@ public class FullRestoreEngine extends RestoreEngine {
return false;
}
- BytesReadListener bytesReadListener = new BytesReadListener() {
- @Override
- public void onBytesRead(long bytesRead) {
- mBytes += bytesRead;
- }
- };
+ BytesReadListener bytesReadListener = bytesRead -> { };
TarBackupReader tarBackupReader = new TarBackupReader(instream,
bytesReadListener, monitor);
@@ -378,9 +367,7 @@ public class FullRestoreEngine extends RestoreEngine {
? ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
: ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
mAgentPackage = pkg;
- } catch (IOException e) {
- // fall through to error handling
- } catch (NameNotFoundException e) {
+ } catch (IOException | NameNotFoundException e) {
// fall through to error handling
}
@@ -485,9 +472,6 @@ public class FullRestoreEngine extends RestoreEngine {
int toRead = (toCopy > buffer.length)
? buffer.length : (int) toCopy;
int nRead = instream.read(buffer, 0, toRead);
- if (nRead >= 0) {
- mBytes += nRead;
- }
if (nRead <= 0) {
break;
}
@@ -548,9 +532,6 @@ public class FullRestoreEngine extends RestoreEngine {
int toRead = (bytesToConsume > buffer.length)
? buffer.length : (int) bytesToConsume;
long nRead = instream.read(buffer, 0, toRead);
- if (nRead >= 0) {
- mBytes += nRead;
- }
if (nRead <= 0) {
break;
}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index c9042566cf67..01b40fbff201 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -23,21 +23,12 @@ import static com.android.server.backup.BackupPasswordManager.PBKDF_CURRENT;
import static com.android.server.backup.BackupPasswordManager.PBKDF_FALLBACK;
import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_HEADER_MAGIC;
import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_VERSION;
-import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
-import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
-import android.app.IBackupAgent;
-import android.app.backup.BackupAgent;
import android.app.backup.IFullBackupRestoreObserver;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.Signature;
-import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.fullbackup.FullBackupObbConnection;
import com.android.server.backup.utils.FullBackupRestoreObserverUtils;
@@ -50,8 +41,6 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.InflaterInputStream;
@@ -71,34 +60,9 @@ public class PerformAdbRestoreTask implements Runnable {
private final String mCurrentPassword;
private final String mDecryptPassword;
private final AtomicBoolean mLatchObject;
- private final BackupAgent mPackageManagerBackupAgent;
- private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
+ private final FullBackupObbConnection mObbConnection;
private IFullBackupRestoreObserver mObserver;
- private IBackupAgent mAgent;
- private String mAgentPackage;
- private ApplicationInfo mTargetApp;
- private FullBackupObbConnection mObbConnection = null;
- private ParcelFileDescriptor[] mPipes = null;
- private byte[] mWidgetData = null;
- private long mAppVersion;
-
- private long mBytes;
- private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
-
- // possible handling states for a given package in the restore dataset
- private final HashMap<String, RestorePolicy> mPackagePolicies
- = new HashMap<>();
-
- // installer package names for each encountered app, derived from the manifests
- private final HashMap<String, String> mPackageInstallers = new HashMap<>();
-
- // Signatures for a given package found in its manifest file
- private final HashMap<String, Signature[]> mManifestSignatures
- = new HashMap<>();
-
- // Packages we've already wiped data on when restoring their first file
- private final HashSet<String> mClearedPackages = new HashSet<>();
public PerformAdbRestoreTask(UserBackupManagerService backupManagerService,
ParcelFileDescriptor fd, String curPassword, String decryptPassword,
@@ -109,19 +73,7 @@ public class PerformAdbRestoreTask implements Runnable {
mDecryptPassword = decryptPassword;
mObserver = observer;
mLatchObject = latch;
- mAgent = null;
- mPackageManagerBackupAgent = backupManagerService.makeMetadataAgent();
- mAgentPackage = null;
- mTargetApp = null;
mObbConnection = new FullBackupObbConnection(backupManagerService);
- mAgentTimeoutParameters = Preconditions.checkNotNull(
- backupManagerService.getAgentTimeoutParameters(),
- "Timeout parameters cannot be null");
-
- // Which packages we've already wiped data on. We prepopulate this
- // with a whitelist of packages known to be unclearable.
- mClearedPackages.add("android");
- mClearedPackages.add(SETTINGS_PACKAGE);
}
@Override
@@ -130,11 +82,6 @@ public class PerformAdbRestoreTask implements Runnable {
mObbConnection.establish();
mObserver = FullBackupRestoreObserverUtils.sendStartRestore(mObserver);
- // Are we able to restore shared-storage data?
- if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE, RestorePolicy.ACCEPT);
- }
-
FileInputStream rawInStream = null;
try {
if (!mBackupManagerService.backupPasswordMatches(mCurrentPassword)) {
@@ -144,8 +91,6 @@ public class PerformAdbRestoreTask implements Runnable {
return;
}
- mBytes = 0;
-
rawInStream = new FileInputStream(mInputFile.getFileDescriptor());
InputStream tarInputStream = parseBackupFileHeaderAndReturnTarStream(rawInStream,
@@ -157,7 +102,7 @@ public class PerformAdbRestoreTask implements Runnable {
}
FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService, null,
- mObserver, null, null, true, true/*unused*/, 0 /*unused*/, true);
+ mObserver, null, null, true, 0 /*unused*/, true);
FullRestoreEngineThread mEngineThread = new FullRestoreEngineThread(mEngine,
tarInputStream);
mEngineThread.run();
@@ -165,7 +110,7 @@ public class PerformAdbRestoreTask implements Runnable {
if (MORE_DEBUG) {
Slog.v(TAG, "Done consuming input tarfile.");
}
- } catch (IOException e) {
+ } catch (Exception e) {
Slog.e(TAG, "Unable to read restore input");
} finally {
try {
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 6714b0aea261..12113fea12a4 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -26,6 +26,7 @@ import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAG
import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTORE_STEP;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.Nullable;
import android.app.ApplicationThreadConstants;
@@ -51,8 +52,8 @@ import android.os.UserHandle;
import android.util.EventLog;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
-import com.android.internal.util.Preconditions;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -76,6 +77,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
public class PerformUnifiedRestoreTask implements BackupRestoreTask {
@@ -86,7 +89,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
private final TransportClient mTransportClient;
// Where per-transport saved state goes
- File mStateDir;
+ private File mStateDir;
// Restore observer; may be null
private IRestoreObserver mObserver;
@@ -153,14 +156,24 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Key/value: bookkeeping about staged data and files for agent access
private File mBackupDataName;
private File mStageName;
- private File mSavedStateName;
private File mNewStateName;
- ParcelFileDescriptor mBackupData;
- ParcelFileDescriptor mNewState;
+ private ParcelFileDescriptor mBackupData;
+ private ParcelFileDescriptor mNewState;
private final int mEphemeralOpToken;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
+ @VisibleForTesting
+ PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
+ mListener = null;
+ mAgentTimeoutParameters = null;
+ mTransportClient = null;
+ mTransportManager = null;
+ mEphemeralOpToken = 0;
+ mUserId = 0;
+ this.backupManagerService = backupManagerService;
+ }
+
// This task can assume that the wakelock is properly held for it and doesn't have to worry
// about releasing it.
public PerformUnifiedRestoreTask(
@@ -191,7 +204,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
mFinished = false;
mDidLaunch = false;
mListener = listener;
- mAgentTimeoutParameters = Preconditions.checkNotNull(
+ mAgentTimeoutParameters = Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
@@ -223,7 +236,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
try {
PackageManager pm = backupManagerService.getPackageManager();
PackageInfo info = pm.getPackageInfoAsUser(filterSet[i], 0, mUserId);
- if ("android".equals(info.packageName)) {
+ if (PLATFORM_PACKAGE_NAME.equals(info.packageName)) {
hasSystem = true;
continue;
}
@@ -242,7 +255,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
if (hasSystem) {
try {
mAcceptSet.add(0, backupManagerService.getPackageManager().getPackageInfoAsUser(
- "android", 0, mUserId));
+ PLATFORM_PACKAGE_NAME, 0, mUserId));
} catch (NameNotFoundException e) {
// won't happen; we know a priori that it's valid
}
@@ -257,6 +270,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
}
}
+ mAcceptSet = backupManagerService.filterUserFacingPackages(mAcceptSet);
+
if (MORE_DEBUG) {
Slog.v(TAG, "Restore; accept set size is " + mAcceptSet.size());
for (PackageInfo info : mAcceptSet) {
@@ -666,7 +681,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
}
// Guts of a key/value restore operation
- void initiateOneRestore(PackageInfo app, long appVersionCode) {
+ private void initiateOneRestore(PackageInfo app, long appVersionCode) {
final String packageName = app.packageName;
if (DEBUG) {
@@ -677,13 +692,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
mBackupDataName = new File(backupManagerService.getDataDir(), packageName + ".restore");
mStageName = new File(backupManagerService.getDataDir(), packageName + ".stage");
mNewStateName = new File(mStateDir, packageName + ".new");
- mSavedStateName = new File(mStateDir, packageName);
- // don't stage the 'android' package where the wallpaper data lives. this is
- // an optimization: we know there's no widget data hosted/published by that
- // package, and this way we avoid doing a spurious copy of MB-sized wallpaper
- // data following the download.
- boolean staging = !packageName.equals("android");
+ boolean staging = shouldStageBackupData(packageName);
ParcelFileDescriptor stage;
File downloadFile = (staging) ? mStageName : mBackupDataName;
boolean startedAgentRestore = false;
@@ -725,27 +735,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
BackupDataInput in = new BackupDataInput(stage.getFileDescriptor());
BackupDataOutput out = new BackupDataOutput(mBackupData.getFileDescriptor());
- byte[] buffer = new byte[8192]; // will grow when needed
- while (in.readNextHeader()) {
- final String key = in.getKey();
- final int size = in.getDataSize();
-
- // is this a special key?
- if (key.equals(KEY_WIDGET_STATE)) {
- if (DEBUG) {
- Slog.i(TAG, "Restoring widget state for " + packageName);
- }
- mWidgetData = new byte[size];
- in.readEntityData(mWidgetData, 0, size);
- } else {
- if (size > buffer.length) {
- buffer = new byte[size];
- }
- in.readEntityData(buffer, 0, size);
- out.writeEntityHeader(key, size);
- out.writeEntityData(buffer, size);
- }
- }
+ filterExcludedKeys(packageName, in, out);
mBackupData.close();
}
@@ -768,8 +758,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
backupManagerService.prepareOperationTimeout(
mEphemeralOpToken, restoreAgentTimeoutMillis, this, OP_TYPE_RESTORE_WAIT);
startedAgentRestore = true;
- mAgent.doRestore(mBackupData, appVersionCode, mNewState,
- mEphemeralOpToken, backupManagerService.getBackupManagerBinder());
+ mAgent.doRestoreWithExcludedKeys(mBackupData, appVersionCode, mNewState,
+ mEphemeralOpToken, backupManagerService.getBackupManagerBinder(),
+ new ArrayList<>(getExcludedKeysForPackage(packageName)));
} catch (Exception e) {
Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
@@ -784,6 +775,56 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
}
}
+ @VisibleForTesting
+ boolean shouldStageBackupData(String packageName) {
+ // Backup data is staged for 2 reasons:
+ // 1. We might need to exclude keys from the data before passing it to the agent
+ // 2. Widget metadata needs to be separated from the rest to be handled separately
+ // But 'android' package doesn't contain widget metadata so we want to skip staging for it
+ // when there are no keys to be excluded either.
+ return !packageName.equals(PLATFORM_PACKAGE_NAME) ||
+ !getExcludedKeysForPackage(PLATFORM_PACKAGE_NAME).isEmpty();
+ }
+
+ @VisibleForTesting
+ Set<String> getExcludedKeysForPackage(String packageName) {
+ return backupManagerService.getExcludedRestoreKeys(packageName);
+ }
+
+ @VisibleForTesting
+ void filterExcludedKeys(String packageName, BackupDataInput in, BackupDataOutput out)
+ throws Exception {
+ Set<String> excludedKeysForPackage = getExcludedKeysForPackage(packageName);
+
+ byte[] buffer = new byte[8192]; // will grow when needed
+ while (in.readNextHeader()) {
+ final String key = in.getKey();
+ final int size = in.getDataSize();
+
+ if (excludedKeysForPackage != null && excludedKeysForPackage.contains(key)) {
+ Slog.i(TAG, "Skipping blocked key " + key);
+ in.skipEntityData();
+ continue;
+ }
+
+ // is this a special key?
+ if (key.equals(KEY_WIDGET_STATE)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Restoring widget state for " + packageName);
+ }
+ mWidgetData = new byte[size];
+ in.readEntityData(mWidgetData, 0, size);
+ } else {
+ if (size > buffer.length) {
+ buffer = new byte[size];
+ }
+ in.readEntityData(buffer, 0, size);
+ out.writeEntityHeader(key, size);
+ out.writeEntityData(buffer, size);
+ }
+ }
+ }
+
// state RESTORE_FULL : restore one package via streaming engine
private void restoreFull() {
// None of this can run on the work looper here, so we spin asynchronous
@@ -870,7 +911,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
mCurrentPackage.packageName);
mEngine = new FullRestoreEngine(backupManagerService, this, null,
- mMonitor, mCurrentPackage, false, false, mEphemeralOpToken, false);
+ mMonitor, mCurrentPackage, false, mEphemeralOpToken, false);
mEngineThread = new FullRestoreEngineThread(mEngine, mEnginePipes[0]);
ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
@@ -1160,7 +1201,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// the following from a discard of the newly-written state to the
// "correct" operation of renaming into the canonical state blob.
mNewStateName.delete(); // TODO: remove; see above comment
- //mNewStateName.renameTo(mSavedStateName); // TODO: replace with this
// If this wasn't the PM pseudopackage, tear down the agent side
if (mCurrentPackage.applicationInfo != null) {
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index f3b80988fef3..d2d382dfc14d 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -775,17 +775,17 @@ public class TarBackupReader {
private static void hexLog(byte[] block) {
int offset = 0;
- int todo = block.length;
+ int remaining = block.length;
StringBuilder buf = new StringBuilder(64);
- while (todo > 0) {
+ while (remaining > 0) {
buf.append(String.format("%04x ", offset));
- int numThisLine = (todo > 16) ? 16 : todo;
+ int numThisLine = (remaining > 16) ? 16 : remaining;
for (int i = 0; i < numThisLine; i++) {
buf.append(String.format("%02x ", block[offset + i]));
}
Slog.i("hexdump", buf.toString());
buf.setLength(0);
- todo -= numThisLine;
+ remaining -= numThisLine;
offset += numThisLine;
}
}