Integrates with wryk/tusky-api, but only partially working.

Registers to the web-service fine but loses connection when subscribing with the broker.
This commit is contained in:
Vavassor 2017-05-20 02:39:29 -04:00
commit c90c909ca6
5 changed files with 75 additions and 291 deletions

View file

@ -31,6 +31,7 @@ import android.view.Menu;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.keylesspalace.tusky.entity.Session;
import com.keylesspalace.tusky.json.SpannedTypeAdapter; import com.keylesspalace.tusky.json.SpannedTypeAdapter;
import com.keylesspalace.tusky.json.StringWithEmoji; import com.keylesspalace.tusky.json.StringWithEmoji;
import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
@ -162,8 +163,9 @@ public class BaseActivity extends AppCompatActivity {
protected void createTuskyApi() { protected void createTuskyApi() {
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + getString(R.string.tusky_api_domain)) .baseUrl("http://" + getString(R.string.tusky_api_domain) + ":8080")
.client(OkHttpUtils.getCompatibleClient()) .client(OkHttpUtils.getCompatibleClient())
.addConverterFactory(GsonConverterFactory.create())
.build(); .build();
tuskyApi = retrofit.create(TuskyApi.class); tuskyApi = retrofit.create(TuskyApi.class);
@ -172,7 +174,7 @@ public class BaseActivity extends AppCompatActivity {
protected void createPushNotificationClient() { protected void createPushNotificationClient() {
// TODO: Switch to ssl:// when TLS support is added. // TODO: Switch to ssl:// when TLS support is added.
pushNotificationClient = new PushNotificationClient(getApplicationContext(), pushNotificationClient = new PushNotificationClient(getApplicationContext(),
"tcp://" + getString(R.string.tusky_api_domain)); "tcp://" + getString(R.string.tusky_api_domain) + ":8000");
} }
protected void redirectIfNotLoggedIn() { protected void redirectIfNotLoggedIn() {
@ -212,6 +214,7 @@ public class BaseActivity extends AppCompatActivity {
retrofit2.Response<ResponseBody> response) { retrofit2.Response<ResponseBody> response) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
pushNotificationClient.subscribeToTopic(getPushNotificationTopic()); pushNotificationClient.subscribeToTopic(getPushNotificationTopic());
pushNotificationClient.connect();
} else { } else {
onEnablePushNotificationsFailure(); onEnablePushNotificationsFailure();
} }
@ -222,7 +225,9 @@ public class BaseActivity extends AppCompatActivity {
onEnablePushNotificationsFailure(); onEnablePushNotificationsFailure();
} }
}; };
tuskyApi.register(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) String deviceToken = pushNotificationClient.getDeviceToken();
Session session = new Session(getDomain(), getAccessToken(), deviceToken);
tuskyApi.register(session)
.enqueue(callback); .enqueue(callback);
} }
@ -247,7 +252,9 @@ public class BaseActivity extends AppCompatActivity {
onDisablePushNotificationsFailure(); onDisablePushNotificationsFailure();
} }
}; };
tuskyApi.unregister(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) String deviceToken = pushNotificationClient.getDeviceToken();
Session session = new Session(getDomain(), getAccessToken(), deviceToken);
tuskyApi.unregister(session)
.enqueue(callback); .enqueue(callback);
} }
@ -256,7 +263,11 @@ public class BaseActivity extends AppCompatActivity {
} }
private String getPushNotificationTopic() { private String getPushNotificationTopic() {
return String.format("%s/%s/%s", getBaseUrl(), getAccessToken(), return String.format("%s/%s/#", getDomain(), getAccessToken());
pushNotificationClient.getDeviceToken()); }
private String getDomain() {
return getPrivatePreferences()
.getString("domain", null);
} }
} }

View file

@ -0,0 +1,28 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.entity;
public class Session {
public String instanceUrl;
public String accessToken;
public String deviceToken;
public Session(String instanceUrl, String accessToken, String deviceToken) {
this.instanceUrl = instanceUrl;
this.accessToken = accessToken;
this.deviceToken = deviceToken;
}
}

