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