From cf962601186a05a8818c02b690c1e7b5c15144f1 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Thu, 15 Jan 2015 17:26:22 -0800 Subject: Don't write widget metadata to backup unless it's new/changed Redundant backup traffic is bad. Don't commit the widget metadata payload (or the deletion operation for it) unless the widget state of the app has actually changed since the last backup. Bug 19003911 Change-Id: I93819173c0e2357b030d9e2b3d2ee57f2410bb57 --- .../server/backup/BackupManagerService.java | 91 +++++++++++++++++++--- 1 file changed, 80 insertions(+), 11 deletions(-) diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 7d085a33f00b..289152b52b31 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -130,6 +130,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.TreeMap; @@ -2695,6 +2696,84 @@ public class BackupManagerService { } } + // SHA-1 a byte array and return the result in hex + private String SHA1Checksum(byte[] input) { + final byte[] checksum; + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + checksum = md.digest(input); + } catch (NoSuchAlgorithmException e) { + Slog.e(TAG, "Unable to use SHA-1!"); + return "00"; + } + + StringBuffer sb = new StringBuffer(checksum.length * 2); + for (int i = 0; i < checksum.length; i++) { + sb.append(Integer.toHexString(checksum[i])); + } + return sb.toString(); + } + + private void writeWidgetPayloadIfAppropriate(FileDescriptor fd, String pkgName) + throws IOException { + byte[] widgetState = AppWidgetBackupBridge.getWidgetState(pkgName, + UserHandle.USER_OWNER); + // has the widget state changed since last time? + final File widgetFile = new File(mStateDir, pkgName + "_widget"); + final boolean priorStateExists = widgetFile.exists(); + + if (MORE_DEBUG) { + if (priorStateExists || widgetState != null) { + Slog.i(TAG, "Checking widget update: state=" + (widgetState != null) + + " prior=" + priorStateExists); + } + } + + if (!priorStateExists && widgetState == null) { + // no prior state, no new state => nothing to do + return; + } + + // if the new state is not null, we might need to compare checksums to + // determine whether to update the widget blob in the archive. If the + // widget state *is* null, we know a priori at this point that we simply + // need to commit a deletion for it. + String newChecksum = null; + if (widgetState != null) { + newChecksum = SHA1Checksum(widgetState); + if (priorStateExists) { + final String priorChecksum; + try ( + FileInputStream fin = new FileInputStream(widgetFile); + DataInputStream in = new DataInputStream(fin) + ) { + priorChecksum = in.readUTF(); + } + if (Objects.equals(newChecksum, priorChecksum)) { + // Same checksum => no state change => don't rewrite the widget data + return; + } + } + } // else widget state *became* empty, so we need to commit a deletion + + BackupDataOutput out = new BackupDataOutput(fd); + if (widgetState != null) { + try ( + FileOutputStream fout = new FileOutputStream(widgetFile); + DataOutputStream stateOut = new DataOutputStream(fout) + ) { + stateOut.writeUTF(newChecksum); + } + + out.writeEntityHeader(KEY_WIDGET_STATE, widgetState.length); + out.writeEntityData(widgetState, widgetState.length); + } else { + // Widget state for this app has been removed; commit a deletion + out.writeEntityHeader(KEY_WIDGET_STATE, -1); + widgetFile.delete(); + } + } + @Override public void operationComplete() { // Okay, the agent successfully reported back to us! @@ -2733,17 +2812,7 @@ public class BackupManagerService { } // Piggyback the widget state payload, if any - BackupDataOutput out = new BackupDataOutput(fd); - byte[] widgetState = AppWidgetBackupBridge.getWidgetState(pkgName, - UserHandle.USER_OWNER); - if (widgetState != null) { - out.writeEntityHeader(KEY_WIDGET_STATE, widgetState.length); - out.writeEntityData(widgetState, widgetState.length); - } else { - // No widget state for this app, but push a 'delete' operation for it - // in case they're trying to play games with the payload. - out.writeEntityHeader(KEY_WIDGET_STATE, -1); - } + writeWidgetPayloadIfAppropriate(fd, pkgName); } catch (IOException e) { // Hard disk error; recovery/failure policy TBD. For now roll back, // but we may want to consider this a transport-level failure (i.e. -- cgit v1.2.3-59-g8ed1b