View file

@ -15,16 +15,16 @@
package com.keylesspalace.tusky.network; package com.keylesspalace.tusky.network;
import com.keylesspalace.tusky.entity.Session;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.FormUrlEncoded; import retrofit2.http.Body;
import retrofit2.http.POST; import retrofit2.http.POST;
public interface TuskyApi { public interface TuskyApi {
@FormUrlEncoded
@POST("/register") @POST("/register")
Call<ResponseBody> register(String instanceUrl, String accessToken, String deviceToken); Call<ResponseBody> register(@Body Session session);
@FormUrlEncoded
@POST("/unregister") @POST("/unregister")
Call<ResponseBody> unregister(String instanceUrl, String accessToken, String deviceToken); Call<ResponseBody> unregister(@Body Session session);
} }

View file

@ -1,257 +0,0 @@
package com.keylesspalace.tusky.service;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.text.Spanned;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.json.SpannedTypeAdapter;
import com.keylesspalace.tusky.json.StringWithEmoji;
import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
import com.keylesspalace.tusky.network.MastodonAPI;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.NotificationMaker;
import com.keylesspalace.tusky.util.OkHttpUtils;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import java.io.IOException;
import java.util.Locale;
import java.util.UUID;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class PushNotificationService extends Service {
private class LocalBinder extends Binder {
PushNotificationService getService() {
return PushNotificationService.this;
}
}
private static final String TAG = "PushNotificationService";
private static final String CLIENT_NAME = "TuskyMastodonClient";
private static final String TOPIC = "tusky/notification";
private static final int NOTIFY_ID = 666;
private final IBinder binder = new LocalBinder();
private MqttAndroidClient mqttAndroidClient;
private MastodonAPI mastodonApi;
@Override
public void onCreate() {
super.onCreate();
// Create the MQTT client.
String clientId = String.format(Locale.getDefault(), "%s/%s/%s", CLIENT_NAME,
System.currentTimeMillis(), UUID.randomUUID().toString());
String serverUri = getString(R.string.tusky_api_url);
mqttAndroidClient = new MqttAndroidClient(this, serverUri, clientId);
mqttAndroidClient.setCallback(new MqttCallbackExtended() {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
if (reconnect) {
subscribeToTopic();
}
}
@Override
public void connectionLost(Throwable cause) {
onConnectionLost();
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
onMessageReceived(new String(message.getPayload()));
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// This client is read-only, so this is unused.
}
});
// Open the MQTT connection.
MqttConnectOptions options = new MqttConnectOptions();
options.setAutomaticReconnect(true);
options.setCleanSession(false);
try {
mqttAndroidClient.connect(options, null, new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
DisconnectedBufferOptions options = new DisconnectedBufferOptions();
options.setBufferEnabled(true);
options.setBufferSize(100);
options.setPersistBuffer(false);
options.setDeleteOldestMessages(false);
mqttAndroidClient.setBufferOpts(options);
onConnectionSuccess();
subscribeToTopic();
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
onConnectionFailure();
}
});
} catch (MqttException e) {
Log.e(TAG, "An exception occurred while connecting. " + e.getMessage());
onConnectionFailure();
}
}
@Override
public void onDestroy() {
super.onDestroy();
disconnect();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
/** Subscribe to the push notification topic. */
public void subscribeToTopic() {
try {
mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
onConnectionSuccess();
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
onConnectionFailure();
}
});
} catch (MqttException e) {
Log.e(TAG, "An exception occurred while subscribing." + e.getMessage());
onConnectionFailure();
}
}
/** Unsubscribe from the push notification topic. */
public void unsubscribeToTopic() {
try {
mqttAndroidClient.unsubscribe(TOPIC);
} catch (MqttException e) {
Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage());
onConnectionFailure();
}
}
private void onConnectionSuccess() {
}
private void onConnectionFailure() {
}
private void onConnectionLost() {
}
private void onMessageReceived(String message) {
String notificationId = message; // TODO: finalize the form the messages will be received
Log.d(TAG, "Notification received: " + notificationId);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
getApplicationContext());
boolean enabled = preferences.getBoolean("notificationsEnabled", true);
if (!enabled) {
return;
}
createMastodonAPI();
mastodonApi.notification(notificationId).enqueue(new Callback<Notification>() {
@Override
public void onResponse(Call<Notification> call, Response<Notification> response) {
if (response.isSuccessful()) {
NotificationMaker.make(PushNotificationService.this, NOTIFY_ID,
response.body());
}
}
@Override
public void onFailure(Call<Notification> call, Throwable t) {}
});
}
/** Disconnect from the MQTT broker. */
public void disconnect() {
try {
mqttAndroidClient.disconnect();
} catch (MqttException ex) {
Log.e(TAG, "An exception occurred while disconnecting.");
onDisconnectFailed();
}
}
private void onDisconnectFailed() {
}
private void createMastodonAPI() {
SharedPreferences preferences = getSharedPreferences(
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
final String domain = preferences.getString("domain", null);
final String accessToken = preferences.getString("accessToken", null);
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request.Builder builder = originalRequest.newBuilder()
.header("Authorization", String.format("Bearer %s", accessToken));
Request newRequest = builder.build();
return chain.proceed(newRequest);
}
})
.build();
Gson gson = new GsonBuilder()
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + domain)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
mastodonApi = retrofit.create(MastodonAPI.class);
}
}

