Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent sleep timer from running on native platforms #1786

Merged
merged 6 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ const CONST = {
TIMEZONE: 'timeZone',
},
DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'},
APP_STATE: {
ACTIVE: 'active',
BACKGROUND: 'background',
INACTIVE: 'inactive',
},

// at least 8 characters, 1 capital letter, 1 lowercase number, 1 number
PASSWORD_COMPLEXITY_REGEX_STRING: '^(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{8,}$',
Expand Down
35 changes: 35 additions & 0 deletions src/libs/AppStateMonitor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {AppState} from 'react-native';
import CONST from '../../CONST';
import shouldReportActivity from './shouldReportActivity';

let appState = CONST.APP_STATE.ACTIVE;

/**
* Listener that will only fire the callback when the user has become active.
*
* @param {Function} callback
* @returns {Function} to unsubscribe
*/
function addBecameActiveListener(callback) {
/**
* @param {String} state
*/
function appStateChangeCallback(state) {
if (
shouldReportActivity
&& (appState === CONST.APP_STATE.INACTIVE || appState === CONST.APP_STATE.BACKGROUND)
&& state === CONST.APP_STATE.ACTIVE
Comment on lines +19 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand this but basically this is only change the app state if the user has become active and the stored state is not active right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. We set up the state (assume to be active when the app inits) then listen for changes and save it locally each time so we can detect a change from one state to another.

) {
callback();
}
appState = state;
}
AppState.addEventListener('change', appStateChangeCallback);
return () => {
AppState.removeEventListener('change', appStateChangeCallback);
};
Comment on lines +27 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what is happening here? I'm not totally sure what this is doing, is it initializing with an event listener and then whenever it is returned it gets removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! So, addBecameActiveListener is both setting up an event listener and then returning a function that we can call later to remove the listener. The appStateChangeCallback exists in the closure created by calling the outermost function. It's just sort of a convenient pattern for cleaning up the listener we created and I borrowed it from how NetInfo works.

}

export default {
addBecameActiveListener,
};
4 changes: 4 additions & 0 deletions src/libs/AppStateMonitor/shouldReportActivity/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// We only need to report when the app becomes active on native since web maintains most of it's network functions while
// in the "background" and the concept is not quite the same on mobile. We avoid setting this to true for web since
// the event would fire much more frequently than it does on native causing performance issues.
export default false;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default true;
46 changes: 18 additions & 28 deletions src/libs/NetworkConnection.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import _ from 'underscore';
import {AppState} from 'react-native';
import Onyx from 'react-native-onyx';
import NetInfo from './NetInfo';
import ONYXKEYS from '../ONYXKEYS';
import SleepTimer from './SleepTimer';
import AppStateMonitor from './AppStateMonitor';

// NetInfo.addEventListener() returns a function used to unsubscribe the
// listener so we must create a reference to it and call it in stopListeningForReconnect()
let unsubscribeFromNetInfo;
let sleepTimer;
let lastTime;
let unsubscribeFromSleepTimer;
let unsubscribeFromAppState;
let isOffline = false;
let listeningForAppStateChanges = false;
let logInfo = () => {};

// Holds all of the callbacks that need to be triggered when the network reconnects
Expand Down Expand Up @@ -42,10 +42,6 @@ function setOfflineStatus(isCurrentlyOffline) {
isOffline = isCurrentlyOffline;
}

function logAppStateChange(state) {
logInfo('[NetworkConnection] AppState change event', true, {state});
}

/**
* Set up the event listener for NetInfo to tell whether the user has
* internet connectivity or not. This is more reliable than the Pusher
Expand All @@ -54,10 +50,9 @@ function logAppStateChange(state) {
function listenForReconnect() {
logInfo('[NetworkConnection] listenForReconnect called', true);

if (!listeningForAppStateChanges) {
AppState.addEventListener('change', logAppStateChange);
listeningForAppStateChanges = true;
}
unsubscribeFromAppState = AppStateMonitor.addBecameActiveListener(() => {
triggerReconnectionCallbacks('app became active');
});

// Subscribe to the state change event via NetInfo so we can update
// whether a user has internet connectivity or not.
Expand All @@ -68,34 +63,29 @@ function listenForReconnect() {

// When a device is put to sleep, NetInfo is not always able to detect
// when connectivity has been lost. As a failsafe we will capture the time
// every two seconds and if the last time recorded is greater than 4 seconds
// every two seconds and if the last time recorded goes past a threshold
// we know that the computer has been asleep.
lastTime = (new Date()).getTime();
clearInterval(sleepTimer);
sleepTimer = setInterval(() => {
const currentTime = (new Date()).getTime();
const isSkewed = currentTime > (lastTime + 4000);
if (isSkewed) {
triggerReconnectionCallbacks('sleep timer clock skewed');
}
lastTime = currentTime;
}, 2000);
unsubscribeFromSleepTimer = SleepTimer.addClockSkewListener(() => (
triggerReconnectionCallbacks('timer clock skewed')
));
}

/**
* Tear down the event listeners when we are finished with them.
*/
function stopListeningForReconnect() {
logInfo('[NetworkConnection] stopListeningForReconnect called', true);
clearInterval(sleepTimer);
sleepTimer = null;
if (unsubscribeFromNetInfo) {
unsubscribeFromNetInfo();
unsubscribeFromNetInfo = undefined;
}
if (listeningForAppStateChanges) {
AppState.removeEventListener('change', logAppStateChange);
listeningForAppStateChanges = false;
if (unsubscribeFromSleepTimer) {
unsubscribeFromSleepTimer();
unsubscribeFromSleepTimer = undefined;
}
if (unsubscribeFromAppState) {
unsubscribeFromAppState();
unsubscribeFromAppState = undefined;
}
}

Expand Down
37 changes: 37 additions & 0 deletions src/libs/SleepTimer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* The Timer module is used on web/desktop to detect when a computer has gone to sleep. We don't use this on native
* mobile since it does not work reliably and fires at inappropriate times.
*/
let sleepTimer;
let lastTime;

/**
* Adds a listener for detecting when laptop screens have closed or desktop computers put to sleep. Not reliable on
* native platforms.
*
* @param {Function} onClockSkewCallback function to call when the
* @returns {Fuction} that when called clears the timer
*/
function addClockSkewListener(onClockSkewCallback) {
clearInterval(sleepTimer);
sleepTimer = setInterval(() => {
const currentTime = (new Date()).getTime();
const isSkewed = currentTime > (lastTime + 8000);
lastTime = currentTime;

if (!isSkewed) {
return;
}

onClockSkewCallback();
}, 2000);

return () => {
clearInterval(sleepTimer);
sleepTimer = null;
};
}

export default {
addClockSkewListener,
};
8 changes: 8 additions & 0 deletions src/libs/SleepTimer/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* We don't want the clock skew listener to run on native as it only helps us on desktop/web when a laptop is closed
* and reopened. This method of detecting timing variance to see if we are inactive doesn't work well on native mobile
* platforms. These platforms should use AppState instead to determine whether they must catch up on missing data.
*/
export default {
addClockSkewListener: () => () => {},
};