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.util.HexEncoding;
27
28import org.xmlpull.v1.XmlPullParser;
29import org.xmlpull.v1.XmlPullParserException;
30import org.xmlpull.v1.XmlSerializer;
31
32import java.io.IOException;
33import java.io.PrintWriter;
34import java.util.ArrayList;
35import java.util.Base64;
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            final String encoded = Base64.getEncoder().encodeToString(mSigHashes.get(i));
165            ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, encoded);
166            out.endTag(null, TAG_SIGNATURE);
167        }
168        out.endTag(null, TAG_ROOT);
169    }
170
171    public void loadFromXml(XmlPullParser parser, boolean fromBackup)
172            throws IOException, XmlPullParserException {
173
174        final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
175
176        final long lastUpdateTime = ShortcutService.parseLongAttribute(
177                parser, ATTR_LAST_UPDATE_TIME);
178
179        // When restoring from backup, it's always shadow.
180        final boolean shadow =
181                fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
182
183        final ArrayList<byte[]> hashes = new ArrayList<>();
184
185        final int outerDepth = parser.getDepth();
186        int type;
187        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
188                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
189            if (type != XmlPullParser.START_TAG) {
190                continue;
191            }
192            final int depth = parser.getDepth();
193            final String tag = parser.getName();
194
195            if (depth == outerDepth + 1) {
196                switch (tag) {
197                    case TAG_SIGNATURE: {
198                        final String hash = ShortcutService.parseStringAttribute(
199                                parser, ATTR_SIGNATURE_HASH);
200                        // Throws IllegalArgumentException if hash is invalid base64 data
201                        final byte[] decoded = Base64.getDecoder().decode(hash);
202                        hashes.add(decoded);
203                        continue;
204                    }
205                }
206            }
207            ShortcutService.warnForInvalidTag(depth, tag);
208        }
209
210        // Successfully loaded; replace the feilds.
211        mVersionCode = versionCode;
212        mLastUpdateTime = lastUpdateTime;
213        mIsShadow = shadow;
214        mSigHashes = hashes;
215    }
216
217    public void dump(PrintWriter pw, String prefix) {
218        pw.println();
219
220        pw.print(prefix);
221        pw.println("PackageInfo:");
222
223        pw.print(prefix);
224        pw.print("  IsShadow: ");
225        pw.print(mIsShadow);
226        pw.println();
227
228        pw.print(prefix);
229        pw.print("  Version: ");
230        pw.print(mVersionCode);
231        pw.println();
232
233        pw.print(prefix);
234        pw.print("  Last package update time: ");
235        pw.print(mLastUpdateTime);
236        pw.println();
237
238        for (int i = 0; i < mSigHashes.size(); i++) {
239            pw.print(prefix);
240            pw.print("    ");
241            pw.print("SigHash: ");
242            pw.println(HexEncoding.encode(mSigHashes.get(i)));
243        }
244    }
245}
246