ShortcutPackageInfo.java revision a4f89b1251235a7373996d0dda0d888673d8e941
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 int mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN; 63 private int 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(int 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 int getVersionCode() { 100 return mVersionCode; 101 } 102 103 public int 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.versionCode; 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.versionCode < mBackupSourceVersionCode)) { 149 Slog.w(TAG, String.format( 150 "Can't restore: package current version %d < backed up version %d", 151 currentPackage.versionCode, 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.versionCode, pi.lastUpdateTime, 166 BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false); 167 168 ret.mBackupSourceBackupAllowed = s.shouldBackupApp(pi); 169 ret.mBackupSourceVersionCode = pi.versionCode; 170 return ret; 171 } 172 173 public void refreshSignature(ShortcutService s, ShortcutPackageItem pkg) { 174 if (mIsShadow) { 175 s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName() 176 + ", user=" + pkg.getOwnerUserId()); 177 return; 178 } 179 // Note use mUserId here, rather than userId. 180 final PackageInfo pi = s.getPackageInfoWithSignatures( 181 pkg.getPackageName(), pkg.getPackageUserId()); 182 if (pi == null) { 183 Slog.w(TAG, "Package not found: " + pkg.getPackageName()); 184 return; 185 } 186 mSigHashes = BackupUtils.hashSignatureArray(pi.signatures); 187 } 188 189 public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException { 190 191 out.startTag(null, TAG_ROOT); 192 193 ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode); 194 ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime); 195 ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow); 196 ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED, mBackupAllowed); 197 198 ShortcutService.writeAttr(out, ATTR_BACKUP_SOURCE_VERSION, mBackupSourceVersionCode); 199 ShortcutService.writeAttr(out, 200 ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, mBackupSourceBackupAllowed); 201 202 203 for (int i = 0; i < mSigHashes.size(); i++) { 204 out.startTag(null, TAG_SIGNATURE); 205 final String encoded = Base64.getEncoder().encodeToString(mSigHashes.get(i)); 206 ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, encoded); 207 out.endTag(null, TAG_SIGNATURE); 208 } 209 out.endTag(null, TAG_ROOT); 210 } 211 212 public void loadFromXml(XmlPullParser parser, boolean fromBackup) 213 throws IOException, XmlPullParserException { 214 215 // Don't use the version code from the backup file. 216 final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION, 217 ShortcutInfo.VERSION_CODE_UNKNOWN); 218 219 final long lastUpdateTime = ShortcutService.parseLongAttribute( 220 parser, ATTR_LAST_UPDATE_TIME); 221 222 // When restoring from backup, it's always shadow. 223 final boolean shadow = 224 fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); 225 226 // We didn't used to save these attributes, and all backed up shortcuts were from 227 // apps that support backups, so the default values take this fact into consideration. 228 final int backupSourceVersion = ShortcutService.parseIntAttribute(parser, 229 ATTR_BACKUP_SOURCE_VERSION, ShortcutInfo.VERSION_CODE_UNKNOWN); 230 231 // Note the only time these "true" default value is used is when restoring from an old 232 // build that didn't save ATTR_BACKUP_ALLOWED, and that means all the data included in 233 // a backup file were from apps that support backup, so we can just use "true" as the 234 // default. 235 final boolean backupAllowed = ShortcutService.parseBooleanAttribute( 236 parser, ATTR_BACKUP_ALLOWED, true); 237 final boolean backupSourceBackupAllowed = ShortcutService.parseBooleanAttribute( 238 parser, ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, true); 239 240 final ArrayList<byte[]> hashes = new ArrayList<>(); 241 242 final int outerDepth = parser.getDepth(); 243 int type; 244 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 245 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 246 if (type != XmlPullParser.START_TAG) { 247 continue; 248 } 249 final int depth = parser.getDepth(); 250 final String tag = parser.getName(); 251 252 if (depth == outerDepth + 1) { 253 switch (tag) { 254 case TAG_SIGNATURE: { 255 final String hash = ShortcutService.parseStringAttribute( 256 parser, ATTR_SIGNATURE_HASH); 257 // Throws IllegalArgumentException if hash is invalid base64 data 258 final byte[] decoded = Base64.getDecoder().decode(hash); 259 hashes.add(decoded); 260 continue; 261 } 262 } 263 } 264 ShortcutService.warnForInvalidTag(depth, tag); 265 } 266 267 // Successfully loaded; replace the fields. 268 if (fromBackup) { 269 mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN; 270 mBackupSourceVersionCode = versionCode; 271 mBackupSourceBackupAllowed = backupAllowed; 272 } else { 273 mVersionCode = versionCode; 274 mBackupSourceVersionCode = backupSourceVersion; 275 mBackupSourceBackupAllowed = backupSourceBackupAllowed; 276 } 277 mLastUpdateTime = lastUpdateTime; 278 mIsShadow = shadow; 279 mSigHashes = hashes; 280 281 // Note we don't restore it from the file because it didn't used to be saved. 282 // We always start by assuming backup is disabled for the current package, 283 // and this field will have been updated before we actually create a backup, at the same 284 // time when we update the version code. 285 // Until then, the value of mBackupAllowed shouldn't matter, but we don't want to print 286 // a false flag on dumpsys, so set mBackupAllowedInitialized to false. 287 mBackupAllowed = false; 288 mBackupAllowedInitialized = false; 289 } 290 291 public void dump(PrintWriter pw, String prefix) { 292 pw.println(); 293 294 pw.print(prefix); 295 pw.println("PackageInfo:"); 296 297 pw.print(prefix); 298 pw.print(" IsShadow: "); 299 pw.print(mIsShadow); 300 pw.print(mIsShadow ? " (not installed)" : " (installed)"); 301 pw.println(); 302 303 pw.print(prefix); 304 pw.print(" Version: "); 305 pw.print(mVersionCode); 306 pw.println(); 307 308 if (mBackupAllowedInitialized) { 309 pw.print(prefix); 310 pw.print(" Backup Allowed: "); 311 pw.print(mBackupAllowed); 312 pw.println(); 313 } 314 315 if (mBackupSourceVersionCode != ShortcutInfo.VERSION_CODE_UNKNOWN) { 316 pw.print(prefix); 317 pw.print(" Backup source version: "); 318 pw.print(mBackupSourceVersionCode); 319 pw.println(); 320 321 pw.print(prefix); 322 pw.print(" Backup source backup allowed: "); 323 pw.print(mBackupSourceBackupAllowed); 324 pw.println(); 325 } 326 327 pw.print(prefix); 328 pw.print(" Last package update time: "); 329 pw.print(mLastUpdateTime); 330 pw.println(); 331 332 for (int i = 0; i < mSigHashes.size(); i++) { 333 pw.print(prefix); 334 pw.print(" "); 335 pw.print("SigHash: "); 336 pw.println(HexEncoding.encode(mSigHashes.get(i))); 337 } 338 } 339} 340