diff options
author | Xin Li <delphij@google.com> | 2020-08-31 21:21:38 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2020-08-31 21:21:38 -0700 |
commit | 628590d7ec80e10a3fc24b1c18a1afb55cca10a8 (patch) | |
tree | 4b1c3f52d86d7fb53afbe9e9438468588fa489f8 /services/backup | |
parent | b11b8ec3aec8bb42f2c07e1c5ac7942da293baa8 (diff) | |
parent | d2d3a20624d968199353ccf6ddbae6f3ac39c9af (diff) |
Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)
Bug: 166295507
Merged-In: I3d92a6de21a938f6b352ec26dc23420c0fe02b27
Change-Id: Ifdb80563ef042738778ebb8a7581a97c4e3d96e2
Diffstat (limited to 'services/backup')
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; } } |