diff --git a/Examples/CodePushDemoApp/android/app/build.gradle b/Examples/CodePushDemoApp/android/app/build.gradle index 19f6777..6170451 100644 --- a/Examples/CodePushDemoApp/android/app/build.gradle +++ b/Examples/CodePushDemoApp/android/app/build.gradle @@ -61,8 +61,7 @@ apply from: "react.gradle" apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" /** - * Set this to true to create three separate APKs instead of one: - * - A universal APK that works on all devices + * Set this to true to create two separate APKs instead of one: * - An APK that only works on ARM devices * - An APK that only works on x86 devices * The advantage is the size of the APK is reduced by about 4MB. @@ -93,7 +92,7 @@ android { splits { abi { enable enableSeparateBuildPerCPUArchitecture - universalApk true + universalApk false // Also generate an universal APK reset() include "armeabi-v7a", "x86" } diff --git a/Examples/CodePushDemoApp/crossplatformdemo.js b/Examples/CodePushDemoApp/crossplatformdemo.js index ff7de19..368b7e3 100644 --- a/Examples/CodePushDemoApp/crossplatformdemo.js +++ b/Examples/CodePushDemoApp/crossplatformdemo.js @@ -57,7 +57,7 @@ let CodePushDemoApp = React.createClass({ break; case CodePush.SyncStatus.UPDATE_INSTALLED: self.setState({ - syncMessage: "Update installed and will be run when the app next resumes.", + syncMessage: "Update installed.", progress: false }); break; diff --git a/RestartManager.js b/RestartManager.js index a34fffe..00bde29 100644 --- a/RestartManager.js +++ b/RestartManager.js @@ -3,44 +3,57 @@ const NativeCodePush = require("react-native").NativeModules.CodePush; const RestartManager = (() => { let _allowed = true; - let _restartPending = false; + let _restartInProgress = false; + let _restartQueue = []; function allow() { log("Re-allowing restarts"); _allowed = true; - - if (_restartPending) { + + if (_restartQueue.length) { log("Executing pending restart"); - restartApp(true); + restartApp(_restartQueue.shift(1)); } } - + function clearPendingRestart() { - _restartPending = false; + _restartQueue = []; } - + function disallow() { log("Disallowing restarts"); _allowed = false; } - function restartApp(onlyIfUpdateIsPending = false) { - if (_allowed) { - NativeCodePush.restartApp(onlyIfUpdateIsPending); - log("Restarting app"); - } else { + async function restartApp(onlyIfUpdateIsPending = false) { + if (_restartInProgress) { + log("Restart request queued until the current restart is completed"); + _restartQueue.push(onlyIfUpdateIsPending); + } else if (!_allowed) { log("Restart request queued until restarts are re-allowed"); - _restartPending = true; - return true; + _restartQueue.push(onlyIfUpdateIsPending); + } else { + _restartInProgress = true; + if (await NativeCodePush.restartApp(onlyIfUpdateIsPending)) { + // The app has already restarted, so there is no need to + // process the remaining queued restarts. + log("Restarting app"); + return; + } + + _restartInProgress = false; + if (_restartQueue.length) { + restartApp(_restartQueue.shift(1)); + } } } return { allow, - clearPendingRestart, + clearPendingRestart, disallow, restartApp }; })(); -module.exports = RestartManager; \ No newline at end of file +module.exports = RestartManager; diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java index 761a94c..8a88db1 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java @@ -449,12 +449,16 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule { } @ReactMethod - public void restartApp(boolean onlyIfUpdateIsPending) { + public void restartApp(boolean onlyIfUpdateIsPending, Promise promise) { // If this is an unconditional restart request, or there // is current pending update, then reload the app. if (!onlyIfUpdateIsPending || mSettingsManager.isPendingUpdate(null)) { loadBundle(); + promise.resolve(true); + return; } + + promise.resolve(false); } @ReactMethod diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 0acfd26..3931cc1 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -740,13 +740,19 @@ RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve /* * This method is the native side of the CodePush.restartApp() method. */ -RCT_EXPORT_METHOD(restartApp:(BOOL)onlyIfUpdateIsPending) +RCT_EXPORT_METHOD(restartApp:(BOOL)onlyIfUpdateIsPending + resolve:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { // If this is an unconditional restart request, or there // is current pending update, then reload the app. if (!onlyIfUpdateIsPending || [self isPendingUpdate:nil]) { [self loadBundle]; + resolve(@(YES)); + return; } + + resolve(@(NO)); } #pragma mark - JavaScript-exported module methods (Private) diff --git a/test/template/scenarios/scenarioInstallRestart2x.js b/test/template/scenarios/scenarioInstallRestart2x.js new file mode 100644 index 0000000..bd78f68 --- /dev/null +++ b/test/template/scenarios/scenarioInstallRestart2x.js @@ -0,0 +1,17 @@ +var CodePushWrapper = require("../codePushWrapper.js"); +import CodePush from "react-native-code-push"; + +module.exports = { + startTest: function(testApp) { + CodePushWrapper.checkAndInstall(testApp, + () => { + CodePush.restartApp(); + CodePush.restartApp(); + } + ); + }, + + getScenarioName: function() { + return "Install and Restart 2x"; + } +}; \ No newline at end of file diff --git a/test/template/scenarios/scenarioRestart2x.js b/test/template/scenarios/scenarioRestart2x.js new file mode 100644 index 0000000..11e775b --- /dev/null +++ b/test/template/scenarios/scenarioRestart2x.js @@ -0,0 +1,17 @@ +var CodePushWrapper = require("../codePushWrapper.js"); +import CodePush from "react-native-code-push"; + +module.exports = { + startTest: function(testApp) { + CodePush.restartApp(true); + CodePushWrapper.checkAndInstall(testApp, + () => { + CodePush.restartApp(true); + } + ); + }, + + getScenarioName: function() { + return "Restart2x"; + } +}; \ No newline at end of file diff --git a/test/test.ts b/test/test.ts index 6d90225..9399266 100644 --- a/test/test.ts +++ b/test/test.ts @@ -465,12 +465,14 @@ const ScenarioInstall = "scenarioInstall.js"; const ScenarioInstallOnResumeWithRevert = "scenarioInstallOnResumeWithRevert.js"; const ScenarioInstallOnRestartWithRevert = "scenarioInstallOnRestartWithRevert.js"; const ScenarioInstallWithRevert = "scenarioInstallWithRevert.js"; +const ScenarioInstallRestart2x = "scenarioInstallRestart2x.js"; const ScenarioSync1x = "scenarioSync.js"; const ScenarioSyncResume = "scenarioSyncResume.js"; const ScenarioSyncResumeDelay = "scenarioSyncResumeDelay.js"; const ScenarioSyncRestartDelay = "scenarioSyncResumeDelay.js"; const ScenarioSync2x = "scenarioSync2x.js"; const ScenarioRestart = "scenarioRestart.js"; +const ScenarioRestart2x = "scenarioRestart2x.js"; const ScenarioSyncMandatoryDefault = "scenarioSyncMandatoryDefault.js"; const ScenarioSyncMandatoryResume = "scenarioSyncMandatoryResume.js"; const ScenarioSyncMandatoryRestart = "scenarioSyncMandatoryRestart.js"; @@ -986,6 +988,43 @@ PluginTestingFramework.initializeTests(new RNProjectManager(), supportedTargetPl }); }, ScenarioRestart); + TestBuilder.describe("#codePush.restartApplication.2x", + () => { + TestBuilder.it("blocks when a restart is in progress and doesn't crash if there is a pending package", false, + (done: MochaDone) => { + ServerUtil.updateResponse = { updateInfo: ServerUtil.createUpdateResponse(false, targetPlatform) }; + setupTestRunScenario(projectManager, targetPlatform, ScenarioInstallRestart2x) + .then(setupUpdateScenario.bind(this, projectManager, targetPlatform, UpdateDeviceReady, "Update 1")) + .then((updatePath: string) => { + ServerUtil.updatePackagePath = updatePath; + projectManager.runApplication(TestConfig.testRunDirectory, targetPlatform); + return ServerUtil.expectTestMessages([ + ServerUtil.TestMessage.CHECK_UPDATE_AVAILABLE, + ServerUtil.TestMessage.DOWNLOAD_SUCCEEDED, + ServerUtil.TestMessage.UPDATE_INSTALLED, + ServerUtil.TestMessage.DEVICE_READY_AFTER_UPDATE]); + }) + .done(() => { done(); }, (e) => { done(e); }); + }); + + TestBuilder.it("doesn't block when the restart is ignored", false, + (done: MochaDone) => { + ServerUtil.updateResponse = { updateInfo: ServerUtil.createUpdateResponse(false, targetPlatform) }; + setupTestRunScenario(projectManager, targetPlatform, ScenarioRestart2x) + .then(setupUpdateScenario.bind(this, projectManager, targetPlatform, UpdateDeviceReady, "Update 1")) + .then((updatePath: string) => { + ServerUtil.updatePackagePath = updatePath; + projectManager.runApplication(TestConfig.testRunDirectory, targetPlatform); + return ServerUtil.expectTestMessages([ + ServerUtil.TestMessage.CHECK_UPDATE_AVAILABLE, + ServerUtil.TestMessage.DOWNLOAD_SUCCEEDED, + ServerUtil.TestMessage.UPDATE_INSTALLED, + ServerUtil.TestMessage.DEVICE_READY_AFTER_UPDATE]); + }) + .done(() => { done(); }, (e) => { done(e); }); + }); + }); + TestBuilder.describe("#window.codePush.sync", () => { // We test the functionality with sync twice--first, with sync only called once,