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.util.Slog; 22 23import com.android.internal.annotations.VisibleForTesting; 24import com.android.server.backup.BackupUtils; 25 26import libcore.io.Base64; 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; 36 37/** 38 * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore. 39 * 40 * All methods should be guarded by {@code ShortcutService.mLock}. 41 */ 42class ShortcutPackageInfo { 43 private static final String TAG = ShortcutService.TAG; 44 45 static final String TAG_ROOT = "package-info"; 46 private static final String ATTR_VERSION = "version"; 47 private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time"; 48 private static final String ATTR_SHADOW = "shadow"; 49 50 private static final String TAG_SIGNATURE = "signature"; 51 private static final String ATTR_SIGNATURE_HASH = "hash"; 52 53 private static final int VERSION_UNKNOWN = -1; 54 55 /** 56 * When true, this package information was restored from the previous device, and the app hasn't 57 * been installed yet. 58 */ 59 private boolean mIsShadow; 60 private int mVersionCode = VERSION_UNKNOWN; 61 private long mLastUpdateTime; 62 private ArrayList<byte[]> mSigHashes; 63 64 private ShortcutPackageInfo(int versionCode, long lastUpdateTime, 65 ArrayList<byte[]> sigHashes, boolean isShadow) { 66 mVersionCode = versionCode; 67 mLastUpdateTime = lastUpdateTime; 68 mIsShadow = isShadow; 69 mSigHashes = sigHashes; 70 } 71 72 public static ShortcutPackageInfo newEmpty() { 73 return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0, 74 new ArrayList<>(0), /* isShadow */ false); 75 } 76 77 public boolean isShadow() { 78 return mIsShadow; 79 } 80 81 public void setShadow(boolean shadow) { 82 mIsShadow = shadow; 83 } 84 85 public int getVersionCode() { 86 return mVersionCode; 87 } 88 89 public long getLastUpdateTime() { 90 return mLastUpdateTime; 91 } 92 93 /** Set {@link #mVersionCode} and {@link #mLastUpdateTime} from a {@link PackageInfo}. */ 94 public void updateVersionInfo(@NonNull PackageInfo pi) { 95 if (pi != null) { 96 mVersionCode = pi.versionCode; 97 mLastUpdateTime = pi.lastUpdateTime; 98 } 99 } 100 101 public boolean hasSignatures() { 102 return mSigHashes.size() > 0; 103 } 104 105 public boolean canRestoreTo(ShortcutService s, PackageInfo target) { 106 if (!s.shouldBackupApp(target)) { 107 // "allowBackup" was true when backed up, but now false. 108 Slog.w(TAG, "Can't restore: package no longer allows backup"); 109 return false; 110 } 111 if (target.versionCode < mVersionCode) { 112 Slog.w(TAG, String.format( 113 "Can't restore: package current version %d < backed up version %d", 114 target.versionCode, mVersionCode)); 115 return false; 116 } 117 if (!BackupUtils.signaturesMatch(mSigHashes, target)) { 118 Slog.w(TAG, "Can't restore: Package signature mismatch"); 119 return false; 120 } 121 return true; 122 } 123 124 @VisibleForTesting 125 public static ShortcutPackageInfo generateForInstalledPackageForTest( 126 ShortcutService s, String packageName, @UserIdInt int packageUserId) { 127 final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId); 128 if (pi.signatures == null || pi.signatures.length == 0) { 129 Slog.e(TAG, "Can't get signatures: package=" + packageName); 130 return null; 131 } 132 final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime, 133 BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false); 134 135 return ret; 136 } 137 138 public void refreshSignature(ShortcutService s, ShortcutPackageItem pkg) { 139 if (mIsShadow) { 140 s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName() 141 + ", user=" + pkg.getOwnerUserId()); 142 return; 143 } 144 // Note use mUserId here, rather than userId. 145 final PackageInfo pi = s.getPackageInfoWithSignatures( 146 pkg.getPackageName(), pkg.getPackageUserId()); 147 if (pi == null) { 148 Slog.w(TAG, "Package not found: " + pkg.getPackageName()); 149 return; 150 } 151 mSigHashes = BackupUtils.hashSignatureArray(pi.signatures); 152 } 153 154 public void saveToXml(XmlSerializer out) throws IOException { 155 156 out.startTag(null, TAG_ROOT); 157 158 ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode); 159 ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime); 160 ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow); 161 162 for (int i = 0; i < mSigHashes.size(); i++) { 163 out.startTag(null, TAG_SIGNATURE); 164 ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, Base64.encode(mSigHashes.get(i))); 165 out.endTag(null, TAG_SIGNATURE); 166 } 167 out.endTag(null, TAG_ROOT); 168 } 169 170 public void loadFromXml(XmlPullParser parser, boolean fromBackup) 171 throws IOException, XmlPullParserException { 172 173 final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION); 174 175 final long lastUpdateTime = ShortcutService.parseLongAttribute( 176 parser, ATTR_LAST_UPDATE_TIME); 177 178 // When restoring from backup, it's always shadow. 179 final boolean shadow = 180 fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); 181 182 final ArrayList<byte[]> hashes = new ArrayList<>(); 183 184 final int outerDepth = parser.getDepth(); 185 int type; 186 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 187 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 188 if (type != XmlPullParser.START_TAG) { 189 continue; 190 } 191 final int depth = parser.getDepth(); 192 final String tag = parser.getName(); 193 194 if (depth == outerDepth + 1) { 195 switch (tag) { 196 case TAG_SIGNATURE: { 197 final String hash = ShortcutService.parseStringAttribute( 198 parser, ATTR_SIGNATURE_HASH); 199 hashes.add(Base64.decode(hash.getBytes())); 200 continue; 201 } 202 } 203 } 204 ShortcutService.warnForInvalidTag(depth, tag); 205 } 206 207 // Successfully loaded; replace the feilds. 208 mVersionCode = versionCode; 209 mLastUpdateTime = lastUpdateTime; 210 mIsShadow = shadow; 211 mSigHashes = hashes; 212 } 213 214 public void dump(PrintWriter pw, String prefix) { 215 pw.println(); 216 217 pw.print(prefix); 218 pw.println("PackageInfo:"); 219 220 pw.print(prefix); 221 pw.print(" IsShadow: "); 222 pw.print(mIsShadow); 223 pw.println(); 224 225 pw.print(prefix); 226 pw.print(" Version: "); 227 pw.print(mVersionCode); 228 pw.println(); 229 230 pw.print(prefix); 231 pw.print(" Last package update time: "); 232 pw.print(mLastUpdateTime); 233 pw.println(); 234 235 for (int i = 0; i < mSigHashes.size(); i++) { 236 pw.print(prefix); 237 pw.print(" "); 238 pw.print("SigHash: "); 239 pw.println(HexEncoding.encode(mSigHashes.get(i))); 240 } 241 } 242} 243