1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.people.data;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.annotation.WorkerThread;
23 import android.os.Environment;
24 import android.text.TextUtils;
25 import android.util.ArrayMap;
26 import android.util.Slog;
27 
28 import java.io.ByteArrayInputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.io.DataInputStream;
31 import java.io.DataOutputStream;
32 import java.io.File;
33 import java.io.IOException;
34 import java.util.Map;
35 import java.util.concurrent.ScheduledExecutorService;
36 import java.util.function.Consumer;
37 
38 /** The data associated with a user profile. */
39 class UserData {
40 
41     private static final String TAG = UserData.class.getSimpleName();
42 
43     private static final int CONVERSATIONS_END_TOKEN = -1;
44 
45     private final @UserIdInt int mUserId;
46 
47     private final File mPerUserPeopleDataDir;
48 
49     private final ScheduledExecutorService mScheduledExecutorService;
50 
51     private boolean mIsUnlocked;
52 
53     private Map<String, PackageData> mPackageDataMap = new ArrayMap<>();
54 
55     @Nullable
56     private String mDefaultDialer;
57 
58     @Nullable
59     private String mDefaultSmsApp;
60 
UserData(@serIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService)61     UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService) {
62         mUserId = userId;
63         mPerUserPeopleDataDir = new File(Environment.getDataSystemCeDirectory(mUserId), "people");
64         mScheduledExecutorService = scheduledExecutorService;
65     }
66 
getUserId()67     @UserIdInt int getUserId() {
68         return mUserId;
69     }
70 
forAllPackages(@onNull Consumer<PackageData> consumer)71     void forAllPackages(@NonNull Consumer<PackageData> consumer) {
72         for (PackageData packageData : mPackageDataMap.values()) {
73             consumer.accept(packageData);
74         }
75     }
76 
setUserUnlocked()77     void setUserUnlocked() {
78         mIsUnlocked = true;
79     }
80 
setUserStopped()81     void setUserStopped() {
82         mIsUnlocked = false;
83     }
84 
isUnlocked()85     boolean isUnlocked() {
86         return mIsUnlocked;
87     }
88 
89     @WorkerThread
loadUserData()90     void loadUserData() {
91         mPerUserPeopleDataDir.mkdir();
92         Map<String, PackageData> packageDataMap = PackageData.packagesDataFromDisk(
93                 mUserId, this::isDefaultDialer, this::isDefaultSmsApp, mScheduledExecutorService,
94                 mPerUserPeopleDataDir);
95         mPackageDataMap.putAll(packageDataMap);
96     }
97 
98     /**
99      * Gets the {@link PackageData} for the specified {@code packageName} if exists; otherwise
100      * creates a new instance and returns it.
101      */
102     @NonNull
getOrCreatePackageData(String packageName)103     PackageData getOrCreatePackageData(String packageName) {
104         return mPackageDataMap.computeIfAbsent(packageName, key -> createPackageData(packageName));
105     }
106 
107     /**
108      * Gets the {@link PackageData} for the specified {@code packageName} if exists; otherwise
109      * returns {@code null}.
110      */
111     @Nullable
getPackageData(@onNull String packageName)112     PackageData getPackageData(@NonNull String packageName) {
113         return mPackageDataMap.get(packageName);
114     }
115 
116     /** Deletes the specified package data. */
deletePackageData(@onNull String packageName)117     void deletePackageData(@NonNull String packageName) {
118         PackageData packageData = mPackageDataMap.remove(packageName);
119         if (packageData != null) {
120             packageData.onDestroy();
121         }
122     }
123 
setDefaultDialer(@ullable String packageName)124     void setDefaultDialer(@Nullable String packageName) {
125         mDefaultDialer = packageName;
126     }
127 
128     @Nullable
getDefaultDialer()129     PackageData getDefaultDialer() {
130         return mDefaultDialer != null ? getPackageData(mDefaultDialer) : null;
131     }
132 
setDefaultSmsApp(@ullable String packageName)133     void setDefaultSmsApp(@Nullable String packageName) {
134         mDefaultSmsApp = packageName;
135     }
136 
137     @Nullable
getDefaultSmsApp()138     PackageData getDefaultSmsApp() {
139         return mDefaultSmsApp != null ? getPackageData(mDefaultSmsApp) : null;
140     }
141 
142     @Nullable
getBackupPayload()143     byte[] getBackupPayload() {
144         ByteArrayOutputStream baos = new ByteArrayOutputStream();
145         DataOutputStream out = new DataOutputStream(baos);
146         for (PackageData packageData : mPackageDataMap.values()) {
147             try {
148                 byte[] conversationsBackupPayload =
149                         packageData.getConversationStore().getBackupPayload();
150                 out.writeInt(conversationsBackupPayload.length);
151                 out.write(conversationsBackupPayload);
152                 out.writeUTF(packageData.getPackageName());
153             } catch (IOException e) {
154                 Slog.e(TAG, "Failed to write conversations to backup payload.", e);
155                 return null;
156             }
157         }
158         try {
159             out.writeInt(CONVERSATIONS_END_TOKEN);
160         } catch (IOException e) {
161             Slog.e(TAG, "Failed to write conversations end token to backup payload.", e);
162             return null;
163         }
164         return baos.toByteArray();
165     }
166 
restore(@onNull byte[] payload)167     void restore(@NonNull byte[] payload) {
168         DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload));
169         try {
170             for (int conversationsPayloadSize = in.readInt();
171                     conversationsPayloadSize != CONVERSATIONS_END_TOKEN;
172                     conversationsPayloadSize = in.readInt()) {
173                 byte[] conversationsPayload = new byte[conversationsPayloadSize];
174                 in.readFully(conversationsPayload, 0, conversationsPayloadSize);
175                 String packageName = in.readUTF();
176                 getOrCreatePackageData(packageName).getConversationStore().restore(
177                         conversationsPayload);
178             }
179         } catch (IOException e) {
180             Slog.e(TAG, "Failed to restore conversations from backup payload.", e);
181         }
182     }
183 
createPackageData(String packageName)184     private PackageData createPackageData(String packageName) {
185         return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp,
186                 mScheduledExecutorService, mPerUserPeopleDataDir);
187     }
188 
isDefaultDialer(String packageName)189     private boolean isDefaultDialer(String packageName) {
190         return TextUtils.equals(mDefaultDialer, packageName);
191     }
192 
isDefaultSmsApp(String packageName)193     private boolean isDefaultSmsApp(String packageName) {
194         return TextUtils.equals(mDefaultSmsApp, packageName);
195     }
196 }
197