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