blob: 8c7871ffaf962b0fb3c7f930bed16cb2a95236a4 [file] [log] [blame]
Makoto Onuki0acbb142016-03-22 17:02:57 -07001/*
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
Makoto Onuki22fcc682016-05-17 14:52:19 -070018import android.annotation.NonNull;
Makoto Onuki0acbb142016-03-22 17:02:57 -070019import android.annotation.UserIdInt;
Makoto Onuki0acbb142016-03-22 17:02:57 -070020import android.content.pm.PackageInfo;
Michal Karpinski528c3e52018-02-07 17:47:10 +000021import android.content.pm.PackageManagerInternal;
Makoto Onukia4f89b12017-10-05 10:37:55 -070022import android.content.pm.ShortcutInfo;
Michal Karpinski528c3e52018-02-07 17:47:10 +000023import android.content.pm.Signature;
Dan Cashman5c9f527e2018-04-03 16:42:23 -070024import android.content.pm.SigningInfo;
Makoto Onuki0acbb142016-03-22 17:02:57 -070025import android.util.Slog;
26
Makoto Onukic8c33292016-09-12 16:36:59 -070027import com.android.internal.annotations.VisibleForTesting;
Michal Karpinski528c3e52018-02-07 17:47:10 +000028import com.android.server.LocalServices;
Makoto Onuki9da23fc2016-03-29 11:14:42 -070029import com.android.server.backup.BackupUtils;
Makoto Onuki0acbb142016-03-22 17:02:57 -070030
Makoto Onuki0acbb142016-03-22 17:02:57 -070031import libcore.util.HexEncoding;
32
33import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35import org.xmlpull.v1.XmlSerializer;
36
37import java.io.IOException;
38import java.io.PrintWriter;
Makoto Onuki0acbb142016-03-22 17:02:57 -070039import java.util.ArrayList;
Tobias Thierer9f00d712016-12-01 23:04:36 +000040import java.util.Base64;
Makoto Onuki0acbb142016-03-22 17:02:57 -070041
42/**
43 * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
Makoto Onuki22fcc682016-05-17 14:52:19 -070044 *
45 * All methods should be guarded by {@code ShortcutService.mLock}.
Makoto Onuki0acbb142016-03-22 17:02:57 -070046 */
Makoto Onuki9da23fc2016-03-29 11:14:42 -070047class ShortcutPackageInfo {
Makoto Onuki0acbb142016-03-22 17:02:57 -070048 private static final String TAG = ShortcutService.TAG;
49
50 static final String TAG_ROOT = "package-info";
Makoto Onuki0acbb142016-03-22 17:02:57 -070051 private static final String ATTR_VERSION = "version";
Makoto Onuki22fcc682016-05-17 14:52:19 -070052 private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
Makoto Onukia4f89b12017-10-05 10:37:55 -070053 private static final String ATTR_BACKUP_SOURCE_VERSION = "bk_src_version";
54 private static final String ATTR_BACKUP_ALLOWED = "allow-backup";
Makoto Onukie3fffa92018-02-28 16:25:40 -080055 private static final String ATTR_BACKUP_ALLOWED_INITIALIZED = "allow-backup-initialized";
Makoto Onukia4f89b12017-10-05 10:37:55 -070056 private static final String ATTR_BACKUP_SOURCE_BACKUP_ALLOWED = "bk_src_backup-allowed";
Makoto Onuki0acbb142016-03-22 17:02:57 -070057 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
Makoto Onuki0acbb142016-03-22 17:02:57 -070062 /**
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;
Dianne Hackborn3accca02013-09-20 09:32:11 -070067 private long mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
68 private long mBackupSourceVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
Makoto Onuki22fcc682016-05-17 14:52:19 -070069 private long mLastUpdateTime;
Makoto Onuki0acbb142016-03-22 17:02:57 -070070 private ArrayList<byte[]> mSigHashes;
71
Makoto Onukia4f89b12017-10-05 10:37:55 -070072 // 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
Dianne Hackborn3accca02013-09-20 09:32:11 -070081 private ShortcutPackageInfo(long versionCode, long lastUpdateTime,
Makoto Onuki22fcc682016-05-17 14:52:19 -070082 ArrayList<byte[]> sigHashes, boolean isShadow) {
Makoto Onuki0acbb142016-03-22 17:02:57 -070083 mVersionCode = versionCode;
Makoto Onuki22fcc682016-05-17 14:52:19 -070084 mLastUpdateTime = lastUpdateTime;
Makoto Onuki0acbb142016-03-22 17:02:57 -070085 mIsShadow = isShadow;
86 mSigHashes = sigHashes;
Makoto Onukia4f89b12017-10-05 10:37:55 -070087 mBackupAllowed = false; // By default, we assume false.
88 mBackupSourceBackupAllowed = false;
Makoto Onuki0acbb142016-03-22 17:02:57 -070089 }
90
Makoto Onuki9da23fc2016-03-29 11:14:42 -070091 public static ShortcutPackageInfo newEmpty() {
Makoto Onukia4f89b12017-10-05 10:37:55 -070092 return new ShortcutPackageInfo(ShortcutInfo.VERSION_CODE_UNKNOWN, /* last update time =*/ 0,
Makoto Onuki22fcc682016-05-17 14:52:19 -070093 new ArrayList<>(0), /* isShadow */ false);
Makoto Onukid99c6f02016-03-28 11:02:54 -070094 }
95
Makoto Onuki0acbb142016-03-22 17:02:57 -070096 public boolean isShadow() {
97 return mIsShadow;
98 }
99
Makoto Onuki0acbb142016-03-22 17:02:57 -0700100 public void setShadow(boolean shadow) {
101 mIsShadow = shadow;
102 }
103
Dianne Hackborn3accca02013-09-20 09:32:11 -0700104 public long getVersionCode() {
Makoto Onuki0acbb142016-03-22 17:02:57 -0700105 return mVersionCode;
106 }
107
Dianne Hackborn3accca02013-09-20 09:32:11 -0700108 public long getBackupSourceVersionCode() {
Makoto Onukia4f89b12017-10-05 10:37:55 -0700109 return mBackupSourceVersionCode;
110 }
111
112 @VisibleForTesting
113 public boolean isBackupSourceBackupAllowed() {
114 return mBackupSourceBackupAllowed;
115 }
116
Makoto Onuki22fcc682016-05-17 14:52:19 -0700117 public long getLastUpdateTime() {
118 return mLastUpdateTime;
119 }
120
Makoto Onukia4f89b12017-10-05 10:37:55 -0700121 public boolean isBackupAllowed() {
122 return mBackupAllowed;
123 }
124
125 /**
126 * Set {@link #mVersionCode}, {@link #mLastUpdateTime} and {@link #mBackupAllowed}
127 * from a {@link PackageInfo}.
128 */
129 public void updateFromPackageInfo(@NonNull PackageInfo pi) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700130 if (pi != null) {
Dianne Hackborn3accca02013-09-20 09:32:11 -0700131 mVersionCode = pi.getLongVersionCode();
Makoto Onuki22fcc682016-05-17 14:52:19 -0700132 mLastUpdateTime = pi.lastUpdateTime;
Makoto Onukia4f89b12017-10-05 10:37:55 -0700133 mBackupAllowed = ShortcutService.shouldBackupApp(pi);
134 mBackupAllowedInitialized = true;
Makoto Onuki22fcc682016-05-17 14:52:19 -0700135 }
Makoto Onuki39686e82016-04-13 18:03:00 -0700136 }
137
Makoto Onuki2e210c42016-03-30 08:30:36 -0700138 public boolean hasSignatures() {
139 return mSigHashes.size() > 0;
140 }
141
Makoto Onukia4f89b12017-10-05 10:37:55 -0700142 //@DisabledReason
143 public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) {
Michal Karpinski528c3e52018-02-07 17:47:10 +0000144 PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
145 if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage, pmi)) {
Makoto Onukia4f89b12017-10-05 10:37:55 -0700146 Slog.w(TAG, "Can't restore: Package signature mismatch");
147 return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
Makoto Onuki2e210c42016-03-30 08:30:36 -0700148 }
Makoto Onukia4f89b12017-10-05 10:37:55 -0700149 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 }
Dianne Hackborn3accca02013-09-20 09:32:11 -0700154 if (!anyVersionOkay && (currentPackage.getLongVersionCode() < mBackupSourceVersionCode)) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700155 Slog.w(TAG, String.format(
156 "Can't restore: package current version %d < backed up version %d",
Dianne Hackborn44947972017-12-11 16:44:08 -0800157 currentPackage.getLongVersionCode(), mBackupSourceVersionCode));
Makoto Onukia4f89b12017-10-05 10:37:55 -0700158 return ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
Makoto Onuki0acbb142016-03-22 17:02:57 -0700159 }
Makoto Onukia4f89b12017-10-05 10:37:55 -0700160 return ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
Makoto Onuki0acbb142016-03-22 17:02:57 -0700161 }
162
Makoto Onukic8c33292016-09-12 16:36:59 -0700163 @VisibleForTesting
164 public static ShortcutPackageInfo generateForInstalledPackageForTest(
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700165 ShortcutService s, String packageName, @UserIdInt int packageUserId) {
166 final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId);
Michal Karpinski528c3e52018-02-07 17:47:10 +0000167 // retrieve the newest sigs
Dan Cashman5c9f527e2018-04-03 16:42:23 -0700168 SigningInfo signingInfo = pi.signingInfo;
169 if (signingInfo == null) {
Makoto Onuki0acbb142016-03-22 17:02:57 -0700170 Slog.e(TAG, "Can't get signatures: package=" + packageName);
171 return null;
172 }
Dan Cashman5c9f527e2018-04-03 16:42:23 -0700173 // TODO (b/73988180) use entire signing history in case of rollbacks
174 Signature[] signatures = signingInfo.getApkContentsSigners();
Dianne Hackborn3accca02013-09-20 09:32:11 -0700175 final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.getLongVersionCode(),
Michal Karpinski528c3e52018-02-07 17:47:10 +0000176 pi.lastUpdateTime, BackupUtils.hashSignatureArray(signatures), /* shadow=*/ false);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700177
Makoto Onukia4f89b12017-10-05 10:37:55 -0700178 ret.mBackupSourceBackupAllowed = s.shouldBackupApp(pi);
Dianne Hackborn3accca02013-09-20 09:32:11 -0700179 ret.mBackupSourceVersionCode = pi.getLongVersionCode();
Makoto Onuki0acbb142016-03-22 17:02:57 -0700180 return ret;
181 }
182
Makoto Onukic8c33292016-09-12 16:36:59 -0700183 public void refreshSignature(ShortcutService s, ShortcutPackageItem pkg) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700184 if (mIsShadow) {
185 s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName()
186 + ", user=" + pkg.getOwnerUserId());
187 return;
188 }
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700189 // Note use mUserId here, rather than userId.
190 final PackageInfo pi = s.getPackageInfoWithSignatures(
191 pkg.getPackageName(), pkg.getPackageUserId());
Makoto Onuki0acbb142016-03-22 17:02:57 -0700192 if (pi == null) {
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700193 Slog.w(TAG, "Package not found: " + pkg.getPackageName());
Makoto Onuki0acbb142016-03-22 17:02:57 -0700194 return;
195 }
Michal Karpinski528c3e52018-02-07 17:47:10 +0000196 // retrieve the newest sigs
Dan Cashman5c9f527e2018-04-03 16:42:23 -0700197 SigningInfo signingInfo = pi.signingInfo;
198 if (signingInfo == null) {
Michal Karpinski528c3e52018-02-07 17:47:10 +0000199 Slog.w(TAG, "Not refreshing signature for " + pkg.getPackageName()
Dan Cashman5c9f527e2018-04-03 16:42:23 -0700200 + " since it appears to have no signing info.");
Michal Karpinski528c3e52018-02-07 17:47:10 +0000201 return;
202 }
Dan Cashman5c9f527e2018-04-03 16:42:23 -0700203 // TODO (b/73988180) use entire signing history in case of rollbacks
204 Signature[] signatures = signingInfo.getApkContentsSigners();
Michal Karpinski528c3e52018-02-07 17:47:10 +0000205 mSigHashes = BackupUtils.hashSignatureArray(signatures);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700206 }
207
Makoto Onukie3fffa92018-02-28 16:25:40 -0800208 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 }
Makoto Onuki0acbb142016-03-22 17:02:57 -0700213
214 out.startTag(null, TAG_ROOT);
215
Makoto Onuki0acbb142016-03-22 17:02:57 -0700216 ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700217 ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700218 ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
Makoto Onukia4f89b12017-10-05 10:37:55 -0700219 ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED, mBackupAllowed);
220
Makoto Onukie3fffa92018-02-28 16:25:40 -0800221 // 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
Makoto Onukia4f89b12017-10-05 10:37:55 -0700225 ShortcutService.writeAttr(out, ATTR_BACKUP_SOURCE_VERSION, mBackupSourceVersionCode);
226 ShortcutService.writeAttr(out,
227 ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, mBackupSourceBackupAllowed);
228
Makoto Onuki0acbb142016-03-22 17:02:57 -0700229
230 for (int i = 0; i < mSigHashes.size(); i++) {
231 out.startTag(null, TAG_SIGNATURE);
Tobias Thierer9f00d712016-12-01 23:04:36 +0000232 final String encoded = Base64.getEncoder().encodeToString(mSigHashes.get(i));
233 ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, encoded);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700234 out.endTag(null, TAG_SIGNATURE);
235 }
236 out.endTag(null, TAG_ROOT);
237 }
238
Makoto Onuki2e210c42016-03-30 08:30:36 -0700239 public void loadFromXml(XmlPullParser parser, boolean fromBackup)
Makoto Onuki0acbb142016-03-22 17:02:57 -0700240 throws IOException, XmlPullParserException {
Makoto Onukia4f89b12017-10-05 10:37:55 -0700241 // Don't use the version code from the backup file.
Dianne Hackborn3accca02013-09-20 09:32:11 -0700242 final long versionCode = ShortcutService.parseLongAttribute(parser, ATTR_VERSION,
Makoto Onukia4f89b12017-10-05 10:37:55 -0700243 ShortcutInfo.VERSION_CODE_UNKNOWN);
Makoto Onuki2e210c42016-03-30 08:30:36 -0700244
Makoto Onuki440a1ea2016-07-20 14:21:18 -0700245 final long lastUpdateTime = ShortcutService.parseLongAttribute(
Makoto Onuki22fcc682016-05-17 14:52:19 -0700246 parser, ATTR_LAST_UPDATE_TIME);
247
Makoto Onuki2e210c42016-03-30 08:30:36 -0700248 // When restoring from backup, it's always shadow.
249 final boolean shadow =
250 fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700251
Makoto Onukia4f89b12017-10-05 10:37:55 -0700252 // 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.
Dianne Hackborn3accca02013-09-20 09:32:11 -0700254 final long backupSourceVersion = ShortcutService.parseLongAttribute(parser,
Makoto Onukia4f89b12017-10-05 10:37:55 -0700255 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
Makoto Onuki0acbb142016-03-22 17:02:57 -0700266 final ArrayList<byte[]> hashes = new ArrayList<>();
267
Makoto Onuki0acbb142016-03-22 17:02:57 -0700268 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();
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700277
278 if (depth == outerDepth + 1) {
279 switch (tag) {
280 case TAG_SIGNATURE: {
281 final String hash = ShortcutService.parseStringAttribute(
282 parser, ATTR_SIGNATURE_HASH);
Tobias Thierer9f00d712016-12-01 23:04:36 +0000283 // Throws IllegalArgumentException if hash is invalid base64 data
284 final byte[] decoded = Base64.getDecoder().decode(hash);
285 hashes.add(decoded);
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700286 continue;
287 }
Makoto Onuki0acbb142016-03-22 17:02:57 -0700288 }
289 }
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700290 ShortcutService.warnForInvalidTag(depth, tag);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700291 }
Makoto Onuki2e210c42016-03-30 08:30:36 -0700292
Makoto Onukia4f89b12017-10-05 10:37:55 -0700293 // 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 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700303 mLastUpdateTime = lastUpdateTime;
Makoto Onuki2e210c42016-03-30 08:30:36 -0700304 mIsShadow = shadow;
305 mSigHashes = hashes;
Makoto Onukia4f89b12017-10-05 10:37:55 -0700306
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;
Makoto Onuki0acbb142016-03-22 17:02:57 -0700315 }
316
Makoto Onukic51b2872016-05-04 15:24:50 -0700317 public void dump(PrintWriter pw, String prefix) {
Makoto Onuki0acbb142016-03-22 17:02:57 -0700318 pw.println();
319
320 pw.print(prefix);
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700321 pw.println("PackageInfo:");
Makoto Onukid99c6f02016-03-28 11:02:54 -0700322
323 pw.print(prefix);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700324 pw.print(" IsShadow: ");
325 pw.print(mIsShadow);
Makoto Onukia4f89b12017-10-05 10:37:55 -0700326 pw.print(mIsShadow ? " (not installed)" : " (installed)");
Makoto Onuki0acbb142016-03-22 17:02:57 -0700327 pw.println();
328
329 pw.print(prefix);
330 pw.print(" Version: ");
331 pw.print(mVersionCode);
332 pw.println();
333
Makoto Onukia4f89b12017-10-05 10:37:55 -0700334 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
Makoto Onuki22fcc682016-05-17 14:52:19 -0700353 pw.print(prefix);
354 pw.print(" Last package update time: ");
355 pw.print(mLastUpdateTime);
356 pw.println();
357
Makoto Onuki0acbb142016-03-22 17:02:57 -0700358 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}