Skip to content

Commit

Permalink
installModes: implement new ON_NEXT_SUSPEND install mode (microsoft#770)
Browse files Browse the repository at this point in the history
Restart the app _while_ it is in the background, but only after it has been in the background for "minimumBackgroundDuration" seconds (0 by default), so that user context isn't lost unless the app suspension is long enough to not matter.
  • Loading branch information
sergey-akhalkov authored Apr 11, 2017
1 parent 068a9b8 commit 54351db
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 6 deletions.
5 changes: 4 additions & 1 deletion CodePush.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,10 @@ if (NativeCodePush) {
InstallMode: {
IMMEDIATE: NativeCodePush.codePushInstallModeImmediate, // Restart the app immediately
ON_NEXT_RESTART: NativeCodePush.codePushInstallModeOnNextRestart, // Don't artificially restart the app. Allow the update to be "picked up" on the next app restart
ON_NEXT_RESUME: NativeCodePush.codePushInstallModeOnNextResume // Restart the app the next time it is resumed from the background
ON_NEXT_RESUME: NativeCodePush.codePushInstallModeOnNextResume, // Restart the app the next time it is resumed from the background
ON_NEXT_SUSPEND: NativeCodePush.codePushInstallModeOnNextSuspend // Restart the app _while_ it is in the background,
// but only after it has been in the background for "minimumBackgroundDuration" seconds (0 by default),
// so that user context isn't lost unless the app suspension is long enough to not matter
},
SyncStatus: {
UP_TO_DATE: 0, // The running app is up-to-date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
public enum CodePushInstallMode {
IMMEDIATE(0),
ON_NEXT_RESTART(1),
ON_NEXT_RESUME(2);
ON_NEXT_RESUME(2),
ON_NEXT_SUSPEND(3);

private final int value;
CodePushInstallMode(int value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public Map<String, Object> getConstants() {
constants.put("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue());
constants.put("codePushInstallModeOnNextRestart", CodePushInstallMode.ON_NEXT_RESTART.getValue());
constants.put("codePushInstallModeOnNextResume", CodePushInstallMode.ON_NEXT_RESUME.getValue());
constants.put("codePushInstallModeOnNextSuspend", CodePushInstallMode.ON_NEXT_SUSPEND.getValue());

constants.put("codePushUpdateStateRunning", CodePushUpdateState.RUNNING.getValue());
constants.put("codePushUpdateStatePending", CodePushUpdateState.PENDING.getValue());
Expand Down Expand Up @@ -438,7 +439,8 @@ protected Void doInBackground(Void... params) {
// We also add the resume listener if the installMode is IMMEDIATE, because
// if the current activity is backgrounded, we want to reload the bundle when
// it comes back into the foreground.
installMode == CodePushInstallMode.IMMEDIATE.getValue()) {
installMode == CodePushInstallMode.IMMEDIATE.getValue() ||
installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) {

// Store the minimum duration on the native module as an instance
// variable instead of relying on a closure below, so that any
Expand All @@ -449,9 +451,18 @@ protected Void doInBackground(Void... params) {
// Ensure we do not add the listener twice.
mLifecycleEventListener = new LifecycleEventListener() {
private Date lastPausedDate = null;
private Handler appSuspendHandler = new Handler(Looper.getMainLooper());
private Runnable loadBundleRunnable = new Runnable() {
@Override
public void run() {
CodePushUtils.log("Loading bundle on suspend");
loadBundle();
}
};

@Override
public void onHostResume() {
appSuspendHandler.removeCallbacks(loadBundleRunnable);
// As of RN 36, the resume handler fires immediately if the app is in
// the foreground, so explicitly wait for it to be backgrounded first
if (lastPausedDate != null) {
Expand All @@ -469,6 +480,10 @@ public void onHostPause() {
// Save the current time so that when the app is later
// resumed, we can detect how long it was in the background.
lastPausedDate = new Date();

if (installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue() && mSettingsManager.isPendingUpdate(null)) {
appSuspendHandler.postDelayed(loadBundleRunnable, minimumBackgroundDuration * 1000);
}
}

@Override
Expand Down
3 changes: 2 additions & 1 deletion ios/CodePush/CodePush.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ void CPLog(NSString *formatString, ...);
typedef NS_ENUM(NSInteger, CodePushInstallMode) {
CodePushInstallModeImmediate,
CodePushInstallModeOnNextRestart,
CodePushInstallModeOnNextResume
CodePushInstallModeOnNextResume,
CodePushInstallModeOnNextSuspend
};

typedef NS_ENUM(NSInteger, CodePushUpdateState) {
Expand Down
22 changes: 21 additions & 1 deletion ios/CodePush/CodePush.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ @implementation CodePush {
BOOL _isFirstRunAfterUpdate;
int _minimumBackgroundDuration;
NSDate *_lastResignedDate;
CodePushInstallMode *_installMode;
NSTimer *_appSuspendTimer;

// Used to coordinate the dispatching of download progress events to JS.
long long _latestExpectedContentLength;
Expand Down Expand Up @@ -292,6 +294,7 @@ - (NSDictionary *)constantsToExport
@"codePushInstallModeOnNextRestart":@(CodePushInstallModeOnNextRestart),
@"codePushInstallModeImmediate": @(CodePushInstallModeImmediate),
@"codePushInstallModeOnNextResume": @(CodePushInstallModeOnNextResume),
@"codePushInstallModeOnNextSuspend": @(CodePushInstallModeOnNextSuspend),

@"codePushUpdateStateRunning": @(CodePushUpdateStateRunning),
@"codePushUpdateStatePending": @(CodePushUpdateStatePending),
Expand Down Expand Up @@ -559,6 +562,10 @@ - (void)savePendingUpdate:(NSString *)packageHash
// a resume-based update still pending installation.
- (void)applicationWillEnterForeground
{
if (_appSuspendTimer) {
[_appSuspendTimer invalidate];
_appSuspendTimer = nil;
}
// Determine how long the app was in the background and ensure
// that it meets the minimum duration amount of time.
int durationInBackground = 0;
Expand All @@ -576,6 +583,18 @@ - (void)applicationWillResignActive
// Save the current time so that when the app is later
// resumed, we can detect how long it was in the background.
_lastResignedDate = [NSDate date];

if (_installMode == CodePushInstallModeOnNextSuspend && [[self class] isPendingUpdate:nil]) {
_appSuspendTimer = [NSTimer scheduledTimerWithTimeInterval:_minimumBackgroundDuration
target:self
selector:@selector(loadBundleOnTick:)
userInfo:nil
repeats:NO];
}
}

-(void)loadBundleOnTick:(NSTimer *)timer {
[self loadBundle];
}

#pragma mark - JavaScript-exported module methods (Public)
Expand Down Expand Up @@ -749,7 +768,8 @@ - (void)applicationWillResignActive
[self savePendingUpdate:updatePackage[PackageHashKey]
isLoading:NO];

if (installMode == CodePushInstallModeOnNextResume) {
_installMode = installMode;
if (_installMode == CodePushInstallModeOnNextResume || _installMode == CodePushInstallModeOnNextSuspend) {
_minimumBackgroundDuration = minimumBackgroundDuration;

if (!_hasResumeListener) {
Expand Down
3 changes: 2 additions & 1 deletion ios/CodePush/RCTConvert+CodePushInstallMode.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ @implementation RCTConvert (CodePushInstallMode)

RCT_ENUM_CONVERTER(CodePushInstallMode, (@{ @"codePushInstallModeImmediate": @(CodePushInstallModeImmediate),
@"codePushInstallModeOnNextRestart": @(CodePushInstallModeOnNextRestart),
@"codePushInstallModeOnNextResume": @(CodePushInstallModeOnNextResume) }),
@"codePushInstallModeOnNextResume": @(CodePushInstallModeOnNextResume),
@"codePushInstallModeOnNextSuspend": @(CodePushInstallModeOnNextSuspend) }),
CodePushInstallModeImmediate, // Default enum value
integerValue)

Expand Down

0 comments on commit 54351db

Please sign in to comment.