View file

@ -105,8 +105,21 @@ public class PushNotificationClient {
// This client is read-only, so this is unused. // This client is read-only, so this is unused.
} }
}); });
}
// Open the MQTT connection. private void flushQueuedActions() {
while (!queuedActions.isEmpty()) {
QueuedAction action = queuedActions.pop();
switch (action.type) {
case SUBSCRIBE: subscribeToTopic(action.topic); break;
case UNSUBSCRIBE: unsubscribeToTopic(action.topic); break;
case DISCONNECT: disconnect(); break;
}
}
}
/** Connect to the MQTT broker. */
public void connect() {
MqttConnectOptions options = new MqttConnectOptions(); MqttConnectOptions options = new MqttConnectOptions();
options.setAutomaticReconnect(true); options.setAutomaticReconnect(true);
options.setCleanSession(false); options.setCleanSession(false);
@ -140,20 +153,21 @@ public class PushNotificationClient {
} }
}); });
} catch (MqttException e) { } catch (MqttException e) {
Log.e(TAG, "An exception occurred while connecting. " + e.getMessage()); Log.e(TAG, "An exception occurred while connecpting. " + e.getMessage());
onConnectionFailure(); onConnectionFailure();
} }
} }
private void flushQueuedActions() { private void onConnectionSuccess() {
while (!queuedActions.isEmpty()) { Log.v(TAG, "The connection succeeded.");
QueuedAction action = queuedActions.pop(); }
switch (action.type) {
case SUBSCRIBE: subscribeToTopic(action.topic); break; private void onConnectionFailure() {
case UNSUBSCRIBE: unsubscribeToTopic(action.topic); break; Log.v(TAG, "The connection failed.");
case DISCONNECT: disconnect(); break; }
}
} private void onConnectionLost() {
Log.v(TAG, "The connection was lost.");
} }
/** Disconnect from the MQTT broker. */ /** Disconnect from the MQTT broker. */
@ -215,18 +229,6 @@ public class PushNotificationClient {
} }
} }
private void onConnectionSuccess() {
Log.v(TAG, "The connection succeeded.");
}
private void onConnectionFailure() {
Log.v(TAG, "The connection failed.");
}
private void onConnectionLost() {
Log.v(TAG, "The connection was lost.");
}
private void onMessageReceived(final Context context, String message) { private void onMessageReceived(final Context context, String message) {
String notificationId = message; // TODO: finalize the form the messages will be received String notificationId = message; // TODO: finalize the form the messages will be received