diff options
author | Trung Lam <lamtrung@google.com> | 2020-02-24 16:51:37 -0800 |
---|---|---|
committer | Trung Lam <lamtrung@google.com> | 2020-02-25 14:32:25 -0800 |
commit | b213fca0ed9770a3011a69c7f5ae2208e2ab6f47 (patch) | |
tree | ee9e792abd0388119102922d008247e099a27ffa /services/people | |
parent | 3ef69f7e7fd309db03af6047f58a4fed3ce44c51 (diff) |
Implement backup and restoration of conversation infos.
Change-Id: I2cc08c54b714b4ab65762ae261cf8a50d7f258fd
Test: Built and tested on device.
Bug: 147512341
Diffstat (limited to 'services/people')
5 files changed, 183 insertions, 4 deletions
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index 2499614a3738..20302990cae7 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -17,6 +17,7 @@ package com.android.server.people; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; @@ -145,14 +146,15 @@ public class PeopleService extends SystemService { mDataManager.pruneDataForUser(userId, signal); } + @Nullable @Override - public byte[] backupConversationInfos(@UserIdInt int userId) { - return new byte[0]; + public byte[] getBackupPayload(@UserIdInt int userId) { + return mDataManager.getBackupPayload(userId); } @Override - public void restoreConversationInfos(@UserIdInt int userId, @NonNull String key, - @NonNull byte[] payload) { + public void restore(@UserIdInt int userId, @NonNull byte[] payload) { + mDataManager.restore(userId, payload); } @VisibleForTesting diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java index 41bc3611e085..dc3fa2a048f6 100644 --- a/services/people/java/com/android/server/people/data/ConversationInfo.java +++ b/services/people/java/com/android/server/people/data/ConversationInfo.java @@ -24,6 +24,7 @@ import android.content.LocusIdProto; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo.ShortcutFlags; import android.net.Uri; +import android.text.TextUtils; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; @@ -31,6 +32,10 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; import com.android.server.people.ConversationInfoProto; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -280,6 +285,25 @@ public class ConversationInfo { } } + @Nullable + byte[] getBackupPayload() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(baos); + try { + out.writeUTF(mShortcutId); + out.writeUTF(mLocusId != null ? mLocusId.getId() : ""); + out.writeUTF(mContactUri != null ? mContactUri.toString() : ""); + out.writeUTF(mNotificationChannelId != null ? mNotificationChannelId : ""); + out.writeInt(mShortcutFlags); + out.writeInt(mConversationFlags); + out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : ""); + } catch (IOException e) { + Slog.e(TAG, "Failed to write fields to backup payload.", e); + return null; + } + return baos.toByteArray(); + } + /** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */ @NonNull static ConversationInfo readFromProto(@NonNull ProtoInputStream protoInputStream) @@ -331,6 +355,37 @@ public class ConversationInfo { return builder.build(); } + @Nullable + static ConversationInfo readFromBackupPayload(@NonNull byte[] payload) { + ConversationInfo.Builder builder = new ConversationInfo.Builder(); + DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload)); + try { + builder.setShortcutId(in.readUTF()); + String locusId = in.readUTF(); + if (!TextUtils.isEmpty(locusId)) { + builder.setLocusId(new LocusId(locusId)); + } + String contactUri = in.readUTF(); + if (!TextUtils.isEmpty(contactUri)) { + builder.setContactUri(Uri.parse(contactUri)); + } + String notificationChannelId = in.readUTF(); + if (!TextUtils.isEmpty(notificationChannelId)) { + builder.setNotificationChannelId(notificationChannelId); + } + builder.setShortcutFlags(in.readInt()); + builder.setConversationFlags(in.readInt()); + String contactPhoneNumber = in.readUTF(); + if (!TextUtils.isEmpty(contactPhoneNumber)) { + builder.setContactPhoneNumber(contactPhoneNumber); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e); + return null; + } + return builder.build(); + } + /** * Builder class for {@link ConversationInfo} objects. */ diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java index 89c4972c8ef4..2f2a95cb0a6c 100644 --- a/services/people/java/com/android/server/people/data/ConversationStore.java +++ b/services/people/java/com/android/server/people/data/ConversationStore.java @@ -31,6 +31,10 @@ import com.android.server.people.ConversationInfosProto; import com.google.android.collect.Lists; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -48,6 +52,8 @@ class ConversationStore { private static final String CONVERSATIONS_FILE_NAME = "conversations"; + private static final int CONVERSATION_INFOS_END_TOKEN = -1; + // Shortcut ID -> Conversation Info @GuardedBy("this") private final Map<String, ConversationInfo> mConversationInfoMap = new ArrayMap<>(); @@ -195,6 +201,51 @@ class ConversationStore { mConversationInfosProtoDiskReadWriter.deleteConversationsFile(); } + @Nullable + synchronized byte[] getBackupPayload() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream conversationInfosOut = new DataOutputStream(baos); + for (ConversationInfo conversationInfo : mConversationInfoMap.values()) { + byte[] backupPayload = conversationInfo.getBackupPayload(); + if (backupPayload == null) { + continue; + } + try { + conversationInfosOut.writeInt(backupPayload.length); + conversationInfosOut.write(backupPayload); + } catch (IOException e) { + Slog.e(TAG, "Failed to write conversation info to backup payload.", e); + return null; + } + } + try { + conversationInfosOut.writeInt(CONVERSATION_INFOS_END_TOKEN); + } catch (IOException e) { + Slog.e(TAG, "Failed to write conversation infos end token to backup payload.", e); + return null; + } + return baos.toByteArray(); + } + + synchronized void restore(@NonNull byte[] payload) { + DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload)); + try { + for (int conversationInfoSize = in.readInt(); + conversationInfoSize != CONVERSATION_INFOS_END_TOKEN; + conversationInfoSize = in.readInt()) { + byte[] conversationInfoPayload = new byte[conversationInfoSize]; + in.readFully(conversationInfoPayload, 0, conversationInfoSize); + ConversationInfo conversationInfo = ConversationInfo.readFromBackupPayload( + conversationInfoPayload); + if (conversationInfo != null) { + addOrUpdate(conversationInfo); + } + } + } catch (IOException e) { + Slog.e(TAG, "Failed to read conversation info from payload.", e); + } + } + @MainThread private synchronized void updateConversationsInMemory( @NonNull ConversationInfo conversationInfo) { diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 3a34c6a6be95..e4ce6bafff31 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -334,6 +334,25 @@ public class DataManager { }); } + /** Retrieves a backup payload blob for specified user id. */ + @Nullable + public byte[] getBackupPayload(@UserIdInt int userId) { + UserData userData = getUnlockedUserData(userId); + if (userData == null) { + return null; + } + return userData.getBackupPayload(); + } + + /** Attempts to restore data for the specified user id. */ + public void restore(@UserIdInt int userId, @NonNull byte[] payload) { + UserData userData = getUnlockedUserData(userId); + if (userData == null) { + return; + } + userData.restore(payload); + } + private int mimeTypeToShareEventType(String mimeType) { if (mimeType.startsWith("text/")) { return Event.TYPE_SHARE_TEXT; diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java index 0f8b91bfa2b1..ed8c595ab42a 100644 --- a/services/people/java/com/android/server/people/data/UserData.java +++ b/services/people/java/com/android/server/people/data/UserData.java @@ -22,8 +22,14 @@ import android.annotation.UserIdInt; import android.os.Environment; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.Slog; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; +import java.io.IOException; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; @@ -31,6 +37,10 @@ import java.util.function.Consumer; /** The data associated with a user profile. */ class UserData { + private static final String TAG = UserData.class.getSimpleName(); + + private static final int CONVERSATIONS_END_TOKEN = -1; + private final @UserIdInt int mUserId; private final File mPerUserPeopleDataDir; @@ -125,6 +135,48 @@ class UserData { return mDefaultSmsApp != null ? getPackageData(mDefaultSmsApp) : null; } + @Nullable + byte[] getBackupPayload() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(baos); + for (PackageData packageData : mPackageDataMap.values()) { + try { + byte[] conversationsBackupPayload = + packageData.getConversationStore().getBackupPayload(); + out.writeInt(conversationsBackupPayload.length); + out.write(conversationsBackupPayload); + out.writeUTF(packageData.getPackageName()); + } catch (IOException e) { + Slog.e(TAG, "Failed to write conversations to backup payload.", e); + return null; + } + } + try { + out.writeInt(CONVERSATIONS_END_TOKEN); + } catch (IOException e) { + Slog.e(TAG, "Failed to write conversations end token to backup payload.", e); + return null; + } + return baos.toByteArray(); + } + + void restore(@NonNull byte[] payload) { + DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload)); + try { + for (int conversationsPayloadSize = in.readInt(); + conversationsPayloadSize != CONVERSATIONS_END_TOKEN; + conversationsPayloadSize = in.readInt()) { + byte[] conversationsPayload = new byte[conversationsPayloadSize]; + in.readFully(conversationsPayload, 0, conversationsPayloadSize); + String packageName = in.readUTF(); + getOrCreatePackageData(packageName).getConversationStore().restore( + conversationsPayload); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to restore conversations from backup payload.", e); + } + } + private PackageData createPackageData(String packageName) { return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp, mScheduledExecutorService, mPerUserPeopleDataDir); |