1 /*
2  * Copyright (C) 2018 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 com.example.android.systemupdatersample;
18 
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 
22 import org.json.JSONArray;
23 import org.json.JSONException;
24 import org.json.JSONObject;
25 
26 import java.io.File;
27 import java.io.Serializable;
28 import java.util.ArrayList;
29 import java.util.Optional;
30 
31 /**
32  * An update description. It will be parsed from JSON, which is intended to
33  * be sent from server to the update app, but in this sample app it will be stored on the device.
34  */
35 public class UpdateConfig implements Parcelable {
36 
37     public static final int AB_INSTALL_TYPE_NON_STREAMING = 0;
38     public static final int AB_INSTALL_TYPE_STREAMING = 1;
39 
40     public static final Parcelable.Creator<UpdateConfig> CREATOR =
41             new Parcelable.Creator<UpdateConfig>() {
42                 @Override
43                 public UpdateConfig createFromParcel(Parcel source) {
44                     return new UpdateConfig(source);
45                 }
46 
47                 @Override
48                 public UpdateConfig[] newArray(int size) {
49                     return new UpdateConfig[size];
50                 }
51             };
52 
53     /** parse update config from json */
fromJson(String json)54     public static UpdateConfig fromJson(String json) throws JSONException {
55         UpdateConfig c = new UpdateConfig();
56 
57         JSONObject o = new JSONObject(json);
58         c.mName = o.getString("name");
59         c.mUrl = o.getString("url");
60         switch (o.getString("ab_install_type")) {
61             case AB_INSTALL_TYPE_NON_STREAMING_JSON:
62                 c.mAbInstallType = AB_INSTALL_TYPE_NON_STREAMING;
63                 break;
64             case AB_INSTALL_TYPE_STREAMING_JSON:
65                 c.mAbInstallType = AB_INSTALL_TYPE_STREAMING;
66                 break;
67             default:
68                 throw new JSONException("Invalid type, expected either "
69                         + "NON_STREAMING or STREAMING, got " + o.getString("ab_install_type"));
70         }
71 
72         // TODO: parse only for A/B updates when non-A/B is implemented
73         JSONObject ab = o.getJSONObject("ab_config");
74         boolean forceSwitchSlot = ab.getBoolean("force_switch_slot");
75         boolean verifyPayloadMetadata = ab.getBoolean("verify_payload_metadata");
76         ArrayList<PackageFile> propertyFiles = new ArrayList<>();
77         if (ab.has("property_files")) {
78             JSONArray propertyFilesJson = ab.getJSONArray("property_files");
79             for (int i = 0; i < propertyFilesJson.length(); i++) {
80                 JSONObject p = propertyFilesJson.getJSONObject(i);
81                 propertyFiles.add(new PackageFile(
82                         p.getString("filename"),
83                         p.getLong("offset"),
84                         p.getLong("size")));
85             }
86         }
87         String authorization = ab.optString("authorization", null);
88         c.mAbConfig = new AbConfig(
89                 forceSwitchSlot,
90                 verifyPayloadMetadata,
91                 propertyFiles.toArray(new PackageFile[0]),
92                 authorization);
93 
94         c.mRawJson = json;
95         return c;
96     }
97 
98     /**
99      * these strings are represent types in JSON config files
100      */
101     private static final String AB_INSTALL_TYPE_NON_STREAMING_JSON = "NON_STREAMING";
102     private static final String AB_INSTALL_TYPE_STREAMING_JSON = "STREAMING";
103 
104     /** name will be visible on UI */
105     private String mName;
106 
107     /** update zip file URI, can be https:// or file:// */
108     private String mUrl;
109 
110     /** non-streaming (first saves locally) OR streaming (on the fly) */
111     private int mAbInstallType;
112 
113     /** A/B update configurations */
114     private AbConfig mAbConfig;
115 
116     private String mRawJson;
117 
UpdateConfig()118     protected UpdateConfig() {
119     }
120 
UpdateConfig(Parcel in)121     protected UpdateConfig(Parcel in) {
122         this.mName = in.readString();
123         this.mUrl = in.readString();
124         this.mAbInstallType = in.readInt();
125         this.mAbConfig = (AbConfig) in.readSerializable();
126         this.mRawJson = in.readString();
127     }
128 
UpdateConfig(String name, String url, int installType)129     public UpdateConfig(String name, String url, int installType) {
130         this.mName = name;
131         this.mUrl = url;
132         this.mAbInstallType = installType;
133     }
134 
getName()135     public String getName() {
136         return mName;
137     }
138 
getUrl()139     public String getUrl() {
140         return mUrl;
141     }
142 
getRawJson()143     public String getRawJson() {
144         return mRawJson;
145     }
146 
getInstallType()147     public int getInstallType() {
148         return mAbInstallType;
149     }
150 
getAbConfig()151     public AbConfig getAbConfig() {
152         return mAbConfig;
153     }
154 
155     /**
156      * @return File object for given url
157      */
getUpdatePackageFile()158     public File getUpdatePackageFile() {
159         if (mAbInstallType != AB_INSTALL_TYPE_NON_STREAMING) {
160             throw new RuntimeException("Expected non-streaming install type");
161         }
162         if (!mUrl.startsWith("file://")) {
163             throw new RuntimeException("url is expected to start with file://");
164         }
165         return new File(mUrl.substring(7, mUrl.length()));
166     }
167 
168     @Override
describeContents()169     public int describeContents() {
170         return 0;
171     }
172 
173     @Override
writeToParcel(Parcel dest, int flags)174     public void writeToParcel(Parcel dest, int flags) {
175         dest.writeString(mName);
176         dest.writeString(mUrl);
177         dest.writeInt(mAbInstallType);
178         dest.writeSerializable(mAbConfig);
179         dest.writeString(mRawJson);
180     }
181 
182     /**
183      * Description of a file in an OTA package zip file.
184      */
185     public static class PackageFile implements Serializable {
186 
187         private static final long serialVersionUID = 31043L;
188 
189         /** filename in an archive */
190         private String mFilename;
191 
192         /** defines beginning of update data in archive */
193         private long mOffset;
194 
195         /** size of the update data in archive */
196         private long mSize;
197 
PackageFile(String filename, long offset, long size)198         public PackageFile(String filename, long offset, long size) {
199             this.mFilename = filename;
200             this.mOffset = offset;
201             this.mSize = size;
202         }
203 
getFilename()204         public String getFilename() {
205             return mFilename;
206         }
207 
getOffset()208         public long getOffset() {
209             return mOffset;
210         }
211 
getSize()212         public long getSize() {
213             return mSize;
214         }
215     }
216 
217     /**
218      * A/B (seamless) update configurations.
219      */
220     public static class AbConfig implements Serializable {
221 
222         private static final long serialVersionUID = 31044L;
223 
224         /**
225          * if set true device will boot to new slot, otherwise user manually
226          * switches slot on the screen.
227          */
228         private boolean mForceSwitchSlot;
229 
230         /**
231          * if set true device will boot to new slot, otherwise user manually
232          * switches slot on the screen.
233          */
234         private boolean mVerifyPayloadMetadata;
235 
236         /** defines beginning of update data in archive */
237         private PackageFile[] mPropertyFiles;
238 
239         /**
240          * SystemUpdaterSample receives the authorization token from the OTA server, in addition
241          * to the package URL. It passes on the info to update_engine, so that the latter can
242          * fetch the data from the package server directly with the token.
243          */
244         private String mAuthorization;
245 
AbConfig( boolean forceSwitchSlot, boolean verifyPayloadMetadata, PackageFile[] propertyFiles, String authorization)246         public AbConfig(
247                 boolean forceSwitchSlot,
248                 boolean verifyPayloadMetadata,
249                 PackageFile[] propertyFiles,
250                 String authorization) {
251             this.mForceSwitchSlot = forceSwitchSlot;
252             this.mVerifyPayloadMetadata = verifyPayloadMetadata;
253             this.mPropertyFiles = propertyFiles;
254             this.mAuthorization = authorization;
255         }
256 
getForceSwitchSlot()257         public boolean getForceSwitchSlot() {
258             return mForceSwitchSlot;
259         }
260 
getVerifyPayloadMetadata()261         public boolean getVerifyPayloadMetadata() {
262             return mVerifyPayloadMetadata;
263         }
264 
getPropertyFiles()265         public PackageFile[] getPropertyFiles() {
266             return mPropertyFiles;
267         }
268 
getAuthorization()269         public Optional<String> getAuthorization() {
270             return mAuthorization == null ? Optional.empty() : Optional.of(mAuthorization);
271         }
272     }
273 
274 }
275