Merge "updater_sample: Improve UpdateConfig"

This commit is contained in:
Zhomart Mukhamejanov
2018-05-01 19:05:22 +00:00
committed by Gerrit Code Review
9 changed files with 140 additions and 47 deletions
+4
View File
@@ -26,6 +26,10 @@ LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES += guava
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
include $(BUILD_PACKAGE) include $(BUILD_PACKAGE)
# Use the following include to make our test apk. # Use the following include to make our test apk.
@@ -19,6 +19,7 @@ package com.example.android.systemupdatersample;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@@ -26,13 +27,13 @@ import java.io.File;
import java.io.Serializable; import java.io.Serializable;
/** /**
* UpdateConfig describes an update. It will be parsed from JSON, which is intended to * An update description. It will be parsed from JSON, which is intended to
* be sent from server to the update app, but in this sample app it will be stored on the device. * be sent from server to the update app, but in this sample app it will be stored on the device.
*/ */
public class UpdateConfig implements Parcelable { public class UpdateConfig implements Parcelable {
public static final int TYPE_NON_STREAMING = 0; public static final int AB_INSTALL_TYPE_NON_STREAMING = 0;
public static final int TYPE_STREAMING = 1; public static final int AB_INSTALL_TYPE_STREAMING = 1;
public static final Parcelable.Creator<UpdateConfig> CREATOR = public static final Parcelable.Creator<UpdateConfig> CREATOR =
new Parcelable.Creator<UpdateConfig>() { new Parcelable.Creator<UpdateConfig>() {
@@ -54,18 +55,30 @@ public class UpdateConfig implements Parcelable {
JSONObject o = new JSONObject(json); JSONObject o = new JSONObject(json);
c.mName = o.getString("name"); c.mName = o.getString("name");
c.mUrl = o.getString("url"); c.mUrl = o.getString("url");
if (TYPE_NON_STREAMING_JSON.equals(o.getString("type"))) { switch (o.getString("ab_install_type")) {
c.mInstallType = TYPE_NON_STREAMING; case AB_INSTALL_TYPE_NON_STREAMING_JSON:
} else if (TYPE_STREAMING_JSON.equals(o.getString("type"))) { c.mAbInstallType = AB_INSTALL_TYPE_NON_STREAMING;
c.mInstallType = TYPE_STREAMING; break;
} else { case AB_INSTALL_TYPE_STREAMING_JSON:
throw new JSONException("Invalid type, expected either " c.mAbInstallType = AB_INSTALL_TYPE_STREAMING;
+ "NON_STREAMING or STREAMING, got " + o.getString("type")); break;
default:
throw new JSONException("Invalid type, expected either "
+ "NON_STREAMING or STREAMING, got " + o.getString("ab_install_type"));
} }
if (o.has("metadata")) { if (c.mAbInstallType == AB_INSTALL_TYPE_STREAMING) {
c.mMetadata = new Metadata( JSONObject meta = o.getJSONObject("ab_streaming_metadata");
o.getJSONObject("metadata").getInt("offset"), JSONArray propertyFilesJson = meta.getJSONArray("property_files");
o.getJSONObject("metadata").getInt("size")); InnerFile[] propertyFiles =
new InnerFile[propertyFilesJson.length()];
for (int i = 0; i < propertyFilesJson.length(); i++) {
JSONObject p = propertyFilesJson.getJSONObject(i);
propertyFiles[i] = new InnerFile(
p.getString("filename"),
p.getLong("offset"),
p.getLong("size"));
}
c.mAbStreamingMetadata = new StreamingMetadata(propertyFiles);
} }
c.mRawJson = json; c.mRawJson = json;
return c; return c;
@@ -74,8 +87,8 @@ public class UpdateConfig implements Parcelable {
/** /**
* these strings are represent types in JSON config files * these strings are represent types in JSON config files
*/ */
private static final String TYPE_NON_STREAMING_JSON = "NON_STREAMING"; private static final String AB_INSTALL_TYPE_NON_STREAMING_JSON = "NON_STREAMING";
private static final String TYPE_STREAMING_JSON = "STREAMING"; private static final String AB_INSTALL_TYPE_STREAMING_JSON = "STREAMING";
/** name will be visible on UI */ /** name will be visible on UI */
private String mName; private String mName;
@@ -84,10 +97,10 @@ public class UpdateConfig implements Parcelable {
private String mUrl; private String mUrl;
/** non-streaming (first saves locally) OR streaming (on the fly) */ /** non-streaming (first saves locally) OR streaming (on the fly) */
private int mInstallType; private int mAbInstallType;
/** metadata is required only for streaming update */ /** metadata is required only for streaming update */
private Metadata mMetadata; private StreamingMetadata mAbStreamingMetadata;
private String mRawJson; private String mRawJson;
@@ -97,15 +110,15 @@ public class UpdateConfig implements Parcelable {
protected UpdateConfig(Parcel in) { protected UpdateConfig(Parcel in) {
this.mName = in.readString(); this.mName = in.readString();
this.mUrl = in.readString(); this.mUrl = in.readString();
this.mInstallType = in.readInt(); this.mAbInstallType = in.readInt();
this.mMetadata = (Metadata) in.readSerializable(); this.mAbStreamingMetadata = (StreamingMetadata) in.readSerializable();
this.mRawJson = in.readString(); this.mRawJson = in.readString();
} }
public UpdateConfig(String name, String url, int installType) { public UpdateConfig(String name, String url, int installType) {
this.mName = name; this.mName = name;
this.mUrl = url; this.mUrl = url;
this.mInstallType = installType; this.mAbInstallType = installType;
} }
public String getName() { public String getName() {
@@ -121,16 +134,18 @@ public class UpdateConfig implements Parcelable {
} }
public int getInstallType() { public int getInstallType() {
return mInstallType; return mAbInstallType;
}
public StreamingMetadata getStreamingMetadata() {
return mAbStreamingMetadata;
} }
/** /**
* "url" must be the file located on the device.
*
* @return File object for given url * @return File object for given url
*/ */
public File getUpdatePackageFile() { public File getUpdatePackageFile() {
if (mInstallType != TYPE_NON_STREAMING) { if (mAbInstallType != AB_INSTALL_TYPE_NON_STREAMING) {
throw new RuntimeException("Expected non-streaming install type"); throw new RuntimeException("Expected non-streaming install type");
} }
if (!mUrl.startsWith("file://")) { if (!mUrl.startsWith("file://")) {
@@ -148,29 +163,60 @@ public class UpdateConfig implements Parcelable {
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName); dest.writeString(mName);
dest.writeString(mUrl); dest.writeString(mUrl);
dest.writeInt(mInstallType); dest.writeInt(mAbInstallType);
dest.writeSerializable(mMetadata); dest.writeSerializable(mAbStreamingMetadata);
dest.writeString(mRawJson); dest.writeString(mRawJson);
} }
/** /**
* Metadata for STREAMING update * Metadata for streaming A/B update.
*/ */
public static class Metadata implements Serializable { public static class StreamingMetadata implements Serializable {
private static final long serialVersionUID = 31042L; private static final long serialVersionUID = 31042L;
/** defines beginning of update data in archive */
private InnerFile[] mPropertyFiles;
public StreamingMetadata() {
mPropertyFiles = new InnerFile[0];
}
public StreamingMetadata(InnerFile[] propertyFiles) {
this.mPropertyFiles = propertyFiles;
}
public InnerFile[] getPropertyFiles() {
return mPropertyFiles;
}
}
/**
* Description of a file in an OTA package zip file.
*/
public static class InnerFile implements Serializable {
private static final long serialVersionUID = 31043L;
/** filename in an archive */
private String mFilename;
/** defines beginning of update data in archive */ /** defines beginning of update data in archive */
private long mOffset; private long mOffset;
/** size of the update data in archive */ /** size of the update data in archive */
private long mSize; private long mSize;
public Metadata(long offset, long size) { public InnerFile(String filename, long offset, long size) {
this.mFilename = filename;
this.mOffset = offset; this.mOffset = offset;
this.mSize = size; this.mSize = size;
} }
public String getFilename() {
return mFilename;
}
public long getOffset() { public long getOffset() {
return mOffset; return mOffset;
} }
@@ -178,6 +224,7 @@ public class UpdateConfig implements Parcelable {
public long getSize() { public long getSize() {
return mSize; return mSize;
} }
} }
} }
@@ -260,7 +260,7 @@ public class MainActivity extends Activity {
* Applies the given update * Applies the given update
*/ */
private void applyUpdate(UpdateConfig config) { private void applyUpdate(UpdateConfig config) {
if (config.getInstallType() == UpdateConfig.TYPE_NON_STREAMING) { if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
AbNonStreamingUpdate update = new AbNonStreamingUpdate(mUpdateEngine, config); AbNonStreamingUpdate update = new AbNonStreamingUpdate(mUpdateEngine, config);
try { try {
update.execute(); update.execute();
@@ -17,6 +17,7 @@
package com.example.android.systemupdatersample.util; package com.example.android.systemupdatersample.util;
import android.content.Context; import android.content.Context;
import android.util.Log;
import com.example.android.systemupdatersample.UpdateConfig; import com.example.android.systemupdatersample.UpdateConfig;
@@ -70,6 +71,7 @@ public final class UpdateConfigs {
StandardCharsets.UTF_8); StandardCharsets.UTF_8);
configs.add(UpdateConfig.fromJson(json)); configs.add(UpdateConfig.fromJson(json));
} catch (Exception e) { } catch (Exception e) {
Log.e("UpdateConfigs", "Can't read/parse config file " + f.getName(), e);
throw new RuntimeException( throw new RuntimeException(
"Can't read/parse config file " + f.getName(), e); "Can't read/parse config file " + f.getName(), e);
} }
+6 -2
View File
@@ -22,11 +22,15 @@ LOCAL_SDK_VERSION := system_current
LOCAL_MODULE_TAGS := tests LOCAL_MODULE_TAGS := tests
LOCAL_JAVA_LIBRARIES := \ LOCAL_JAVA_LIBRARIES := \
android.test.base.stubs \ android.test.base.stubs \
android.test.runner.stubs android.test.runner.stubs \
guava \
mockito-target-minus-junit4
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_INSTRUMENTATION_FOR := SystemUpdaterSample LOCAL_INSTRUMENTATION_FOR := SystemUpdaterSample
LOCAL_PROGUARD_ENABLED := disabled LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_SRC_FILES := $(call all-java-files-under, src)
include $(BUILD_PACKAGE) include $(BUILD_PACKAGE)
+2
View File
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.systemupdatersample.tests"> package="com.example.android.systemupdatersample.tests">
<uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27" />
<!-- We add an application tag here just so that we can indicate that <!-- We add an application tag here just so that we can indicate that
this package needs to link against the android.test library, this package needs to link against the android.test library,
which is needed when building test cases. --> which is needed when building test cases. -->
@@ -1,8 +1,8 @@
{ {
"name": "streaming-001", "name": "streaming-001",
"url": "http://foo.bar/update.zip", "url": "http://foo.bar/update.zip",
"type": "STREAMING", "ab_install_type": "STREAMING",
"streaming_metadata": { "ab_streaming_metadata": {
"property_files": [ "property_files": [
{ {
"filename": "payload.bin", "filename": "payload.bin",
@@ -19,14 +19,23 @@ package com.example.android.systemupdatersample;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import com.example.android.systemupdatersample.tests.R;
import com.google.common.io.CharStreams;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.io.IOException;
import java.io.InputStreamReader;
/** /**
* Tests for {@link UpdateConfig} * Tests for {@link UpdateConfig}
*/ */
@@ -36,27 +45,48 @@ public class UpdateConfigTest {
private static final String JSON_NON_STREAMING = private static final String JSON_NON_STREAMING =
"{\"name\": \"vip update\", \"url\": \"file:///builds/a.zip\", " "{\"name\": \"vip update\", \"url\": \"file:///builds/a.zip\", "
+ " \"type\": \"NON_STREAMING\"}"; + " \"ab_install_type\": \"NON_STREAMING\"}";
private static final String JSON_STREAMING =
"{\"name\": \"vip update 2\", \"url\": \"http://foo.bar/a.zip\", "
+ "\"type\": \"STREAMING\"}";
@Rule @Rule
public final ExpectedException thrown = ExpectedException.none(); public final ExpectedException thrown = ExpectedException.none();
private Context mContext;
private Context mTargetContext;
private String mJsonStreaming001;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
mTargetContext = InstrumentationRegistry.getTargetContext();
mJsonStreaming001 = readResource(R.raw.update_config_stream_001);
}
@Test @Test
public void fromJson_parsesJsonConfigWithoutMetadata() throws Exception { public void fromJson_parsesNonStreaming() throws Exception {
UpdateConfig config = UpdateConfig.fromJson(JSON_NON_STREAMING); UpdateConfig config = UpdateConfig.fromJson(JSON_NON_STREAMING);
assertEquals("name is parsed", "vip update", config.getName()); assertEquals("name is parsed", "vip update", config.getName());
assertEquals("stores raw json", JSON_NON_STREAMING, config.getRawJson()); assertEquals("stores raw json", JSON_NON_STREAMING, config.getRawJson());
assertSame("type is parsed", UpdateConfig.TYPE_NON_STREAMING, config.getInstallType()); assertSame("type is parsed",
UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING,
config.getInstallType());
assertEquals("url is parsed", "file:///builds/a.zip", config.getUrl()); assertEquals("url is parsed", "file:///builds/a.zip", config.getUrl());
} }
@Test
public void fromJson_parsesStreaming() throws Exception {
UpdateConfig config = UpdateConfig.fromJson(mJsonStreaming001);
assertEquals("streaming-001", config.getName());
assertEquals("http://foo.bar/update.zip", config.getUrl());
assertSame(UpdateConfig.AB_INSTALL_TYPE_STREAMING, config.getInstallType());
assertEquals("payload.bin",
config.getStreamingMetadata().getPropertyFiles()[0].getFilename());
assertEquals(195, config.getStreamingMetadata().getPropertyFiles()[0].getOffset());
assertEquals(8, config.getStreamingMetadata().getPropertyFiles()[0].getSize());
}
@Test @Test
public void getUpdatePackageFile_throwsErrorIfStreaming() throws Exception { public void getUpdatePackageFile_throwsErrorIfStreaming() throws Exception {
UpdateConfig config = UpdateConfig.fromJson(JSON_STREAMING); UpdateConfig config = UpdateConfig.fromJson(mJsonStreaming001);
thrown.expect(RuntimeException.class); thrown.expect(RuntimeException.class);
config.getUpdatePackageFile(); config.getUpdatePackageFile();
} }
@@ -64,7 +94,7 @@ public class UpdateConfigTest {
@Test @Test
public void getUpdatePackageFile_throwsErrorIfNotAFile() throws Exception { public void getUpdatePackageFile_throwsErrorIfNotAFile() throws Exception {
String json = "{\"name\": \"upd\", \"url\": \"http://foo.bar\"," String json = "{\"name\": \"upd\", \"url\": \"http://foo.bar\","
+ " \"type\": \"NON_STREAMING\"}"; + " \"ab_install_type\": \"NON_STREAMING\"}";
UpdateConfig config = UpdateConfig.fromJson(json); UpdateConfig config = UpdateConfig.fromJson(json);
thrown.expect(RuntimeException.class); thrown.expect(RuntimeException.class);
config.getUpdatePackageFile(); config.getUpdatePackageFile();
@@ -73,7 +103,11 @@ public class UpdateConfigTest {
@Test @Test
public void getUpdatePackageFile_works() throws Exception { public void getUpdatePackageFile_works() throws Exception {
UpdateConfig c = UpdateConfig.fromJson(JSON_NON_STREAMING); UpdateConfig c = UpdateConfig.fromJson(JSON_NON_STREAMING);
assertEquals("correct path", "/builds/a.zip", c.getUpdatePackageFile().getAbsolutePath()); assertEquals("/builds/a.zip", c.getUpdatePackageFile().getAbsolutePath());
} }
private String readResource(int id) throws IOException {
return CharStreams.toString(new InputStreamReader(
mContext.getResources().openRawResource(id)));
}
} }
@@ -54,8 +54,8 @@ public class UpdateConfigsTest {
@Test @Test
public void configsToNames_extractsNames() { public void configsToNames_extractsNames() {
List<UpdateConfig> configs = Arrays.asList( List<UpdateConfig> configs = Arrays.asList(
new UpdateConfig("blah", "http://", UpdateConfig.TYPE_NON_STREAMING), new UpdateConfig("blah", "http://", UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING),
new UpdateConfig("blah 2", "http://", UpdateConfig.TYPE_STREAMING) new UpdateConfig("blah 2", "http://", UpdateConfig.AB_INSTALL_TYPE_STREAMING)
); );
String[] names = UpdateConfigs.configsToNames(configs); String[] names = UpdateConfigs.configsToNames(configs);
assertArrayEquals(new String[] {"blah", "blah 2"}, names); assertArrayEquals(new String[] {"blah", "blah 2"}, names);