Setup client-side for integration with the wryk/tusky-api prototype.

This commit is contained in:
Vavassor 2017-05-19 21:28:12 -04:00
parent 73a5144741
commit e282f13fdc
4 changed files with 115 additions and 31 deletions

View File

@ -35,6 +35,8 @@ 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;
import com.keylesspalace.tusky.network.MastodonAPI; import com.keylesspalace.tusky.network.MastodonAPI;
import com.keylesspalace.tusky.network.TuskyApi;
import com.keylesspalace.tusky.util.Log;
import com.keylesspalace.tusky.util.OkHttpUtils; import com.keylesspalace.tusky.util.OkHttpUtils;
import com.keylesspalace.tusky.util.PushNotificationClient; import com.keylesspalace.tusky.util.PushNotificationClient;
@ -45,11 +47,17 @@ import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit; import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.converter.gson.GsonConverterFactory;
public class BaseActivity extends AppCompatActivity { public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity"; // logging tag
public MastodonAPI mastodonAPI; public MastodonAPI mastodonAPI;
public TuskyApi tuskyApi;
protected PushNotificationClient pushNotificationClient; protected PushNotificationClient pushNotificationClient;
protected Dispatcher mastodonApiDispatcher; protected Dispatcher mastodonApiDispatcher;
@ -59,7 +67,8 @@ public class BaseActivity extends AppCompatActivity {
redirectIfNotLoggedIn(); redirectIfNotLoggedIn();
createMastodonAPI(); createMastodonAPI();
createTuskyAPI(); createTuskyApi();
createPushNotificationClient();
/* There isn't presently a way to globally change the theme of a whole application at /* There isn't presently a way to globally change the theme of a whole application at
* runtime, just individual activities. So, each activity has to set its theme before any * runtime, just individual activities. So, each activity has to set its theme before any
@ -151,9 +160,19 @@ public class BaseActivity extends AppCompatActivity {
mastodonAPI = retrofit.create(MastodonAPI.class); mastodonAPI = retrofit.create(MastodonAPI.class);
} }
protected void createTuskyAPI() { protected void createTuskyApi() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + getString(R.string.tusky_api_domain))
.client(OkHttpUtils.getCompatibleClient())
.build();
tuskyApi = retrofit.create(TuskyApi.class);
}
protected void createPushNotificationClient() {
// TODO: Switch to ssl:// when TLS support is added.
pushNotificationClient = new PushNotificationClient(getApplicationContext(), pushNotificationClient = new PushNotificationClient(getApplicationContext(),
getString(R.string.tusky_api_url)); "tcp://" + getString(R.string.tusky_api_domain));
} }
protected void redirectIfNotLoggedIn() { protected void redirectIfNotLoggedIn() {
@ -187,10 +206,57 @@ public class BaseActivity extends AppCompatActivity {
} }
protected void enablePushNotifications() { protected void enablePushNotifications() {
pushNotificationClient.subscribeToTopic(); Callback<ResponseBody> callback = new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call,
retrofit2.Response<ResponseBody> response) {
if (response.isSuccessful()) {
pushNotificationClient.subscribeToTopic(getPushNotificationTopic());
} else {
onEnablePushNotificationsFailure();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
onEnablePushNotificationsFailure();
}
};
tuskyApi.register(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken())
.enqueue(callback);
}
private void onEnablePushNotificationsFailure() {
Log.e(TAG, "Enabling push notifications failed.");
} }
protected void disablePushNotifications() { protected void disablePushNotifications() {
pushNotificationClient.unsubscribeToTopic(); Callback<ResponseBody> callback = new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call,
retrofit2.Response<ResponseBody> response) {
if (response.isSuccessful()) {
pushNotificationClient.unsubscribeToTopic(getPushNotificationTopic());
} else {
onDisablePushNotificationsFailure();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
onDisablePushNotificationsFailure();
}
};
tuskyApi.unregister(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken())
.enqueue(callback);
}
private void onDisablePushNotificationsFailure() {
Log.e(TAG, "Disabling push notifications failed.");
}
private String getPushNotificationTopic() {
return String.format("%s/%s/%s", getBaseUrl(), getAccessToken(),
pushNotificationClient.getDeviceToken());
} }
} }

View File

@ -17,15 +17,14 @@ package com.keylesspalace.tusky.network;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded; import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST; import retrofit2.http.POST;
public interface TuskyAPI { public interface TuskyApi {
@FormUrlEncoded @FormUrlEncoded
@POST("/register") @POST("/register")
Call<ResponseBody> register(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken, @Field("device_token") String deviceToken); Call<ResponseBody> register(String instanceUrl, String accessToken, String deviceToken);
@FormUrlEncoded @FormUrlEncoded
@POST("/unregister") @POST("/unregister")
Call<ResponseBody> unregister(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken); Call<ResponseBody> unregister(String instanceUrl, String accessToken, String deviceToken);
} }

View File

@ -29,6 +29,7 @@ import org.eclipse.paho.client.mqttv3.MqttMessage;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -43,23 +44,37 @@ import static android.content.Context.NOTIFICATION_SERVICE;
public class PushNotificationClient { public class PushNotificationClient {
private static final String TAG = "PushNotificationClient"; private static final String TAG = "PushNotificationClient";
private static final String TOPIC = "tusky/notification";
private static final int NOTIFY_ID = 666; private static final int NOTIFY_ID = 666;
private enum QueuedAction { private static class QueuedAction {
enum Type {
SUBSCRIBE, SUBSCRIBE,
UNSUBSCRIBE, UNSUBSCRIBE,
DISCONNECT, DISCONNECT,
} }
Type type;
String topic;
QueuedAction(Type type) {
this.type = type;
}
QueuedAction(Type type, String topic) {
this.type = type;
this.topic = topic;
}
}
private MqttAndroidClient mqttAndroidClient; private MqttAndroidClient mqttAndroidClient;
private MastodonAPI mastodonApi; private MastodonAPI mastodonApi;
private ArrayDeque<QueuedAction> queuedActions; private ArrayDeque<QueuedAction> queuedActions;
private boolean subscribed; private ArrayList<String> subscribedTopics;
public PushNotificationClient(final @NonNull Context applicationContext, public PushNotificationClient(final @NonNull Context applicationContext,
@NonNull String serverUri) { @NonNull String serverUri) {
queuedActions = new ArrayDeque<>(); queuedActions = new ArrayDeque<>();
subscribedTopics = new ArrayList<>();
// Create the MQTT client. // Create the MQTT client.
String clientId = MqttClient.generateClientId(); String clientId = MqttClient.generateClientId();
@ -69,8 +84,8 @@ public class PushNotificationClient {
public void connectComplete(boolean reconnect, String serverURI) { public void connectComplete(boolean reconnect, String serverURI) {
if (reconnect) { if (reconnect) {
flushQueuedActions(); flushQueuedActions();
if (subscribed) { for (String topic : subscribedTopics) {
subscribeToTopic(); subscribeToTopic(topic);
} }
} }
} }
@ -133,9 +148,9 @@ public class PushNotificationClient {
private void flushQueuedActions() { private void flushQueuedActions() {
while (!queuedActions.isEmpty()) { while (!queuedActions.isEmpty()) {
QueuedAction action = queuedActions.pop(); QueuedAction action = queuedActions.pop();
switch (action) { switch (action.type) {
case SUBSCRIBE: subscribeToTopic(); break; case SUBSCRIBE: subscribeToTopic(action.topic); break;
case UNSUBSCRIBE: unsubscribeToTopic(); break; case UNSUBSCRIBE: unsubscribeToTopic(action.topic); break;
case DISCONNECT: disconnect(); break; case DISCONNECT: disconnect(); break;
} }
} }
@ -144,7 +159,7 @@ public class PushNotificationClient {
/** Disconnect from the MQTT broker. */ /** Disconnect from the MQTT broker. */
public void disconnect() { public void disconnect() {
if (!mqttAndroidClient.isConnected()) { if (!mqttAndroidClient.isConnected()) {
queuedActions.add(QueuedAction.DISCONNECT); queuedActions.add(new QueuedAction(QueuedAction.Type.DISCONNECT));
return; return;
} }
try { try {
@ -160,16 +175,16 @@ public class PushNotificationClient {
} }
/** Subscribe to the push notification topic. */ /** Subscribe to the push notification topic. */
public void subscribeToTopic() { public void subscribeToTopic(final String topic) {
if (!mqttAndroidClient.isConnected()) { if (!mqttAndroidClient.isConnected()) {
queuedActions.add(QueuedAction.SUBSCRIBE); queuedActions.add(new QueuedAction(QueuedAction.Type.SUBSCRIBE, topic));
return; return;
} }
try { try {
mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { mqttAndroidClient.subscribe(topic, 0, null, new IMqttActionListener() {
@Override @Override
public void onSuccess(IMqttToken asyncActionToken) { public void onSuccess(IMqttToken asyncActionToken) {
subscribed = true; subscribedTopics.add(topic);
onConnectionSuccess(); onConnectionSuccess();
} }
@ -186,14 +201,14 @@ public class PushNotificationClient {
} }
/** Unsubscribe from the push notification topic. */ /** Unsubscribe from the push notification topic. */
public void unsubscribeToTopic() { public void unsubscribeToTopic(String topic) {
if (!mqttAndroidClient.isConnected()) { if (!mqttAndroidClient.isConnected()) {
queuedActions.add(QueuedAction.UNSUBSCRIBE); queuedActions.add(new QueuedAction(QueuedAction.Type.UNSUBSCRIBE, topic));
return; return;
} }
try { try {
mqttAndroidClient.unsubscribe(TOPIC); mqttAndroidClient.unsubscribe(topic);
subscribed = false; subscribedTopics.remove(topic);
} catch (MqttException e) { } catch (MqttException e) {
Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage());
onConnectionFailure(); onConnectionFailure();
@ -271,4 +286,8 @@ public class PushNotificationClient {
public void clearNotifications(Context context) { public void clearNotifications(Context context) {
((NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE))).cancel(NOTIFY_ID); ((NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE))).cancel(NOTIFY_ID);
} }
public String getDeviceToken() {
return mqttAndroidClient.getClientId();
}
} }

View File

@ -2,7 +2,7 @@
<resources> <resources>
<string name="app_name" translatable="false">Tusky</string> <string name="app_name" translatable="false">Tusky</string>
<string name="app_website" translatable="false">https://tusky.keylesspalace.com</string> <string name="app_website" translatable="false">https://tusky.keylesspalace.com</string>
<string name="tusky_api_url" translatable="false">tcp://tuskyapi.keylesspalace.com</string> <string name="tusky_api_domain" translatable="false">tuskyapi.keylesspalace.com</string>
<string name="tusky_api_keystore_password" translatable="false">your_password_here</string> <string name="tusky_api_keystore_password" translatable="false">your_password_here</string>
<string name="oauth_scheme" translatable="false">oauth2redirect</string> <string name="oauth_scheme" translatable="false">oauth2redirect</string>