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