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