ShortcutPackageInfo.java revision c8c3329dd918b8ea16d6317d19ddee12325800f3
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