1 package com.android.server.backup.fullbackup;
2 
3 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
4 import static com.android.server.backup.BackupManagerService.TAG;
5 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_VERSION;
6 import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_VERSION;
7 import static com.android.server.backup.UserBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
8 
9 import android.annotation.Nullable;
10 import android.annotation.UserIdInt;
11 import android.app.backup.FullBackup;
12 import android.app.backup.FullBackupDataOutput;
13 import android.content.pm.PackageInfo;
14 import android.content.pm.PackageManager;
15 import android.content.pm.Signature;
16 import android.content.pm.SigningInfo;
17 import android.os.Build;
18 import android.os.Environment;
19 import android.util.Log;
20 import android.util.StringBuilderPrinter;
21 
22 import com.android.internal.util.Preconditions;
23 
24 import java.io.BufferedOutputStream;
25 import java.io.DataOutputStream;
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 
30 /**
31  * Writes the backup of app-specific metadata to {@link FullBackupDataOutput}. This data is not
32  * backed up by the app's backup agent and is written before the agent writes its own data. This
33  * includes the app's:
34  *
35  * <ul>
36  *   <li>manifest
37  *   <li>widget data
38  *   <li>apk
39  *   <li>obb content
40  * </ul>
41  */
42 // TODO(b/113807190): Fix or remove apk and obb implementation (only used for adb).
43 public class AppMetadataBackupWriter {
44     private final FullBackupDataOutput mOutput;
45     private final PackageManager mPackageManager;
46 
47     /** The destination of the backup is specified by {@code output}. */
AppMetadataBackupWriter(FullBackupDataOutput output, PackageManager packageManager)48     public AppMetadataBackupWriter(FullBackupDataOutput output, PackageManager packageManager) {
49         mOutput = output;
50         mPackageManager = packageManager;
51     }
52 
53     /**
54      * Back up the app's manifest without specifying a pseudo-directory for the TAR stream.
55      *
56      * @see #backupManifest(PackageInfo, File, File, String, String, boolean)
57      */
backupManifest( PackageInfo packageInfo, File manifestFile, File filesDir, boolean withApk)58     public void backupManifest(
59             PackageInfo packageInfo, File manifestFile, File filesDir, boolean withApk)
60             throws IOException {
61         backupManifest(
62                 packageInfo,
63                 manifestFile,
64                 filesDir,
65                 /* domain */ null,
66                 /* linkDomain */ null,
67                 withApk);
68     }
69 
70     /**
71      * Back up the app's manifest.
72      *
73      * <ol>
74      *   <li>Write the app's manifest data to the specified temporary file {@code manifestFile}.
75      *   <li>Backup the file in TAR format to the backup destination {@link #mOutput}.
76      * </ol>
77      *
78      * <p>Note: {@code domain} and {@code linkDomain} are only used by adb to specify a
79      * pseudo-directory for the TAR stream.
80      */
81     // TODO(b/113806991): Look into streaming the backup data directly.
backupManifest( PackageInfo packageInfo, File manifestFile, File filesDir, @Nullable String domain, @Nullable String linkDomain, boolean withApk)82     public void backupManifest(
83             PackageInfo packageInfo,
84             File manifestFile,
85             File filesDir,
86             @Nullable String domain,
87             @Nullable String linkDomain,
88             boolean withApk)
89             throws IOException {
90         byte[] manifestBytes = getManifestBytes(packageInfo, withApk);
91         FileOutputStream outputStream = new FileOutputStream(manifestFile);
92         outputStream.write(manifestBytes);
93         outputStream.close();
94 
95         // We want the manifest block in the archive stream to be constant each time we generate
96         // a backup stream for the app. However, the underlying TAR mechanism sees it as a file and
97         // will propagate its last modified time. We pin the last modified time to zero to prevent
98         // the TAR header from varying.
99         manifestFile.setLastModified(0);
100 
101         FullBackup.backupToTar(
102                 packageInfo.packageName,
103                 domain,
104                 linkDomain,
105                 filesDir.getAbsolutePath(),
106                 manifestFile.getAbsolutePath(),
107                 mOutput);
108     }
109 
110     /**
111      * Gets the app's manifest as a byte array. All data are strings ending in LF.
112      *
113      * <p>The manifest format is:
114      *
115      * <pre>
116      *     BACKUP_MANIFEST_VERSION
117      *     package name
118      *     package version code
119      *     platform version code
120      *     installer package name (can be empty)
121      *     boolean (1 if archive includes .apk, otherwise 0)
122      *     # of signatures N
123      *     N* (signature byte array in ascii format per Signature.toCharsString())
124      * </pre>
125      */
getManifestBytes(PackageInfo packageInfo, boolean withApk)126     private byte[] getManifestBytes(PackageInfo packageInfo, boolean withApk) {
127         String packageName = packageInfo.packageName;
128         StringBuilder builder = new StringBuilder(4096);
129         StringBuilderPrinter printer = new StringBuilderPrinter(builder);
130 
131         printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
132         printer.println(packageName);
133         printer.println(Long.toString(packageInfo.getLongVersionCode()));
134         printer.println(Integer.toString(Build.VERSION.SDK_INT));
135 
136         String installerName = mPackageManager.getInstallerPackageName(packageName);
137         printer.println((installerName != null) ? installerName : "");
138 
139         printer.println(withApk ? "1" : "0");
140 
141         // Write the signature block.
142         SigningInfo signingInfo = packageInfo.signingInfo;
143         if (signingInfo == null) {
144             printer.println("0");
145         } else {
146             // Retrieve the newest signatures to write.
147             // TODO (b/73988180) use entire signing history in case of rollbacks.
148             Signature[] signatures = signingInfo.getApkContentsSigners();
149             printer.println(Integer.toString(signatures.length));
150             for (Signature sig : signatures) {
151                 printer.println(sig.toCharsString());
152             }
153         }
154         return builder.toString().getBytes();
155     }
156 
157     /**
158      * Backup specified widget data. The widget data is prefaced by a metadata header.
159      *
160      * <ol>
161      *   <li>Write a metadata header to the specified temporary file {@code metadataFile}.
162      *   <li>Write widget data bytes to the same file.
163      *   <li>Backup the file in TAR format to the backup destination {@link #mOutput}.
164      * </ol>
165      *
166      * @throws IllegalArgumentException if the widget data provided is empty.
167      */
168     // TODO(b/113806991): Look into streaming the backup data directly.
backupWidget( PackageInfo packageInfo, File metadataFile, File filesDir, byte[] widgetData)169     public void backupWidget(
170             PackageInfo packageInfo, File metadataFile, File filesDir, byte[] widgetData)
171             throws IOException {
172         Preconditions.checkArgument(widgetData.length > 0, "Can't backup widget with no data.");
173 
174         String packageName = packageInfo.packageName;
175         FileOutputStream fileOutputStream = new FileOutputStream(metadataFile);
176         BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
177         DataOutputStream dataOutputStream = new DataOutputStream(bufferedOutputStream);
178 
179         byte[] metadata = getMetadataBytes(packageName);
180         bufferedOutputStream.write(metadata); // bypassing DataOutputStream
181         writeWidgetData(dataOutputStream, widgetData);
182         bufferedOutputStream.flush();
183         dataOutputStream.close();
184 
185         // As with the manifest file, guarantee consistency of the archive metadata for the widget
186         // block by using a fixed last modified time on the metadata file.
187         metadataFile.setLastModified(0);
188 
189         FullBackup.backupToTar(
190                 packageName,
191                 /* domain */ null,
192                 /* linkDomain */ null,
193                 filesDir.getAbsolutePath(),
194                 metadataFile.getAbsolutePath(),
195                 mOutput);
196     }
197 
198     /**
199      * Gets the app's metadata as a byte array. All entries are strings ending in LF.
200      *
201      * <p>The metadata format is:
202      *
203      * <pre>
204      *     BACKUP_METADATA_VERSION
205      *     package name
206      * </pre>
207      */
getMetadataBytes(String packageName)208     private byte[] getMetadataBytes(String packageName) {
209         StringBuilder builder = new StringBuilder(512);
210         StringBuilderPrinter printer = new StringBuilderPrinter(builder);
211         printer.println(Integer.toString(BACKUP_METADATA_VERSION));
212         printer.println(packageName);
213         return builder.toString().getBytes();
214     }
215 
216     /**
217      * Write a byte array of widget data to the specified output stream. All integers are binary in
218      * network byte order.
219      *
220      * <p>The widget data format:
221      *
222      * <pre>
223      *     4 : Integer token identifying the widget data blob.
224      *     4 : Integer size of the widget data.
225      *     N : Raw bytes of the widget data.
226      * </pre>
227      */
writeWidgetData(DataOutputStream out, byte[] widgetData)228     private void writeWidgetData(DataOutputStream out, byte[] widgetData) throws IOException {
229         out.writeInt(BACKUP_WIDGET_METADATA_TOKEN);
230         out.writeInt(widgetData.length);
231         out.write(widgetData);
232     }
233 
234     /**
235      * Backup the app's .apk to the backup destination {@link #mOutput}. Currently only used for
236      * 'adb backup'.
237      */
238     // TODO(b/113807190): Investigate and potentially remove.
backupApk(PackageInfo packageInfo)239     public void backupApk(PackageInfo packageInfo) {
240         // TODO: handle backing up split APKs
241         String appSourceDir = packageInfo.applicationInfo.getBaseCodePath();
242         String apkDir = new File(appSourceDir).getParent();
243         FullBackup.backupToTar(
244                 packageInfo.packageName,
245                 FullBackup.APK_TREE_TOKEN,
246                 /* linkDomain */ null,
247                 apkDir,
248                 appSourceDir,
249                 mOutput);
250     }
251 
252     /**
253      * Backup the app's .obb files to the backup destination {@link #mOutput}. Currently only used
254      * for 'adb backup'.
255      */
256     // TODO(b/113807190): Investigate and potentially remove.
backupObb(@serIdInt int userId, PackageInfo packageInfo)257     public void backupObb(@UserIdInt int userId, PackageInfo packageInfo) {
258         // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM doesn't have access to
259         // external storage.
260         Environment.UserEnvironment userEnv =
261                 new Environment.UserEnvironment(userId);
262         File obbDir = userEnv.buildExternalStorageAppObbDirs(packageInfo.packageName)[0];
263         if (obbDir != null) {
264             if (MORE_DEBUG) {
265                 Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
266             }
267             File[] obbFiles = obbDir.listFiles();
268             if (obbFiles != null) {
269                 String obbDirName = obbDir.getAbsolutePath();
270                 for (File obb : obbFiles) {
271                     FullBackup.backupToTar(
272                             packageInfo.packageName,
273                             FullBackup.OBB_TREE_TOKEN,
274                             /* linkDomain */ null,
275                             obbDirName,
276                             obb.getAbsolutePath(),
277                             mOutput);
278                 }
279             }
280         }
281     }
282 }
283