Skip to content

Commit

Permalink
Merge pull request #302 from backbonelabs/fix-session-control-bug
Browse files Browse the repository at this point in the history
Tweaks on Session Statistic
  • Loading branch information
kevhuang authored May 18, 2017
2 parents e36c40c + 7375a17 commit 2019f75
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.ArrayList;

import co.backbonelabs.backbone.util.Constants;
import co.backbonelabs.backbone.util.EventEmitter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

public class MainActivity extends ReactActivity {
public static Activity currentActivity;
private NotificationService notificationService;
private Handler idleTimerHandler = new Handler();
private Runnable idleTimerRunnable = null;

Expand Down Expand Up @@ -75,6 +74,12 @@ public void onDestroy() {
Timber.d("onDestroy");
super.onDestroy();

if (idleTimerRunnable != null) {
Timber.d("Cancel idle timer");
idleTimerHandler.removeCallbacks(idleTimerRunnable);
idleTimerRunnable = null;
}

BluetoothService bluetoothService = BluetoothService.getInstance();

// Disconnect from device
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,6 @@ public void onActivityDestroyed(Activity activity) {
Intent stopIntent = new Intent(activity, ForegroundService.class);
stopIntent.setAction(Constants.ACTIONS.STOP_POSTURE_FOREGROUND_SERVICE);
activity.startService(stopIntent);

// Stop the active session
if (hasActiveSession()) {
forceStoppedSession = true;

toggleSessionOperation(Constants.SESSION_OPERATIONS.STOP, new Constants.IntCallBack() {
@Override
public void onIntCallBack(int val) {
}
});
}
}
});
}
Expand Down Expand Up @@ -175,8 +164,6 @@ public void start(ReadableMap sessionParam, final Callback callback) {
vibrationDuration = sessionParam.getInt("vibrationDuration");
}

sessionDuration *= 60; // Convert to second from minute

