diff --git a/.gitignore b/.gitignore
index f487361a..334e15a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -123,3 +123,4 @@ manifest-merger-release-report.txt
# Android Studio heap captures
captures/
+app/google-services.json
diff --git a/app/build.gradle b/app/build.gradle
index fcdd59a5..b3caee18 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,6 +7,10 @@ android {
useLibrary 'org.apache.http.legacy'
+ configurations {
+ compile.exclude group: "org.apache.httpcomponents", module: "httpclient"
+ }
+
defaultConfig {
applicationId 'fi.aalto.legroup.achso'
minSdkVersion 16
@@ -84,10 +88,13 @@ dependencies {
compile 'org.florescu.android.rangeseekbar:rangeseekbar-library:0.3.0'
// Google Play Services APIs
- compile 'com.google.android.gms:play-services-base:8.1.0'
- compile 'com.google.android.gms:play-services-maps:8.1.0'
- compile 'com.google.android.gms:play-services-location:8.1.0'
- compile 'com.google.android.gms:play-services-analytics:8.1.0'
+ compile 'com.google.android.gms:play-services-base:10.2.1'
+ compile 'com.google.android.gms:play-services-maps:10.2.1'
+ compile 'com.google.android.gms:play-services-location:10.2.1'
+ compile 'com.google.android.gms:play-services-analytics:10.2.1'
+
+ // Push notifications
+ compile 'com.google.firebase:firebase-messaging:10.2.1'
// OAuth2 library for OpenID Connect
compile('com.google.oauth-client:google-oauth-client:1.19.0') {
@@ -129,3 +136,5 @@ dependencies {
// Decrypting the Layers Box URL
compile 'fi.aalto.legroup:cryptohelper:0.1.0'
}
+
+apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c5d66253..f6ac48b2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -138,7 +138,24 @@
android:label="@string/choose_account"
android:parentActivityName=".browsing.BrowserActivity" />
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/fi/aalto/legroup/achso/app/App.java b/app/src/main/java/fi/aalto/legroup/achso/app/App.java
index 88deeff0..bf021cda 100644
--- a/app/src/main/java/fi/aalto/legroup/achso/app/App.java
+++ b/app/src/main/java/fi/aalto/legroup/achso/app/App.java
@@ -1,5 +1,6 @@
package fi.aalto.legroup.achso.app;
+import android.accounts.Account;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
@@ -7,23 +8,34 @@
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Environment;
+import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.multidex.MultiDexApplication;
+import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.analytics.GoogleAnalytics;
+import com.google.firebase.iid.FirebaseInstanceId;
import com.rollbar.android.Rollbar;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.otto.Bus;
+import com.squareup.otto.Subscribe;
+
+import org.json.JSONException;
import java.io.File;
+import java.io.IOException;
import java.security.GeneralSecurityException;
+import java.util.Timer;
+import java.util.TimerTask;
import fi.aalto.legroup.achso.BuildConfig;
import fi.aalto.legroup.achso.R;
+import fi.aalto.legroup.achso.authentication.AccountLoggedOutEvent;
import fi.aalto.legroup.achso.authentication.AuthenticatedHttpClient;
import fi.aalto.legroup.achso.authentication.LoginManager;
import fi.aalto.legroup.achso.authentication.LoginRequestEvent;
+import fi.aalto.legroup.achso.authentication.LoginStateEvent;
import fi.aalto.legroup.achso.authentication.OIDCConfig;
import fi.aalto.legroup.achso.authoring.ExportHelper;
import fi.aalto.legroup.achso.authoring.LocationManager;
@@ -79,6 +91,7 @@ public void onCreate() {
setupPreferences();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+
layersBoxUrl = readLayersBoxUrl();
usePublicLayersBox = preferences.getBoolean(AppPreferences.USE_PUBLIC_LAYERS_BOX, false);
publicLayersBoxUrl = Uri.parse(getString(R.string.publicLayersBoxUrl));
@@ -139,6 +152,7 @@ public void onCreate() {
updateOIDCTokens(this);
+ bus.register(this);
bus.post(new LoginRequestEvent(LoginRequestEvent.Type.LOGIN));
// Trim the caches asynchronously
@@ -172,6 +186,64 @@ public void tokensRetrieved() {
}
}
+ public static void tokenUpdated(String notificationToken) {
+ if (loginManager.isLoggedIn()) {
+ App.registerToken(notificationToken);
+ }
+ }
+
+ @Subscribe
+ public static void onLoginStateEvent(final LoginStateEvent event) {
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ // this code will be executed after 2 seconds
+ String token = FirebaseInstanceId.getInstance().getToken();
+
+ if (token == null) return;
+
+ if (event.getState() == LoginManager.LoginState.LOGGED_IN) {
+ App.registerToken(token);
+ }
+ }
+ }, 4000);
+ }
+
+ @Subscribe
+ public static void onAccountLoggedOutEvent(final AccountLoggedOutEvent event) {
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ // this code will be executed after 2 seconds
+ String token = FirebaseInstanceId.getInstance().getToken();
+
+ if (token == null) return;
+
+ App.removeTokenFromAccount(event.getAccount(), token);
+ }
+ }, 10);
+ }
+
+ private static void registerToken(final String notificationToken) {
+ try {
+ achRails.registerToken(notificationToken);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void removeTokenFromAccount(Account account, String notificationToken) {
+ try {
+ achRails.unregisterToken(account, notificationToken);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
public static Uri getLayersBoxUrl() {
if (usePublicLayersBox) {
return publicLayersBoxUrl;
diff --git a/app/src/main/java/fi/aalto/legroup/achso/authentication/AccountLoggedOutEvent.java b/app/src/main/java/fi/aalto/legroup/achso/authentication/AccountLoggedOutEvent.java
new file mode 100644
index 00000000..3ca88c4f
--- /dev/null
+++ b/app/src/main/java/fi/aalto/legroup/achso/authentication/AccountLoggedOutEvent.java
@@ -0,0 +1,19 @@
+package fi.aalto.legroup.achso.authentication;
+
+import android.accounts.Account;
+
+/**
+ * Created by mat on 19/04/2017.
+ */
+
+public class AccountLoggedOutEvent {
+ Account account;
+
+ public AccountLoggedOutEvent(Account account) {
+ this.account = account;
+ }
+
+ public Account getAccount() {
+ return account;
+ }
+}
diff --git a/app/src/main/java/fi/aalto/legroup/achso/authentication/AuthenticatedHttpClient.java b/app/src/main/java/fi/aalto/legroup/achso/authentication/AuthenticatedHttpClient.java
index e85f88be..d173751b 100644
--- a/app/src/main/java/fi/aalto/legroup/achso/authentication/AuthenticatedHttpClient.java
+++ b/app/src/main/java/fi/aalto/legroup/achso/authentication/AuthenticatedHttpClient.java
@@ -5,6 +5,7 @@
import android.content.Context;
import android.util.Log;
+import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
@@ -67,6 +68,16 @@ public Response execute(Request request, Account account, boolean doRetry) throw
return response;
}
+ public void enqueue(Request request, Account account, Callback cb) {
+ AccountManager accountManager = AccountManager.get(context);
+
+ String token = getBearerToken(account);
+
+ request = request.newBuilder().header("Authorization", "Bearer " + token).build();
+
+ httpClient.newCall(request).enqueue(cb);
+ }
+
public boolean accessDenied(Response response) {
int code = response.code();
diff --git a/app/src/main/java/fi/aalto/legroup/achso/authentication/LoginManager.java b/app/src/main/java/fi/aalto/legroup/achso/authentication/LoginManager.java
index 59ed3644..d2c7f483 100644
--- a/app/src/main/java/fi/aalto/legroup/achso/authentication/LoginManager.java
+++ b/app/src/main/java/fi/aalto/legroup/achso/authentication/LoginManager.java
@@ -88,10 +88,16 @@ public void login(Account account) {
new LoginTask().execute(account);
}
+ private void notifyAcccountLoggedOut(Account account) {
+ this.bus.post(new AccountLoggedOutEvent(account));
+ }
+
/**
* Logs out from the account and disables auto-login. Use this if the user manually logs out.
*/
public void logoutExplicitly() {
+ setState(LoginState.LOGGING_OUT, false);
+
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit()
@@ -106,7 +112,12 @@ public void logoutExplicitly() {
* automatic (e.g. connectivity lost) and not initiated by the user.
*/
public void logout() {
+ if (getState() != LoginState.LOGGING_OUT) {
+ setState(LoginState.LOGGING_OUT, false);
+ }
+
setState(LoginState.LOGGED_OUT, true);
+ notifyAcccountLoggedOut(account);
account = null;
user = null;
}
@@ -259,7 +270,5 @@ protected void onPostExecute(String error) {
setState(LoginState.LOGGED_IN, true);
}
-
}
-
}
diff --git a/app/src/main/java/fi/aalto/legroup/achso/browsing/DetailActivity.java b/app/src/main/java/fi/aalto/legroup/achso/browsing/DetailActivity.java
index d2429494..0817d1b0 100644
--- a/app/src/main/java/fi/aalto/legroup/achso/browsing/DetailActivity.java
+++ b/app/src/main/java/fi/aalto/legroup/achso/browsing/DetailActivity.java
@@ -25,6 +25,7 @@
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CircleOptions;
@@ -143,27 +144,30 @@ public void onCreate(Bundle savedInstanceState) {
SupportMapFragment mapFragment = (SupportMapFragment)
getSupportFragmentManager().findFragmentById(R.id.mapFragment);
- Location location = video.getLocation();
+ final Location location = video.getLocation();
if (location != null) {
- LatLng position = new LatLng(location.getLatitude(), location.getLongitude());
-
- GoogleMap map = mapFragment.getMap();
+ mapFragment.getMapAsync(new OnMapReadyCallback() {
+ @Override
+ public void onMapReady(GoogleMap googleMap) {
+ LatLng position = new LatLng(location.getLatitude(), location.getLongitude());
- map.addCircle(new CircleOptions()
- .center(position)
- .radius(location.getAccuracy())
- .strokeWidth(3.0f)
- .strokeColor(Color.WHITE)
- .fillColor(Color.parseColor("#80ffffff")));
+ googleMap.addCircle(new CircleOptions()
+ .center(position)
+ .radius(location.getAccuracy())
+ .strokeWidth(3.0f)
+ .strokeColor(Color.WHITE)
+ .fillColor(Color.parseColor("#80ffffff")));
- map.addMarker(new MarkerOptions()
- .position(position)
- .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
+ googleMap.addMarker(new MarkerOptions()
+ .position(position)
+ .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
- map.moveCamera(CameraUpdateFactory.newLatLngZoom(position, 14.5f));
+ googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(position, 14.5f));
- findViewById(R.id.unknownLocationText).setVisibility(View.GONE);
+ findViewById(R.id.unknownLocationText).setVisibility(View.GONE);
+ }
+ });
}
initializeAddQRButton();
diff --git a/app/src/main/java/fi/aalto/legroup/achso/storage/remote/VideoHost.java b/app/src/main/java/fi/aalto/legroup/achso/storage/remote/VideoHost.java
index 3d0da60c..75f2a9ba 100644
--- a/app/src/main/java/fi/aalto/legroup/achso/storage/remote/VideoHost.java
+++ b/app/src/main/java/fi/aalto/legroup/achso/storage/remote/VideoHost.java
@@ -1,5 +1,6 @@
package fi.aalto.legroup.achso.storage.remote;
+import android.accounts.Account;
import android.net.Uri;
import org.json.JSONException;
@@ -57,4 +58,8 @@ public interface VideoHost {
* Finds a video by the video source uri.
*/
public Video findVideoByVideoUri(Uri videoUri) throws IOException;
+
+ public void registerToken(String notificationToken) throws JSONException, IOException;
+
+ public void unregisterToken(Account account, String notificationToken) throws JSONException, IOException;
}
diff --git a/app/src/main/java/fi/aalto/legroup/achso/storage/remote/strategies/AchRailsStrategy.java b/app/src/main/java/fi/aalto/legroup/achso/storage/remote/strategies/AchRailsStrategy.java
index f523f328..faddede8 100644
--- a/app/src/main/java/fi/aalto/legroup/achso/storage/remote/strategies/AchRailsStrategy.java
+++ b/app/src/main/java/fi/aalto/legroup/achso/storage/remote/strategies/AchRailsStrategy.java
@@ -3,6 +3,7 @@
import android.accounts.Account;
import android.net.Uri;
+import com.squareup.okhttp.Callback;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.Request;
@@ -27,6 +28,7 @@
import fi.aalto.legroup.achso.entities.serialization.json.JsonSerializable;
import fi.aalto.legroup.achso.entities.serialization.json.JsonSerializer;
import fi.aalto.legroup.achso.storage.remote.VideoHost;
+import fi.aalto.legroup.achso.utilities.EmptyCallback;
import okio.BufferedSink;
import okio.Okio;
@@ -60,6 +62,7 @@ Request.Builder buildVideosRequest() {
return new Request.Builder()
.url(endpointUrl.buildUpon().appendPath("videos.json").toString());
}
+
Request.Builder buildVideosRequest(UUID id) {
return new Request.Builder()
.url(endpointUrl.buildUpon()
@@ -101,6 +104,16 @@ private Response validateResponse(Response response) throws IOException {
return response;
}
+ private Response executeRequestWithAccount(Account account, Request request) {
+ try {
+ return App.authenticatedHttpClient.execute(request, account);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
private Response executeRequest(Request request) throws IOException {
return validateResponse(executeRequestNoFail(request));
}
@@ -161,6 +174,41 @@ public void makeVideoPrivate(UUID videoId) throws IOException, JSONException {
Response response = executeRequest(request);
}
+
+ @Override
+ public void registerToken(String notificationToken) throws JSONException, IOException {
+ JSONObject obj = new JSONObject();
+ obj.put("registration_token", notificationToken);
+
+ RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), obj.toString());
+
+ Request request = new Request.Builder()
+ .url(endpointUrl.buildUpon()
+ .appendPath("notifications")
+ .appendPath("register_token")
+ .toString())
+ .put(body).build();
+
+ executeRequest(request);
+ }
+
+ @Override
+ public void unregisterToken(Account account, String notificationToken) throws JSONException, IOException {
+ JSONObject obj = new JSONObject();
+ obj.put("registration_token", notificationToken);
+
+ RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), obj.toString());
+
+ Request request = new Request.Builder()
+ .url(endpointUrl.buildUpon()
+ .appendPath("notifications")
+ .appendPath("unregister_token")
+ .toString())
+ .put(body).build();
+
+ executeRequestWithAccount(account, request);
+ }
+
@Override
public Video downloadVideoManifest(UUID id) throws IOException {
diff --git a/app/src/main/java/fi/aalto/legroup/achso/utilities/AchsoFirebaseInstanceIdService.java b/app/src/main/java/fi/aalto/legroup/achso/utilities/AchsoFirebaseInstanceIdService.java
new file mode 100644
index 00000000..fdfe4e04
--- /dev/null
+++ b/app/src/main/java/fi/aalto/legroup/achso/utilities/AchsoFirebaseInstanceIdService.java
@@ -0,0 +1,17 @@
+package fi.aalto.legroup.achso.utilities;
+import com.google.firebase.iid.FirebaseInstanceId;
+import com.google.firebase.iid.FirebaseInstanceIdService;
+
+import fi.aalto.legroup.achso.app.App;
+
+public class AchsoFirebaseInstanceIdService extends FirebaseInstanceIdService {
+
+ @Override
+ public void onTokenRefresh() {
+ String refreshedToken = FirebaseInstanceId.getInstance().getToken();
+
+ App.tokenUpdated(refreshedToken);
+
+ super.onTokenRefresh();
+ }
+}
diff --git a/app/src/main/java/fi/aalto/legroup/achso/utilities/AchsoFirebaseMessagingService.java b/app/src/main/java/fi/aalto/legroup/achso/utilities/AchsoFirebaseMessagingService.java
new file mode 100644
index 00000000..c159f025
--- /dev/null
+++ b/app/src/main/java/fi/aalto/legroup/achso/utilities/AchsoFirebaseMessagingService.java
@@ -0,0 +1,57 @@
+package fi.aalto.legroup.achso.utilities;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.support.v4.app.NotificationCompat;
+
+import com.google.firebase.messaging.FirebaseMessagingService;
+import com.google.firebase.messaging.RemoteMessage;
+
+import fi.aalto.legroup.achso.R;
+import fi.aalto.legroup.achso.browsing.BrowserActivity;
+
+/**
+ * Created by mat on 18/04/2017.
+ */
+
+public class AchsoFirebaseMessagingService extends FirebaseMessagingService {
+
+ @Override
+ public void onMessageReceived(RemoteMessage remoteMessage) {
+ super.onMessageReceived(remoteMessage);
+
+ if (remoteMessage.getNotification() != null) {
+ RemoteMessage.Notification notification = remoteMessage.getNotification();
+ String title = notification.getTitle();
+ String body = notification.getBody();
+
+ sendNotification(body, title);
+ }
+ }
+
+ private void sendNotification(String messageBody, String messageTitle) {
+ Intent intent = new Intent(this, BrowserActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
+ PendingIntent.FLAG_ONE_SHOT);
+
+
+ Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.ic_launcher)
+ .setContentTitle(messageTitle)
+ .setContentText(messageBody)
+ .setAutoCancel(true)
+ .setSound(defaultSoundUri)
+ .setContentIntent(pendingIntent);
+
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+ notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
+ }
+}
diff --git a/app/src/main/java/fi/aalto/legroup/achso/utilities/EmptyCallback.java b/app/src/main/java/fi/aalto/legroup/achso/utilities/EmptyCallback.java
new file mode 100644
index 00000000..44ba585d
--- /dev/null
+++ b/app/src/main/java/fi/aalto/legroup/achso/utilities/EmptyCallback.java
@@ -0,0 +1,23 @@
+package fi.aalto.legroup.achso.utilities;
+
+import com.squareup.okhttp.Callback;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
+
+import java.io.IOException;
+
+/**
+ * Created by mat on 09/04/2017.
+ */
+
+public class EmptyCallback implements Callback {
+ @Override
+ public void onFailure(Request request, IOException e) {
+ System.out.println(request);
+ }
+
+ @Override
+ public void onResponse(Response response) throws IOException {
+ System.out.println(response);
+ }
+}
diff --git a/build.gradle b/build.gradle
index c6f2f500..e118f6a3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,8 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.0'
+ classpath 'com.android.tools.build:gradle:2.3.1'
+ classpath 'com.google.gms:google-services:3.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files