Skip to content

Commit

Permalink
Fixed location service issues when recreating MainActivity
Browse files Browse the repository at this point in the history
Fixes #77
Fixes #80
  • Loading branch information
bilde2910 committed Dec 9, 2019
1 parent 2144681 commit 319200e
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public void accept() {
Log.i("Resuming shares..."); //NON-NLS
AutoResumptionPrompter.this.resumptionHandler.clearResumableSession();
for (Share share : this.shares) {
AutoResumptionPrompter.this.manager.shareLocation(share);
AutoResumptionPrompter.this.manager.shareLocation(share, SessionInitiationReason.USER_RESUMED);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package info.varden.hauk.manager;

import android.content.Context;

import info.varden.hauk.caching.ResumableSessions;
import info.varden.hauk.caching.ResumeHandler;
import info.varden.hauk.struct.Session;
import info.varden.hauk.struct.Share;
import info.varden.hauk.utils.Log;

/**
* {@link ResumeHandler} implementation used by {@link SessionManager} to automatically resume
* sessions following a service relaunch. This can happen if the main activity is terminated, but
* the share itself keeps running in the background.
*
* @author Marius Lindvall
*/
public class ServiceRelauncher implements ResumeHandler {
/**
* The session manager to call to resume the shares.
*/
private final SessionManager manager;

/**
* The manager's resumption handler. This is used to clear the resumption data before the shares
* are resumed by the session manager, as the session manager will re-flag the shares as
* resumable when it adds them to its internal share list.
*/
private final ResumableSessions resumptionHandler;

public ServiceRelauncher(SessionManager manager, ResumableSessions resumptionHandler) {
this.manager = manager;
this.resumptionHandler = resumptionHandler;
}

@Override
public void onSharesFetched(Context ctx, Session session, Share[] shares) {
Log.i("Resuming %s share(s) automatically found for session %s", shares.length, session); //NON-NLS
// The shares provided by ResumableSessions do not have a session attached to them. Attach
// it to the shares so that they can be shown properly by the prompt and so that the updates
// have a backend to be broadcast to when the shares are resumed.
this.resumptionHandler.clearResumableSession();
for (Share share : shares) {
share.setSession(session);
this.manager.shareLocation(share, SessionInitiationReason.SERVICE_RELAUNCH);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package info.varden.hauk.manager;

import info.varden.hauk.struct.Session;
import info.varden.hauk.struct.Share;

/**
* Describes the reason a session was initiated.
*
* @author Marius Lindvall
*/
public enum SessionInitiationReason {
/**
* The user requested to start a new sharing session.
*/
USER_STARTED,

/**
* The user requested to resume a previous sharing session.
*/
USER_RESUMED,

/**
* The sharing session is automatically resumed as a result of a relaunch of the location
* sharing service.
*/
SERVICE_RELAUNCH,

/**
* The session was created because a share was added to it. This should never be received by
* {@link SessionListener#onSessionCreated(Session, Share, SessionInitiationReason)} under any
* normal circumstances.
*/
SHARE_ADDED
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ public interface SessionListener {
*
* @param session The session that was created.
* @param share The share that the session was created for.
* @param reason The reason the session was created.
*/
void onSessionCreated(Session session, Share share);
void onSessionCreated(Session session, Share share, SessionInitiationReason reason);

/**
* Called if the session could not be initiated due to missing location permissions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public abstract class SessionManager {
*/
private final StopSharingCallback stopCallback;

/**
* Intent for the location pusher, so that it can be stopped if already running when launching
* the app.
*/
private static Intent pusher = null;

/**
* Android application context.
*/
Expand Down Expand Up @@ -182,7 +188,19 @@ public final void attachSessionListener(SessionListener listener) {
* any are found in storage.
*/
public final void resumeShares(ResumePrompt prompt) {
this.resumable.tryResumeShare(new AutoResumptionPrompter(this, this.resumable, prompt));
// Check if the location push service is already running. This will happen if the main UI
// activity is killed/stopped, but the app itself and the pushing service keeps running in
// the background. If this happens, the push service should be silently restarted to ensure
// it behaves properly with new instances of GNSSActiveHandler and StopSharingTask that will
// be created and attached when creating a new SessionManager in MainActivity. There is
// probably a cleaner way to do this.
if (pusher != null) {
this.ctx.stopService(pusher);
pusher = null;
this.resumable.tryResumeShare(new ServiceRelauncher(this, this.resumable));
} else {
this.resumable.tryResumeShare(new AutoResumptionPrompter(this, this.resumable, prompt));
}
}

/**
Expand All @@ -194,7 +212,7 @@ public final void resumeShares(ResumePrompt prompt) {
* @throws LocationServicesDisabledException if location services are disabled.
* @throws LocationPermissionsNotGrantedException if location permissions have not been granted.
*/
private SessionInitiationPacket.ResponseHandler preSessionInitiation(final SessionInitiationResponseHandler upstreamCallback) throws LocationServicesDisabledException, LocationPermissionsNotGrantedException {
private SessionInitiationPacket.ResponseHandler preSessionInitiation(final SessionInitiationResponseHandler upstreamCallback, final SessionInitiationReason reason) throws LocationServicesDisabledException, LocationPermissionsNotGrantedException {
// Check for location permission and prompt the user if missing. This returns because the
// checking function creates async dialogs here - the user is prompted to press the button
// again instead.
Expand All @@ -215,7 +233,7 @@ public void onSessionInitiated(Share share) {
Log.i("Session was initiated for share %s; setting session resumable", share); //NON-NLS

// Proceed with the location share.
shareLocation(share);
shareLocation(share, reason);

upstreamCallback.onSuccess();
}
Expand Down Expand Up @@ -250,7 +268,7 @@ public void onFailure(Exception ex) {
* @throws LocationPermissionsNotGrantedException if location permissions have not been granted.
*/
public final void shareLocation(SessionInitiationPacket.InitParameters initParams, SessionInitiationResponseHandler upstreamCallback, AdoptabilityPreference allowAdoption) throws LocationPermissionsNotGrantedException, LocationServicesDisabledException {
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback);
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback, SessionInitiationReason.USER_STARTED);

// Create a handshake request and handle the response. The handshake transmits the duration
// and interval to the server and waits for the server to return a session ID to confirm
Expand All @@ -269,7 +287,7 @@ public final void shareLocation(SessionInitiationPacket.InitParameters initParam
* @throws LocationPermissionsNotGrantedException if location permissions have not been granted.
*/
public final void shareLocation(SessionInitiationPacket.InitParameters initParams, SessionInitiationResponseHandler upstreamCallback, String nickname) throws LocationPermissionsNotGrantedException, LocationServicesDisabledException {
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback);
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback, SessionInitiationReason.USER_STARTED);

// Create a handshake request and handle the response. The handshake transmits the duration
// and interval to the server and waits for the server to return a session ID to confirm
Expand All @@ -289,7 +307,7 @@ public final void shareLocation(SessionInitiationPacket.InitParameters initParam
* @throws LocationPermissionsNotGrantedException if location permissions have not been granted.
*/
public final void shareLocation(SessionInitiationPacket.InitParameters initParams, SessionInitiationResponseHandler upstreamCallback, String nickname, String groupPin) throws LocationPermissionsNotGrantedException, LocationServicesDisabledException {
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback);
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback, SessionInitiationReason.USER_STARTED);

// Create a handshake request and handle the response. The handshake transmits the duration
// and interval to the server and waits for the server to return a session ID to confirm
Expand All @@ -304,10 +322,10 @@ public final void shareLocation(SessionInitiationPacket.InitParameters initParam
*
* @param share The share to run against the server.
*/
public final void shareLocation(Share share) {
public final void shareLocation(Share share, SessionInitiationReason reason) {
// If we are not already sharing our location, initiate a new session.
if (this.activeSession == null) {
initiateSessionForExistingShare(share);
initiateSessionForExistingShare(share, reason);
}

Log.i("Attaching to share, share=%s", share); //NON-NLS
Expand Down Expand Up @@ -349,7 +367,7 @@ protected void onFailure(Exception ex) {
*
* @param share The share whose session should be pushed to.
*/
private void initiateSessionForExistingShare(Share share) {
private void initiateSessionForExistingShare(Share share, SessionInitiationReason reason) {
this.activeSession = share.getSession();
this.resumable.setSessionResumable(this.activeSession);

Expand Down Expand Up @@ -381,6 +399,7 @@ private void initiateSessionForExistingShare(Share share) {
// When both the notification and pusher are created, we can update the stop task with
// these so that they can be canceled when the location share ends.
this.stopTask.updateTask(pusher);
SessionManager.pusher = pusher;

// stopTask is scheduled for expiration, but it could also be called if the user
// manually stops the share, or if the app is destroyed.
Expand All @@ -393,7 +412,7 @@ private void initiateSessionForExistingShare(Share share) {
listener.onStarted();
}
for (SessionListener listener : this.upstreamSessionListeners) {
listener.onSessionCreated(share.getSession(), share);
listener.onSessionCreated(share.getSession(), share, reason);
}
} else {
Log.w("Location permission has not been granted; sharing will not commence"); //NON-NLS
Expand Down Expand Up @@ -460,7 +479,7 @@ public void onShareListReceived(String linkFormat, String[] shareIDs) {
// that can be initiated by a remote user (through adoption).
Share newShare = new Share(this.session, String.format(linkFormat, shareID), shareID, ShareMode.JOIN_GROUP);
Log.i("Received unknown share %s from server", newShare); //NON-NLS
shareLocation(newShare);
shareLocation(newShare, SessionInitiationReason.SHARE_ADDED);
}
}
for (Iterator<Map.Entry<String, Share>> it = SessionManager.this.knownShares.entrySet().iterator(); it.hasNext();) {
Expand Down
54 changes: 29 additions & 25 deletions android/app/src/main/java/info/varden/hauk/ui/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import info.varden.hauk.dialog.DialogService;
import info.varden.hauk.http.SessionInitiationPacket;
import info.varden.hauk.manager.PromptCallback;
import info.varden.hauk.manager.SessionInitiationReason;
import info.varden.hauk.manager.SessionInitiationResponseHandler;
import info.varden.hauk.manager.SessionListener;
import info.varden.hauk.manager.SessionManager;
Expand Down Expand Up @@ -476,7 +477,7 @@ public void run() {
*/
private final class SessionListenerImpl implements SessionListener {
@Override
public void onSessionCreated(Session session, final Share share) {
public void onSessionCreated(Session session, final Share share, SessionInitiationReason reason) {
// We now have a link to share, so we enable the additional link creation button if the backend supports it. Add an event handler to handle the user clicking on it.
if (session.getBackendVersion().isAtLeast(Constants.VERSION_COMPAT_VIEW_ID)) {
boolean allowNewLinkAdoption = ((Checkable) findViewById(R.id.chkAllowAdopt)).isChecked();
Expand All @@ -485,7 +486,7 @@ public void onSessionCreated(Session session, final Share share) {
btnLink.setOnClickListener(new AddLinkClickListener(MainActivity.this, session, allowNewLinkAdoption) {
@Override
public void onShareCreated(Share share) {
MainActivity.this.manager.shareLocation(share);
MainActivity.this.manager.shareLocation(share, SessionInitiationReason.SHARE_ADDED);
}
});
btnLink.setEnabled(true);
Expand All @@ -501,29 +502,32 @@ public void onShareCreated(Share share) {
// Re-enable the start (stop) button and inform the user.
findViewById(R.id.btnShare).setEnabled(true);

MainActivity.this.dialogSvc.showDialog(R.string.ok_title, R.string.ok_message, Buttons.OK_SHARE, new CustomDialogBuilder() {
@Override
public void onPositive() {
// OK button
}

@Override
public void onNegative() {
// Share button
Log.i("User requested to share %s", share); //NON-NLS
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType(Constants.INTENT_TYPE_COPY_LINK);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, MainActivity.this.getString(R.string.share_subject));
shareIntent.putExtra(Intent.EXTRA_TEXT, share.getViewURL());
MainActivity.this.startActivity(Intent.createChooser(shareIntent, MainActivity.this.getString(R.string.share_via)));
}

@Nullable
@Override
public View createView(Context ctx) {
return null;
}
});
// Service relaunches should be handled silently.
if (reason != SessionInitiationReason.SERVICE_RELAUNCH) {
MainActivity.this.dialogSvc.showDialog(R.string.ok_title, R.string.ok_message, Buttons.OK_SHARE, new CustomDialogBuilder() {
@Override
public void onPositive() {
// OK button
}

@Override
public void onNegative() {
// Share button
Log.i("User requested to share %s", share); //NON-NLS
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType(Constants.INTENT_TYPE_COPY_LINK);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, MainActivity.this.getString(R.string.share_subject));
shareIntent.putExtra(Intent.EXTRA_TEXT, share.getViewURL());
MainActivity.this.startActivity(Intent.createChooser(shareIntent, MainActivity.this.getString(R.string.share_via)));
}

@Nullable
@Override
public View createView(Context ctx) {
return null;
}
});
}
}

@Override
Expand Down

0 comments on commit 319200e

Please sign in to comment.