diff --git a/CodePush.m b/CodePush.m index 00cbfad..1c5292e 100644 --- a/CodePush.m +++ b/CodePush.m @@ -6,9 +6,7 @@ #import "CodePush.h" -@implementation CodePush { - BOOL _resumablePendingUpdateAvailable; -} +@implementation CodePush RCT_EXPORT_MODULE() @@ -84,55 +82,6 @@ static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout"; }); } -/* - * This method checks to see whether a "pending update" has been applied - * (e.g. install was called with a non-immediate mode), but the app hasn't - * yet been restarted (either naturally or programmatically). If there is one, - * it will restart the app (if specified), and start the rollback timer. - * - * Note: This method is safe to call from any thread. - */ -- (void)checkForPendingUpdate:(BOOL)needsRestart -{ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; - NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; - - if (pendingUpdate) { - NSError *error; - NSString *pendingHash = pendingUpdate[PendingUpdateHashKey]; - NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error]; - - NSAssert([pendingHash isEqualToString:currentHash], @"There is a pending update but it's hash doesn't match that of the current package."); - - // Kick off the rollback timer and ensure that the necessary state is setup for the pending update. - int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue]; - [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart]; - - // Clear the pending update and sync - [preferences removeObjectForKey:PendingUpdateKey]; - [preferences synchronize]; - } - }); -} - -/* - * This method is meant as a handler for the global app - * resume notification, and therefore, should not be called - * directly. It simply checks to see whether there is a pending - * update that is meant to be installed on resume, and if so - * it applies it and restarts the app. - */ -- (void)checkForPendingUpdateDuringResume -{ - // In order to ensure that CodePush doesn't impact the app's - // resume experience, we're using a simple boolean check to - // check whether we need to restart, before reading the defaults store - if (_resumablePendingUpdateAvailable) { - [self checkForPendingUpdate:YES]; - } -} - /* * This method is used by the React Native bridge to allow * our plugin to expose constants to the JS-side. In our case @@ -165,19 +114,34 @@ static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout"; // Do an async check to see whether // we need to start the rollback timer // due to a pending update being installed at start - [self checkForPendingUpdate:NO]; - - // Register for app resume notifications so that we - // can check for pending updates which support "restart on resume" - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(checkForPendingUpdateDuringResume) - name:UIApplicationWillEnterForegroundNotification - object:[UIApplication sharedApplication]]; + [self handleInitIfPendingUpdate]; } return self; } + +- (void)handleInitIfPendingUpdate +{ + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; + + if (pendingUpdate) { + didUpdate = true; + int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue]; + if (0 != rollbackTimeout) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self startRollbackTimer:rollbackTimeout]; + }); + } + + // Clear the pending update and sync + [preferences removeObjectForKey:PendingUpdateKey]; + [preferences synchronize]; + } +} + + /* * This method performs the actual initialization work for an update * to ensure that the necessary state is setup, including: @@ -390,10 +354,15 @@ RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage if (error) { reject(error); } else { - if (installMode != CodePushInstallModeImmediate) { - _resumablePendingUpdateAvailable = (installMode == CodePushInstallModeOnNextResume); - [self savePendingUpdate:updatePackage[@"packageHash"] - rollbackTimeout:rollbackTimeout]; + if (installMode == CodePushInstallModeImmediate) { + [self restartPendingUpdate]; + } else if (installMode == CodePushInstallModeOnNextResume) { + // Register for app resume notifications so that we + // can check for pending updates which support "restart on resume" + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(restartPendingUpdate) + name:UIApplicationWillEnterForegroundNotification + object:[UIApplication sharedApplication]]; } // Signal to JS that the update has been applied. resolve(nil); @@ -446,7 +415,7 @@ RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve */ RCT_EXPORT_METHOD(restartImmediateUpdate:(int)rollbackTimeout) { - [self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:YES]; + [self loadBundle]; } /* @@ -454,7 +423,20 @@ RCT_EXPORT_METHOD(restartImmediateUpdate:(int)rollbackTimeout) */ RCT_EXPORT_METHOD(restartPendingUpdate) { - [self checkForPendingUpdate:YES]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; + NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; + + if (pendingUpdate) { + NSError *error; + NSString *pendingHash = pendingUpdate[PendingUpdateHashKey]; + NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error]; + + NSAssert([pendingHash isEqualToString:currentHash], @"There is a pending update but it's hash doesn't match that of the current package."); + + [self loadBundle]; + } + }); } RCT_EXPORT_METHOD(setUsingTestFolder:(BOOL)shouldUseTestFolder) diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 1903267..6a2ec7f 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -41,7 +41,6 @@ import java.util.zip.ZipFile; public class CodePush { - private boolean resumablePendingUpdateAvailable = false; private boolean didUpdate = false; private Timer timer; private boolean usingTestFolder = false; @@ -87,7 +86,7 @@ public class CodePush { throw new CodePushUnknownException("Unable to get package info for " + applicationContext.getPackageName(), e); } - checkForPendingUpdate(/*needsRestart*/ false); + handleInitIfPendingUpdate(); } public ReactPackage getReactPackage() { @@ -144,52 +143,6 @@ public class CodePush { } } - private void checkForPendingUpdate(boolean needsRestart) { - SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); - String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null); - - if (pendingUpdateString != null) { - try { - JSONObject pendingUpdateJSON = new JSONObject(pendingUpdateString); - String pendingHash = pendingUpdateJSON.getString(PENDING_UPDATE_HASH_KEY); - String currentHash = codePushPackage.getCurrentPackageHash(); - if (!pendingHash.equals(currentHash)) { - throw new CodePushUnknownException("Pending hash " + pendingHash + - " and current hash " + currentHash + " are different"); - } - - int rollbackTimeout = pendingUpdateJSON.getInt(PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY); - initializeUpdateWithRollbackTimeout(rollbackTimeout, needsRestart); - settings.edit().remove(PENDING_UPDATE_KEY).commit(); - } catch (JSONException e) { - // Should not happen. - throw new CodePushUnknownException("Unable to parse pending update metadata " + - pendingUpdateString + " stored in SharedPreferences", e); - } catch (IOException e) { - // There is no current package hash. - throw new CodePushUnknownException("Should not register a pending update without a saving a current package", e); - } - } - } - - private void checkForPendingUpdateDuringResume() { - if (resumablePendingUpdateAvailable) { - checkForPendingUpdate(/*needsRestart*/ true); - } - } - - private void initializeUpdateWithRollbackTimeout(int rollbackTimeout, boolean needsRestart) { - didUpdate = true; - - if (needsRestart) { - codePushNativeModule.loadBundle(); - } - - if (0 != rollbackTimeout) { - startRollbackTimer(rollbackTimeout); - } - } - private boolean isFailedHash(String packageHash) { SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null); @@ -277,6 +230,28 @@ public class CodePush { }, timeout); } + private void handleInitIfPendingUpdate() { + SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); + String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null); + + if (pendingUpdateString != null) { + try { + didUpdate = true; + JSONObject pendingUpdateJSON = new JSONObject(pendingUpdateString); + int rollbackTimeout = pendingUpdateJSON.getInt(PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY); + if (0 != rollbackTimeout) { + startRollbackTimer(rollbackTimeout); + } + + settings.edit().remove(PENDING_UPDATE_KEY).commit(); + } catch (JSONException e) { + // Should not happen. + throw new CodePushUnknownException("Unable to parse pending update metadata " + + pendingUpdateString + " stored in SharedPreferences", e); + } + } + } + private class CodePushNativeModule extends ReactContextBaseJavaModule { private void loadBundle() { @@ -289,15 +264,33 @@ public class CodePush { public void installUpdate(ReadableMap updatePackage, int rollbackTimeout, int installMode, Promise promise) { try { codePushPackage.installPackage(updatePackage); - if (installMode != CodePushInstallMode.IMMEDIATE.getValue()) { - resumablePendingUpdateAvailable = installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue(); - String pendingHash = CodePushUtils.tryGetString(updatePackage, codePushPackage.PACKAGE_HASH_KEY); - if (pendingHash == null) { - throw new CodePushUnknownException("Update package to be installed has no hash."); - } else { - savePendingUpdate(pendingHash, rollbackTimeout); - } + + String pendingHash = CodePushUtils.tryGetString(updatePackage, codePushPackage.PACKAGE_HASH_KEY); + if (pendingHash == null) { + throw new CodePushUnknownException("Update package to be installed has no hash."); + } else { + savePendingUpdate(pendingHash, rollbackTimeout); } + + if (installMode != CodePushInstallMode.IMMEDIATE.getValue()) { + restartPendingUpdate(); + } else if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue()) { + getReactApplicationContext().addLifecycleEventListener(new LifecycleEventListener() { + @Override + public void onHostResume() { + restartPendingUpdate(); + } + + @Override + public void onHostPause() { + } + + @Override + public void onHostDestroy() { + } + }); + } + promise.resolve(""); } catch (IOException e) { e.printStackTrace(); @@ -377,12 +370,34 @@ public class CodePush { @ReactMethod public void restartImmediateUpdate(int rollbackTimeout) { - initializeUpdateWithRollbackTimeout(rollbackTimeout, /*needsRestart*/ true); + loadBundle(); } @ReactMethod public void restartPendingUpdate() { - checkForPendingUpdate(/*needsRestart*/ true); + SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); + String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null); + + if (pendingUpdateString != null) { + try { + JSONObject pendingUpdateJSON = new JSONObject(pendingUpdateString); + String pendingHash = pendingUpdateJSON.getString(PENDING_UPDATE_HASH_KEY); + String currentHash = codePushPackage.getCurrentPackageHash(); + if (!pendingHash.equals(currentHash)) { + throw new CodePushUnknownException("Pending hash " + pendingHash + + " and current hash " + currentHash + " are different"); + } + + loadBundle(); + } catch (JSONException e) { + // Should not happen. + throw new CodePushUnknownException("Unable to parse pending update metadata " + + pendingUpdateString + " stored in SharedPreferences", e); + } catch (IOException e) { + // There is no current package hash. + throw new CodePushUnknownException("Should not register a pending update without a saving a current package", e); + } + } } @Override @@ -414,21 +429,6 @@ public class CodePush { nativeModules.add(CodePush.this.codePushNativeModule); nativeModules.add(dialogModule); - reactApplicationContext.addLifecycleEventListener(new LifecycleEventListener() { - @Override - public void onHostResume() { - CodePush.this.checkForPendingUpdateDuringResume(); - } - - @Override - public void onHostPause() { - } - - @Override - public void onHostDestroy() { - } - }); - return nativeModules; }