toggleSessionOperation(Constants.SESSION_OPERATIONS.START, new Constants.IntCallBack() {
@Override
public void onIntCallBack(int val) {
Expand Down Expand Up @@ -318,6 +305,14 @@ public void getSessionState() {
bluetoothService.hasCharacteristic(Constants.CHARACTERISTIC_UUIDS.SESSION_STATISTIC_CHARACTERISTIC)) {
bluetoothService.readCharacteristic(Constants.CHARACTERISTIC_UUIDS.SESSION_STATISTIC_CHARACTERISTIC);
}
else {
WritableMap wm = Arguments.createMap();
wm.putBoolean("hasActiveSession", false);
wm.putInt("totalDuration", 0);
wm.putInt("slouchTime", 0);
Timber.d("SessionState data %s", wm);
EventEmitter.send(reactContext, "SessionState", wm);
}
}

private void toggleSessionOperation(int operation, Constants.IntCallBack errCallBack) {
Expand All @@ -330,14 +325,16 @@ private void toggleSessionOperation(int operation, Constants.IntCallBack errCall
boolean status;

if (operation == Constants.SESSION_OPERATIONS.START) {
int sessionDurationInSecond = sessionDuration * 60; // Convert to second from minute

byte[] commandBytes = new byte[12];

commandBytes[0] = Constants.SESSION_COMMANDS.START;

commandBytes[1] = Utilities.getByteFromInt(sessionDuration, 3);
commandBytes[2] = Utilities.getByteFromInt(sessionDuration, 2);
commandBytes[3] = Utilities.getByteFromInt(sessionDuration, 1);
commandBytes[4] = Utilities.getByteFromInt(sessionDuration, 0);
commandBytes[1] = Utilities.getByteFromInt(sessionDurationInSecond, 3);
commandBytes[2] = Utilities.getByteFromInt(sessionDurationInSecond, 2);
commandBytes[3] = Utilities.getByteFromInt(sessionDurationInSecond, 1);
commandBytes[4] = Utilities.getByteFromInt(sessionDurationInSecond, 0);

commandBytes[5] = Utilities.getByteFromInt(slouchDistanceThreshold, 1);
commandBytes[6] = Utilities.getByteFromInt(slouchDistanceThreshold, 0);
Expand Down Expand Up @@ -450,17 +447,24 @@ public void onReceive(Context context, Intent intent) {
Timber.d("CharacteristicRead");
String uuid = intent.getStringExtra(Constants.EXTRA_BYTE_UUID_VALUE);

int flags = 0;
int totalDuration = 0;
int slouchTime = 0;

if (uuid.equals(Constants.CHARACTERISTIC_UUIDS.SESSION_STATISTIC_CHARACTERISTIC.toString())) {
byte[] responseArray = intent.getByteArrayExtra(Constants.EXTRA_BYTE_VALUE);

int flags = Utilities.getIntFromByteArray(responseArray, 0);
int totalDuration = Utilities.getIntFromByteArray(responseArray, 4);
int slouchTime = Utilities.getIntFromByteArray(responseArray, 8);

boolean hasActiveSession = (flags % 2 == 1);
if (responseArray == null || responseArray.length < 12) {
// Invalid response, default to no active session
}
else {
flags = Utilities.getIntFromByteArray(responseArray, 0);
totalDuration = Utilities.getIntFromByteArray(responseArray, 4);
slouchTime = Utilities.getIntFromByteArray(responseArray, 8);
}

WritableMap wm = Arguments.createMap();
wm.putBoolean("hasActiveSession", hasActiveSession);
wm.putBoolean("hasActiveSession", flags % 2 == 1);
wm.putInt("totalDuration", totalDuration);
wm.putInt("slouchTime", slouchTime);
Timber.d("SessionState data %s", wm);
Expand Down
8 changes: 8 additions & 0 deletions app/actions/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
DEVICE_DISCONNECT,
DEVICE_FORGET,
DEVICE_GET_INFO,
DEVICE_RESTORE_SAVED_SESSION,
DEVICE_CLEAR_SAVED_SESSION,
} from './types';
import store from '../store';
import Fetcher from '../utils/Fetcher';
Expand Down Expand Up @@ -232,6 +234,12 @@ const deviceActions = {
}),
};
},
restoreSavedSession() {
return { type: DEVICE_RESTORE_SAVED_SESSION };
},
clearSavedSession() {
return { type: DEVICE_CLEAR_SAVED_SESSION };
},
};

export default deviceActions;
2 changes: 2 additions & 0 deletions app/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export const DEVICE_FORGET__ERROR = 'DEVICE_FORGET__ERROR';
export const DEVICE_GET_INFO = 'DEVICE_GET_INFO';
export const DEVICE_GET_INFO__START = 'DEVICE_GET_INFO__START';
export const DEVICE_GET_INFO__ERROR = 'DEVICE_GET_INFO__ERROR';
export const DEVICE_RESTORE_SAVED_SESSION = 'DEVICE_RESTORE_SAVED_SESSION';
export const DEVICE_CLEAR_SAVED_SESSION = 'DEVICE_CLEAR_SAVED_SESSION';

// posture actions
export const SET_SESSION_TIME = 'SET_SESSION_TIME';
Expand Down
32 changes: 18 additions & 14 deletions app/components/PartialModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@ const PartialModal = props => ((props.show &&
<View style={styles.outerContainer}>
<View style={styles.innerContainer}>
<View style={styles.buttonContainer}>
<TouchableOpacity
onPress={() => {
props.dispatch(appActions.hidePartialModal());
if (isFunction(props.onClose)) {
props.onClose();
}
}}
>
<Icon
name="close"
size={styles.$iconSize}
color={styles._closeIcon.color}
/>
</TouchableOpacity>
{
!props.hideClose &&
<TouchableOpacity
onPress={() => {
props.dispatch(appActions.hidePartialModal());
if (isFunction(props.onClose)) {
props.onClose();
}
}}
>
<Icon
name="close"
size={styles.$iconSize}
color={styles._closeIcon.color}
/>
</TouchableOpacity>
}
</View>
{props.children}
</View>
Expand All @@ -44,6 +47,7 @@ PartialModal.propTypes = {
children: PropTypes.node,
dispatch: PropTypes.func,
onClose: PropTypes.func,
hideClose: PropTypes.bool,
show: PropTypes.bool,
};

Expand Down
14 changes: 11 additions & 3 deletions app/components/posture/PostureDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class PostureDashboard extends Component {
device: PropTypes.shape({
isConnected: PropTypes.bool,
isConnecting: PropTypes.bool,
hasSavedSession: PropTypes.bool,
}),
user: PropTypes.shape({
isFetchingSessions: PropTypes.bool,
Expand All @@ -83,11 +84,14 @@ class PostureDashboard extends Component {
componentDidMount() {
this.setSessionTime(sessions[0].durationSeconds);

const { hasSavedSession } = this.props.device;

const {
_id: userId, seenBaselineSurvey, seenAppRating, seenFeedbackSurvey, lastSession,
} = this.props.user.user;

if (!seenBaselineSurvey) {
// Prioritize recovering previous session if exists
if (!hasSavedSession && !seenBaselineSurvey) {
// User has not seen the baseline survey modal yet. Display survey modal
// and mark as seen in the user profile to prevent it from being shown again.
const markSurveySeenAndHideModal = () => {
Expand Down Expand Up @@ -189,8 +193,12 @@ class PostureDashboard extends Component {
}

componentWillReceiveProps(nextProps) {
if (this.props.user.isFetchingSessions && !nextProps.user.isFetchingSessions) {
// Finished fetching user sessions.
const { isFetchingSessions } = this.props.user;

const sessionsFetched = isFetchingSessions && !nextProps.user.isFetchingSessions;

if (!nextProps.device.hasSavedSession && sessionsFetched) {
// Finished fetching user sessions and display popUp when needed
if (!this.props.user.user.seenFeedbackSurvey || !this.props.user.user.seenAppRating) {
const createdDate = new Date(this.props.user.user.createdAt);
const timeThreshold = 7 * 24 * 60 * 60 * 1000; // 7 days converted to milliseconds
Expand Down
55 changes: 38 additions & 17 deletions app/components/posture/PostureMonitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ class PostureMonitor extends Component {
slouchDistanceThreshold: PropTypes.number,
vibrationSpeed: PropTypes.number,
vibrationPattern: PropTypes.oneOf([1, 2, 3]),
showSummary: PropTypes.bool,
previousSessionEvent: PropTypes.shape({
hasActiveSession: PropTypes.bool,
totalDuration: PropTypes.number,
slouchTime: PropTypes.number,
}),
}),
user: PropTypes.shape({
settings: PropTypes.shape({
Expand Down Expand Up @@ -156,6 +162,9 @@ class PostureMonitor extends Component {
this.sessionDataListener = null;
this.slouchListener = null;
this.statsListener = null;
this.deviceStateListener = null;
this.sessionStateListener = null;
this.sessionControlStateListener = null;
// Debounce update of user posture threshold setting to limit the number of API requests
this.updateUserPostureThreshold = debounce(this.updateUserPostureThreshold, 1000);
this.backAndroidListener = null;
Expand Down Expand Up @@ -255,7 +264,9 @@ class PostureMonitor extends Component {
// ANDROID ONLY: Listen to the hardware back button
if (!isiOS) {
this.backAndroidListener = BackAndroid.addEventListener('hardwareBackPress', () => {
if (this.state.sessionState !== sessionStates.STOPPED
if (this.props.sessionState && this.props.sessionState.showSummary) {
this.props.navigator.resetTo(routes.postureDashboard);
} else if (this.state.sessionState !== sessionStates.STOPPED
&& !this.state.hasPendingSessionOperation) {
// Back button was pressed during an active session.
// Check if PostureMonitor is the current scene.
Expand Down Expand Up @@ -283,19 +294,25 @@ class PostureMonitor extends Component {
});
}

const { sessionState } = this.state;
if (sessionState === sessionStates.PAUSED) {
// There is an active session that's paused
// Sync app to a paused state
this.pauseSession();
} else if (sessionState === sessionStates.RUNNING) {
// There is an active session that's running
// Sync app to a running state
this.resumeSession();
if (this.props.sessionState && this.props.sessionState.showSummary) {
this.setState({ forceStoppedSession: true }, () => {
this.statsHandler(this.props.sessionState.previousSessionEvent);
});
} else {
// There is no active session
// Automatically start a new session
this.startSession();
const { sessionState } = this.state;
if (sessionState === sessionStates.PAUSED) {
// There is an active session that's paused
// Sync app to a paused state
this.pauseSession();
} else if (sessionState === sessionStates.RUNNING) {
// There is an active session that's running
// Sync app to a running state
this.resumeSession();
} else {
// There is no active session
// Automatically start a new session
this.startSession();
}
}
}

Expand Down Expand Up @@ -326,10 +343,13 @@ class PostureMonitor extends Component {
}

componentWillUnmount() {
// End the session if it's running
SessionControlService.stop(() => {
// no-op
});
const { forceStoppedSession, sessionState } = this.state;
// End the session if it's running and not yet stopped
if (!forceStoppedSession && sessionState !== sessionStates.STOPPED) {
SessionControlService.stop(() => {
// no-op
});
}

// Remove listeners
this.sessionDataListener.remove();
Expand Down Expand Up @@ -399,6 +419,7 @@ class PostureMonitor extends Component {
});
} else if (state === sessionStates.STOPPED) {
// Remove session state from local storage
this.props.dispatch(deviceActions.clearSavedSession());
SensitiveInfo.deleteItem(storageKeys.SESSION_STATE);
}
this.setState({ sessionState: state }, () => {
Expand Down
Loading

0 comments on commit 2019f75

Please sign in to comment.