1/*
2 * Copyright (C) 2017 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 */
16
17package android.app.admin;
18
19import android.annotation.IntDef;
20import android.annotation.Nullable;
21import android.os.Build;
22import android.os.Parcel;
23import android.os.Parcelable;
24
25import org.xmlpull.v1.XmlPullParser;
26import org.xmlpull.v1.XmlSerializer;
27
28import java.io.IOException;
29import java.lang.annotation.Retention;
30import java.lang.annotation.RetentionPolicy;
31import java.util.Objects;
32
33/**
34 * A class containing information about a pending system update.
35 */
36public final class SystemUpdateInfo implements Parcelable {
37
38    /**
39     * Represents it is unknown whether the system update is a security patch.
40     */
41    public static final int SECURITY_PATCH_STATE_UNKNOWN = 0;
42
43    /**
44     * Represents the system update is not a security patch.
45     */
46    public static final int SECURITY_PATCH_STATE_FALSE = 1;
47
48    /**
49     * Represents the system update is a security patch.
50     */
51    public static final int SECURITY_PATCH_STATE_TRUE = 2;
52
53    /** @hide */
54    @Retention(RetentionPolicy.SOURCE)
55    @IntDef({SECURITY_PATCH_STATE_FALSE, SECURITY_PATCH_STATE_TRUE, SECURITY_PATCH_STATE_UNKNOWN})
56    public @interface SecurityPatchState {}
57
58    private static final String ATTR_RECEIVED_TIME = "received-time";
59    private static final String ATTR_SECURITY_PATCH_STATE = "security-patch-state";
60    // Tag used to store original build fingerprint to detect when the update is applied.
61    private static final String ATTR_ORIGINAL_BUILD = "original-build";
62
63    private final long mReceivedTime;
64    @SecurityPatchState
65    private final int mSecurityPatchState;
66
67    private SystemUpdateInfo(long receivedTime, @SecurityPatchState int securityPatchState) {
68        this.mReceivedTime = receivedTime;
69        this.mSecurityPatchState = securityPatchState;
70    }
71
72    private SystemUpdateInfo(Parcel in) {
73        mReceivedTime = in.readLong();
74        mSecurityPatchState = in.readInt();
75    }
76
77    /** @hide */
78    @Nullable
79    public static SystemUpdateInfo of(long receivedTime) {
80        return receivedTime == -1
81                ? null : new SystemUpdateInfo(receivedTime, SECURITY_PATCH_STATE_UNKNOWN);
82    }
83
84    /** @hide */
85    @Nullable
86    public static SystemUpdateInfo of(long receivedTime, boolean isSecurityPatch) {
87        return receivedTime == -1 ? null : new SystemUpdateInfo(receivedTime,
88                isSecurityPatch ? SECURITY_PATCH_STATE_TRUE : SECURITY_PATCH_STATE_FALSE);
89    }
90
91    /**
92     * Gets time when the update was first available in milliseconds since midnight, January 1,
93     * 1970 UTC.
94     * @return Time in milliseconds as given by {@link System#currentTimeMillis()}
95     */
96    public long getReceivedTime() {
97        return mReceivedTime;
98    }
99
100    /**
101     * Gets whether the update is a security patch.
102     * @return {@link #SECURITY_PATCH_STATE_FALSE}, {@link #SECURITY_PATCH_STATE_TRUE}, or
103     *         {@link #SECURITY_PATCH_STATE_UNKNOWN}.
104     */
105    @SecurityPatchState
106    public int getSecurityPatchState() {
107        return mSecurityPatchState;
108    }
109
110    public static final Creator<SystemUpdateInfo> CREATOR =
111            new Creator<SystemUpdateInfo>() {
112                @Override
113                public SystemUpdateInfo createFromParcel(Parcel in) {
114                    return new SystemUpdateInfo(in);
115                }
116
117                @Override
118                public SystemUpdateInfo[] newArray(int size) {
119                    return new SystemUpdateInfo[size];
120                }
121            };
122
123    /** @hide */
124    public void writeToXml(XmlSerializer out, String tag) throws IOException {
125        out.startTag(null, tag);
126        out.attribute(null, ATTR_RECEIVED_TIME, String.valueOf(mReceivedTime));
127        out.attribute(null, ATTR_SECURITY_PATCH_STATE, String.valueOf(mSecurityPatchState));
128        out.attribute(null, ATTR_ORIGINAL_BUILD , Build.FINGERPRINT);
129        out.endTag(null, tag);
130    }
131
132    /** @hide */
133    @Nullable
134    public static SystemUpdateInfo readFromXml(XmlPullParser parser) {
135        // If an OTA has been applied (build fingerprint has changed), discard stale info.
136        final String buildFingerprint = parser.getAttributeValue(null, ATTR_ORIGINAL_BUILD );
137        if (!Build.FINGERPRINT.equals(buildFingerprint)) {
138            return null;
139        }
140        final long receivedTime =
141                Long.parseLong(parser.getAttributeValue(null, ATTR_RECEIVED_TIME));
142        final int securityPatchState =
143                Integer.parseInt(parser.getAttributeValue(null, ATTR_SECURITY_PATCH_STATE));
144        return new SystemUpdateInfo(receivedTime, securityPatchState);
145    }
146
147    @Override
148    public int describeContents() {
149        return 0;
150    }
151
152    @Override
153    public void writeToParcel(Parcel dest, int flags) {
154        dest.writeLong(getReceivedTime());
155        dest.writeInt(getSecurityPatchState());
156    }
157
158    @Override
159    public String toString() {
160        return String.format("SystemUpdateInfo (receivedTime = %d, securityPatchState = %s)",
161                mReceivedTime, securityPatchStateToString(mSecurityPatchState));
162    }
163
164    private static String securityPatchStateToString(@SecurityPatchState int state) {
165        switch (state) {
166            case SECURITY_PATCH_STATE_FALSE:
167                return "false";
168            case SECURITY_PATCH_STATE_TRUE:
169                return "true";
170            case SECURITY_PATCH_STATE_UNKNOWN:
171                return "unknown";
172            default:
173                throw new IllegalArgumentException("Unrecognized security patch state: " + state);
174        }
175    }
176
177    @Override
178    public boolean equals(Object o) {
179        if (this == o) return true;
180        if (o == null || getClass() != o.getClass()) return false;
181        SystemUpdateInfo that = (SystemUpdateInfo) o;
182        return mReceivedTime == that.mReceivedTime
183                && mSecurityPatchState == that.mSecurityPatchState;
184    }
185
186    @Override
187    public int hashCode() {
188        return Objects.hash(mReceivedTime, mSecurityPatchState);
189    }
190}
191