report acquisition status

This commit is contained in:
Geoffrey Goh
2016-01-15 15:15:41 -08:00
parent 4b7e62493c
commit 51c40c9652
8 changed files with 347 additions and 45 deletions

View File

@@ -23,7 +23,9 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.provider.Settings;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -43,17 +45,24 @@ public class CodePush {
private String assetsBundleFileName;
private final String ASSETS_BUNDLE_PREFIX = "assets://";
private final String BINARY_MODIFIED_TIME_KEY = "binaryModifiedTime";
private final String CODE_PUSH_PREFERENCES = "CodePush";
private final String DEPLOYMENT_FAILED_STATUS = "DeploymentFailed";
private final String DEPLOYMENT_KEY_KEY = "deploymentKey";
private final String DEPLOYMENT_SUCCEEDED_STATUS = "DeploymentSucceeded";
private final String DOWNLOAD_PROGRESS_EVENT_NAME = "CodePushDownloadProgress";
private final String FAILED_UPDATES_KEY = "CODE_PUSH_FAILED_UPDATES";
private final String PENDING_UPDATE_KEY = "CODE_PUSH_PENDING_UPDATE";
private final String LABEL_KEY = "label";
private final String PACKAGE_HASH_KEY = "packageHash";
private final String PENDING_UPDATE_HASH_KEY = "hash";
private final String PENDING_UPDATE_IS_LOADING_KEY = "isLoading";
private final String ASSETS_BUNDLE_PREFIX = "assets://";
private final String CODE_PUSH_PREFERENCES = "CodePush";
private final String DOWNLOAD_PROGRESS_EVENT_NAME = "CodePushDownloadProgress";
private final String PENDING_UPDATE_KEY = "CODE_PUSH_PENDING_UPDATE";
private final String RESOURCES_BUNDLE = "resources.arsc";
private final String STATUS_REPORTS_KEY = "CODE_PUSH_STATUS_REPORTS";
// This needs to be kept in sync with https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java#L78
private final String REACT_DEV_BUNDLE_CACHE_FILE_NAME = "ReactNativeDevBundle.js";
private final String BINARY_MODIFIED_TIME_KEY = "binaryModifiedTime";
private CodePushPackage codePushPackage;
private CodePushReactPackage codePushReactPackage;
@@ -145,7 +154,7 @@ public class CodePush {
String pacakgeAppVersion = CodePushUtils.tryGetString(packageMetadata, "appVersion");
if (binaryModifiedDateDuringPackageInstall != null &&
binaryModifiedDateDuringPackageInstall == binaryResourcesModifiedTime &&
this.appVersion.equals(pacakgeAppVersion)) {
(this.isUsingTestConfiguration() || this.appVersion.equals(pacakgeAppVersion))) {
CodePushUtils.logBundleUrl(packageFilePath);
return packageFilePath;
} else {
@@ -165,6 +174,36 @@ public class CodePush {
}
}
private String getPackageStatusReportIdentifier(WritableMap updatePackage) {
// Because deploymentKeys can be dynamically switched, we use a
// combination of the deploymentKey and label as the packageIdentifier.
String deploymentKey = CodePushUtils.tryGetString(updatePackage, DEPLOYMENT_KEY_KEY);
String label = CodePushUtils.tryGetString(updatePackage, LABEL_KEY);
if (deploymentKey != null && label != null) {
return deploymentKey + ":" + label;
} else {
return null;
}
}
private JSONArray getFailedUpdates() {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null);
if (failedUpdatesString == null) {
return new JSONArray();
}
try {
JSONArray failedUpdates = new JSONArray(failedUpdatesString);
return failedUpdates;
} catch (JSONException e) {
// Unrecognized data format, clear and replace with expected format.
JSONArray emptyArray = new JSONArray();
settings.edit().putString(FAILED_UPDATES_KEY, emptyArray.toString()).commit();
return emptyArray;
}
}
private JSONObject getPendingUpdate() {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null);
@@ -217,22 +256,39 @@ public class CodePush {
}
}
}
private boolean isFailedHash(String packageHash) {
private boolean isDeploymentStatusNotYetReported(String appVersionOrPackageIdentifier) {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null);
if (failedUpdatesString == null) {
return false;
String sentStatusReportsString = settings.getString(STATUS_REPORTS_KEY, null);
if (sentStatusReportsString == null) {
return true;
} else {
try {
JSONObject sentStatusReports = new JSONObject(sentStatusReportsString);
return !sentStatusReports.has(appVersionOrPackageIdentifier);
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to parse sent status reports information " +
sentStatusReportsString + " stored in SharedPreferences.", e);
}
}
}
private boolean isFailedHash(String packageHash) {
JSONArray failedUpdates = getFailedUpdates();
for (int i = 0; i < failedUpdates.length(); i++) {
JSONObject failedPackage = null;
try {
failedPackage = failedUpdates.getJSONObject(i);
String failedPackageHash = failedPackage.getString(PACKAGE_HASH_KEY);
if (packageHash.equals(failedPackageHash)) {
return true;
}
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to read failedUpdates data stored in SharedPreferences.", e);
}
}
try {
JSONObject failedUpdates = new JSONObject(failedUpdatesString);
return failedUpdates.has(packageHash);
} catch (JSONException e) {
// Should not happen.
throw new CodePushUnknownException("Unable to parse failed updates information " +
failedUpdatesString + " stored in SharedPreferences", e);
}
return false;
}
private boolean isPendingUpdate(String packageHash) {
@@ -249,6 +305,24 @@ public class CodePush {
}
}
private void recordDeploymentStatusReported(String appVersionOrPackageIdentifier, String status) {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
String sentStatusReportsString = settings.getString(STATUS_REPORTS_KEY, null);
JSONObject sentStatusReports;
try {
if (sentStatusReportsString == null) {
sentStatusReports = new JSONObject();
} else {
sentStatusReports = new JSONObject(sentStatusReportsString);
}
sentStatusReports.put(appVersionOrPackageIdentifier, status);
settings.edit().putString(STATUS_REPORTS_KEY, sentStatusReports.toString()).commit();
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to save new entry in SharedPreferences under " + STATUS_REPORTS_KEY + ".", e);
}
}
private void removeFailedUpdates() {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
settings.edit().remove(FAILED_UPDATES_KEY).commit();
@@ -261,8 +335,8 @@ public class CodePush {
private void rollbackPackage() {
try {
String packageHash = codePushPackage.getCurrentPackageHash();
saveFailedUpdate(packageHash);
WritableMap failedPackage = codePushPackage.getCurrentPackage();
saveFailedUpdate(failedPackage);
} catch (IOException e) {
throw new CodePushUnknownException("Attempted a rollback without having a current downloaded package", e);
}
@@ -276,29 +350,25 @@ public class CodePush {
removePendingUpdate();
}
private void saveFailedUpdate(String packageHash) {
private void saveFailedUpdate(WritableMap failedPackage) {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null);
JSONObject failedUpdates;
JSONArray failedUpdates;
if (failedUpdatesString == null) {
failedUpdates = new JSONObject();
failedUpdates = new JSONArray();
} else {
try {
failedUpdates = new JSONObject(failedUpdatesString);
failedUpdates = new JSONArray(failedUpdatesString);
} catch (JSONException e) {
// Should not happen.
throw new CodePushMalformedDataException("Unable to parse failed updates information " +
failedUpdatesString + " stored in SharedPreferences", e);
}
}
try {
failedUpdates.put(packageHash, true);
settings.edit().putString(FAILED_UPDATES_KEY, failedUpdates.toString()).commit();
} catch (JSONException e) {
// Should not happen unless the packageHash is null.
throw new CodePushUnknownException("Unable to save package hash " +
packageHash + " as a failed update", e);
}
JSONObject failedPackageJSON = CodePushUtils.convertReadableToJsonObject(failedPackage);
failedUpdates.put(failedPackageJSON);
settings.edit().putString(FAILED_UPDATES_KEY, failedUpdates.toString()).commit();
}
private void savePendingUpdate(String packageHash, boolean isLoading) {
@@ -377,6 +447,9 @@ public class CodePush {
configMap.putInt("buildVersion", buildVersion);
configMap.putString("deploymentKey", deploymentKey);
configMap.putString("serverUrl", serverUrl);
configMap.putString("clientUniqueId",
Settings.Secure.getString(mainActivity.getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID));
promise.resolve(configMap);
}
@@ -408,7 +481,60 @@ public class CodePush {
asyncTask.execute();
}
@ReactMethod
public void getNewStatusReport(Promise promise) {
// Check if the current appVersion has been reported.
if (isDeploymentStatusNotYetReported(appVersion)) {
recordDeploymentStatusReported(appVersion, DEPLOYMENT_SUCCEEDED_STATUS);
WritableNativeMap reportMap = new WritableNativeMap();
reportMap.putString("appVersion", appVersion);
promise.resolve(reportMap);
return;
}
// Check if there was a rollback that was not yet reported
JSONArray failedUpdates = getFailedUpdates();
if (failedUpdates != null && failedUpdates.length() > 0) {
try {
JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1);
WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON);
String lastFailedPackageIdentifier = getPackageStatusReportIdentifier(lastFailedPackage);
if (lastFailedPackage != null && isDeploymentStatusNotYetReported(lastFailedPackageIdentifier)) {
recordDeploymentStatusReported(lastFailedPackageIdentifier, DEPLOYMENT_FAILED_STATUS);
WritableNativeMap reportMap = new WritableNativeMap();
reportMap.putMap("package", lastFailedPackage);
reportMap.putString("status", DEPLOYMENT_FAILED_STATUS);
promise.resolve(reportMap);
return;
}
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e);
}
}
// Check if the current CodePush package has been reported
try {
WritableMap currentPackage = codePushPackage.getCurrentPackage();
if (currentPackage != null) {
String currentPackageIdentifier = getPackageStatusReportIdentifier(currentPackage);
if (currentPackageIdentifier != null && isDeploymentStatusNotYetReported(currentPackageIdentifier)) {
recordDeploymentStatusReported(currentPackageIdentifier, DEPLOYMENT_SUCCEEDED_STATUS);
WritableNativeMap reportMap = new WritableNativeMap();
reportMap.putMap("package", currentPackage);
reportMap.putString("status", DEPLOYMENT_SUCCEEDED_STATUS);
promise.resolve(reportMap);
return;
}
}
} catch (IOException e) {
// No current package, resolve no report.
promise.resolve("");
}
promise.resolve("");
}
@ReactMethod
public void installUpdate(final ReadableMap updatePackage, final int installMode, final Promise promise) {
AsyncTask asyncTask = new AsyncTask() {