• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.server.pm;
17 
18 import android.annotation.NonNull;
19 import android.annotation.UserIdInt;
20 import android.content.pm.PackageInfo;
21 import android.content.pm.PackageManagerInternal;
22 import android.content.pm.ShortcutInfo;
23 import android.content.pm.Signature;
24 import android.content.pm.SigningInfo;
25 import android.util.Slog;
26 import android.util.TypedXmlPullParser;
27 import android.util.TypedXmlSerializer;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.server.LocalServices;
31 import com.android.server.backup.BackupUtils;
32 
33 import libcore.util.HexEncoding;
34 
35 import org.xmlpull.v1.XmlPullParser;
36 import org.xmlpull.v1.XmlPullParserException;
37 
38 import java.io.IOException;
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 import java.util.Base64;
42 
43 /**
44  * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
45  *
46  * All methods should be guarded by {@code ShortcutService.mLock}.
47  */
48 class ShortcutPackageInfo {
49     private static final String TAG = ShortcutService.TAG;
50 
51     static final String TAG_ROOT = "package-info";
52     private static final String ATTR_VERSION = "version";
53     private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
54     private static final String ATTR_BACKUP_SOURCE_VERSION = "bk_src_version";
55     private static final String ATTR_BACKUP_ALLOWED = "allow-backup";
56     private static final String ATTR_BACKUP_ALLOWED_INITIALIZED = "allow-backup-initialized";
57     private static final String ATTR_BACKUP_SOURCE_BACKUP_ALLOWED = "bk_src_backup-allowed";
58     private static final String ATTR_SHADOW = "shadow";
59 
60     private static final String TAG_SIGNATURE = "signature";
61     private static final String ATTR_SIGNATURE_HASH = "hash";
62 
63     /**
64      * When true, this package information was restored from the previous device, and the app hasn't
65      * been installed yet.
66      */
67     private boolean mIsShadow;
68     private long mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
69     private long mBackupSourceVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
70     private long mLastUpdateTime;
71     private ArrayList<byte[]> mSigHashes;
72 
73     // mBackupAllowed didn't used to be parsisted, so we don't restore it from a file.
74     // mBackupAllowed will always start with false, and will have been updated before making a
75     // backup next time, which works file.
76     // We just don't want to print an uninitialzied mBackupAlldowed value on dumpsys, so
77     // we use this boolean to control dumpsys.
78     private boolean mBackupAllowedInitialized;
79     private boolean mBackupAllowed;
80     private boolean mBackupSourceBackupAllowed;
81 
ShortcutPackageInfo(long versionCode, long lastUpdateTime, ArrayList<byte[]> sigHashes, boolean isShadow)82     private ShortcutPackageInfo(long versionCode, long lastUpdateTime,
83             ArrayList<byte[]> sigHashes, boolean isShadow) {
84         mVersionCode = versionCode;
85         mLastUpdateTime = lastUpdateTime;
86         mIsShadow = isShadow;
87         mSigHashes = sigHashes;
88         mBackupAllowed = false; // By default, we assume false.
89         mBackupSourceBackupAllowed = false;
90     }
91 
newEmpty()92     public static ShortcutPackageInfo newEmpty() {
93         return new ShortcutPackageInfo(ShortcutInfo.VERSION_CODE_UNKNOWN, /* last update time =*/ 0,
94                 new ArrayList<>(0), /* isShadow */ false);
95     }
96 
isShadow()97     public boolean isShadow() {
98         return mIsShadow;
99     }
100 
setShadow(boolean shadow)101     public void setShadow(boolean shadow) {
102         mIsShadow = shadow;
103     }
104 
getVersionCode()105     public long getVersionCode() {
106         return mVersionCode;
107     }
108 
getBackupSourceVersionCode()109     public long getBackupSourceVersionCode() {
110         return mBackupSourceVersionCode;
111     }
112 
113     @VisibleForTesting
isBackupSourceBackupAllowed()114     public boolean isBackupSourceBackupAllowed() {
115         return mBackupSourceBackupAllowed;
116     }
117 
getLastUpdateTime()118     public long getLastUpdateTime() {
119         return mLastUpdateTime;
120     }
121 
isBackupAllowed()122     public boolean isBackupAllowed() {
123         return mBackupAllowed;
124     }
125 
126     /**
127      * Set {@link #mVersionCode}, {@link #mLastUpdateTime} and {@link #mBackupAllowed}
128      * from a {@link PackageInfo}.
129      */
updateFromPackageInfo(@onNull PackageInfo pi)130     public void updateFromPackageInfo(@NonNull PackageInfo pi) {
131         if (pi != null) {
132             mVersionCode = pi.getLongVersionCode();
133             mLastUpdateTime = pi.lastUpdateTime;
134             mBackupAllowed = ShortcutService.shouldBackupApp(pi);
135             mBackupAllowedInitialized = true;
136         }
137     }
138 
hasSignatures()139     public boolean hasSignatures() {
140         return mSigHashes.size() > 0;
141     }
142 
143     //@DisabledReason
canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay)144     public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) {
145         PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
146         if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage, pmi)) {
147             Slog.w(TAG, "Can't restore: Package signature mismatch");
148             return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
149         }
150         if (!ShortcutService.shouldBackupApp(currentPackage) || !mBackupSourceBackupAllowed) {
151             // "allowBackup" was true when backed up, but now false.
152             Slog.w(TAG, "Can't restore: package didn't or doesn't allow backup");
153             return ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED;
154         }
155         if (!anyVersionOkay && (currentPackage.getLongVersionCode() < mBackupSourceVersionCode)) {
156             Slog.w(TAG, String.format(
157                     "Can't restore: package current version %d < backed up version %d",
158                     currentPackage.getLongVersionCode(), mBackupSourceVersionCode));
159             return ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
160         }
161         return ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
162     }
163 
164     @VisibleForTesting
generateForInstalledPackageForTest( ShortcutService s, String packageName, @UserIdInt int packageUserId)165     public static ShortcutPackageInfo generateForInstalledPackageForTest(
166             ShortcutService s, String packageName, @UserIdInt int packageUserId) {
167         final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId);
168         // retrieve the newest sigs
169         SigningInfo signingInfo = pi.signingInfo;
170         if (signingInfo == null) {
171             Slog.e(TAG, "Can't get signatures: package=" + packageName);
172             return null;
173         }
174         // TODO (b/73988180) use entire signing history in case of rollbacks
175         Signature[] signatures = signingInfo.getApkContentsSigners();
176         final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.getLongVersionCode(),
177                 pi.lastUpdateTime, BackupUtils.hashSignatureArray(signatures), /* shadow=*/ false);
178 
179         ret.mBackupSourceBackupAllowed = s.shouldBackupApp(pi);
180         ret.mBackupSourceVersionCode = pi.getLongVersionCode();
181         return ret;
182     }
183 
refreshSignature(ShortcutService s, ShortcutPackageItem pkg)184     public void refreshSignature(ShortcutService s, ShortcutPackageItem pkg) {
185         if (mIsShadow) {
186             s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName()
187                     + ", user=" + pkg.getOwnerUserId());
188             return;
189         }
190         // Note use mUserId here, rather than userId.
191         final PackageInfo pi = s.getPackageInfoWithSignatures(
192                 pkg.getPackageName(), pkg.getPackageUserId());
193         if (pi == null) {
194             Slog.w(TAG, "Package not found: " + pkg.getPackageName());
195             return;
196         }
197         // retrieve the newest sigs
198         SigningInfo signingInfo = pi.signingInfo;
199         if (signingInfo == null) {
200             Slog.w(TAG, "Not refreshing signature for " + pkg.getPackageName()
201                     + " since it appears to have no signing info.");
202             return;
203         }
204         // TODO (b/73988180) use entire signing history in case of rollbacks
205         Signature[] signatures = signingInfo.getApkContentsSigners();
206         mSigHashes = BackupUtils.hashSignatureArray(signatures);
207     }
208 
saveToXml(ShortcutService s, TypedXmlSerializer out, boolean forBackup)209     public void saveToXml(ShortcutService s, TypedXmlSerializer out, boolean forBackup)
210             throws IOException {
211         if (forBackup && !mBackupAllowedInitialized) {
212             s.wtf("Backup happened before mBackupAllowed is initialized.");
213         }
214 
215         out.startTag(null, TAG_ROOT);
216 
217         ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
218         ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
219         ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
220         ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED, mBackupAllowed);
221 
222         // We don't need to save this field (we don't even read it back), but it'll show up
223         // in the dumpsys in the backup / restore payload.
224         ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED_INITIALIZED, mBackupAllowedInitialized);
225 
226         ShortcutService.writeAttr(out, ATTR_BACKUP_SOURCE_VERSION, mBackupSourceVersionCode);
227         ShortcutService.writeAttr(out,
228                 ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, mBackupSourceBackupAllowed);
229 
230 
231         for (int i = 0; i < mSigHashes.size(); i++) {
232             out.startTag(null, TAG_SIGNATURE);
233             final String encoded = Base64.getEncoder().encodeToString(mSigHashes.get(i));
234             ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, encoded);
235             out.endTag(null, TAG_SIGNATURE);
236         }
237         out.endTag(null, TAG_ROOT);
238     }
239 
loadFromXml(TypedXmlPullParser parser, boolean fromBackup)240     public void loadFromXml(TypedXmlPullParser parser, boolean fromBackup)
241             throws IOException, XmlPullParserException {
242         // Don't use the version code from the backup file.
243         final long versionCode = ShortcutService.parseLongAttribute(parser, ATTR_VERSION,
244                 ShortcutInfo.VERSION_CODE_UNKNOWN);
245 
246         final long lastUpdateTime = ShortcutService.parseLongAttribute(
247                 parser, ATTR_LAST_UPDATE_TIME);
248 
249         // When restoring from backup, it's always shadow.
250         final boolean shadow =
251                 fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
252 
253         // We didn't used to save these attributes, and all backed up shortcuts were from
254         // apps that support backups, so the default values take this fact into consideration.
255         final long backupSourceVersion = ShortcutService.parseLongAttribute(parser,
256                 ATTR_BACKUP_SOURCE_VERSION, ShortcutInfo.VERSION_CODE_UNKNOWN);
257 
258         // Note the only time these "true" default value is used is when restoring from an old
259         // build that didn't save ATTR_BACKUP_ALLOWED, and that means all the data included in
260         // a backup file were from apps that support backup, so we can just use "true" as the
261         // default.
262         final boolean backupAllowed = ShortcutService.parseBooleanAttribute(
263                 parser, ATTR_BACKUP_ALLOWED, true);
264         final boolean backupSourceBackupAllowed = ShortcutService.parseBooleanAttribute(
265                 parser, ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, true);
266 
267         final ArrayList<byte[]> hashes = new ArrayList<>();
268 
269         final int outerDepth = parser.getDepth();
270         int type;
271         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
272                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
273             if (type != XmlPullParser.START_TAG) {
274                 continue;
275             }
276             final int depth = parser.getDepth();
277             final String tag = parser.getName();
278 
279             if (depth == outerDepth + 1) {
280                 switch (tag) {
281                     case TAG_SIGNATURE: {
282                         final String hash = ShortcutService.parseStringAttribute(
283                                 parser, ATTR_SIGNATURE_HASH);
284                         // Throws IllegalArgumentException if hash is invalid base64 data
285                         final byte[] decoded = Base64.getDecoder().decode(hash);
286                         hashes.add(decoded);
287                         continue;
288                     }
289                 }
290             }
291             ShortcutService.warnForInvalidTag(depth, tag);
292         }
293 
294         // Successfully loaded; replace the fields.
295         if (fromBackup) {
296             mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
297             mBackupSourceVersionCode = versionCode;
298             mBackupSourceBackupAllowed = backupAllowed;
299         } else {
300             mVersionCode = versionCode;
301             mBackupSourceVersionCode = backupSourceVersion;
302             mBackupSourceBackupAllowed = backupSourceBackupAllowed;
303         }
304         mLastUpdateTime = lastUpdateTime;
305         mIsShadow = shadow;
306         mSigHashes = hashes;
307 
308         // Note we don't restore it from the file because it didn't used to be saved.
309         // We always start by assuming backup is disabled for the current package,
310         // and this field will have been updated before we actually create a backup, at the same
311         // time when we update the version code.
312         // Until then, the value of mBackupAllowed shouldn't matter, but we don't want to print
313         // a false flag on dumpsys, so set mBackupAllowedInitialized to false.
314         mBackupAllowed = false;
315         mBackupAllowedInitialized = false;
316     }
317 
dump(PrintWriter pw, String prefix)318     public void dump(PrintWriter pw, String prefix) {
319         pw.println();
320 
321         pw.print(prefix);
322         pw.println("PackageInfo:");
323 
324         pw.print(prefix);
325         pw.print("  IsShadow: ");
326         pw.print(mIsShadow);
327         pw.print(mIsShadow ? " (not installed)" : " (installed)");
328         pw.println();
329 
330         pw.print(prefix);
331         pw.print("  Version: ");
332         pw.print(mVersionCode);
333         pw.println();
334 
335         if (mBackupAllowedInitialized) {
336             pw.print(prefix);
337             pw.print("  Backup Allowed: ");
338             pw.print(mBackupAllowed);
339             pw.println();
340         }
341 
342         if (mBackupSourceVersionCode != ShortcutInfo.VERSION_CODE_UNKNOWN) {
343             pw.print(prefix);
344             pw.print("  Backup source version: ");
345             pw.print(mBackupSourceVersionCode);
346             pw.println();
347 
348             pw.print(prefix);
349             pw.print("  Backup source backup allowed: ");
350             pw.print(mBackupSourceBackupAllowed);
351             pw.println();
352         }
353 
354         pw.print(prefix);
355         pw.print("  Last package update time: ");
356         pw.print(mLastUpdateTime);
357         pw.println();
358 
359         for (int i = 0; i < mSigHashes.size(); i++) {
360             pw.print(prefix);
361             pw.print("    ");
362             pw.print("SigHash: ");
363             pw.println(HexEncoding.encode(mSigHashes.get(i)));
364         }
365     }
366 }
367