commit
a6abe569f9
@ -1,3 +1,4 @@ |
|||||||
/build |
/build |
||||||
app-release.apk |
app-release.apk |
||||||
app-google-release.apk |
app-google-release.apk |
||||||
|
src/main/res/raw/keystore_tusky_api.bks |
||||||
|
@ -1,55 +0,0 @@ |
|||||||
{ |
|
||||||
"project_info": { |
|
||||||
"project_number": "268851337880", |
|
||||||
"firebase_url": "https://tusky-62772.firebaseio.com", |
|
||||||
"project_id": "tusky-62772", |
|
||||||
"storage_bucket": "tusky-62772.appspot.com" |
|
||||||
}, |
|
||||||
"client": [ |
|
||||||
{ |
|
||||||
"client_info": { |
|
||||||
"mobilesdk_app_id": "1:268851337880:android:fc4111b1d145a00e", |
|
||||||
"android_client_info": { |
|
||||||
"package_name": "com.keylesspalace.tusky" |
|
||||||
} |
|
||||||
}, |
|
||||||
"oauth_client": [ |
|
||||||
{ |
|
||||||
"client_id": "268851337880-eie2ssto2d21bfihn9d1qupcrke8oebf.apps.googleusercontent.com", |
|
||||||
"client_type": 1, |
|
||||||
"android_info": { |
|
||||||
"package_name": "com.keylesspalace.tusky", |
|
||||||
"certificate_hash": "18d196307d6e928e99c2e0bb9818c01c38aff2f9" |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
"client_id": "268851337880-n19d05m282nirs1fc9kdd5n4of6je4fk.apps.googleusercontent.com", |
|
||||||
"client_type": 3 |
|
||||||
} |
|
||||||
], |
|
||||||
"api_key": [ |
|
||||||
{ |
|
||||||
"current_key": "AIzaSyCbJtSjuk4I3Jy8PdUaO3TaQOXubcOUElo" |
|
||||||
} |
|
||||||
], |
|
||||||
"services": { |
|
||||||
"analytics_service": { |
|
||||||
"status": 1 |
|
||||||
}, |
|
||||||
"appinvite_service": { |
|
||||||
"status": 2, |
|
||||||
"other_platform_oauth_client": [ |
|
||||||
{ |
|
||||||
"client_id": "268851337880-n19d05m282nirs1fc9kdd5n4of6je4fk.apps.googleusercontent.com", |
|
||||||
"client_type": 3 |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
"ads_service": { |
|
||||||
"status": 2 |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
], |
|
||||||
"configuration_version": "1" |
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||||
package="com.keylesspalace.tusky"> |
|
||||||
|
|
||||||
<application> |
|
||||||
<service |
|
||||||
android:name=".MessagingService" |
|
||||||
android:enabled="true" |
|
||||||
android:exported="true" /> |
|
||||||
</application> |
|
||||||
|
|
||||||
</manifest> |
|
@ -1,142 +0,0 @@ |
|||||||
/* 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; |
|
||||||
|
|
||||||
import android.app.IntentService; |
|
||||||
import android.content.Context; |
|
||||||
import android.content.Intent; |
|
||||||
import android.content.SharedPreferences; |
|
||||||
import android.preference.PreferenceManager; |
|
||||||
import android.text.Spanned; |
|
||||||
|
|
||||||
import com.google.gson.Gson; |
|
||||||
import com.google.gson.GsonBuilder; |
|
||||||
|
|
||||||
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.NotificationMaker; |
|
||||||
import com.keylesspalace.tusky.util.OkHttpUtils; |
|
||||||
|
|
||||||
import java.util.HashSet; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
import java.util.Set; |
|
||||||
|
|
||||||
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 MessagingService extends IntentService { |
|
||||||
public static final int NOTIFY_ID = 6; // This is an arbitrary number.
|
|
||||||
|
|
||||||
private MastodonAPI mastodonAPI; |
|
||||||
|
|
||||||
public MessagingService() { |
|
||||||
super("Tusky Pull Notification Service"); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void onHandleIntent(Intent intent) { |
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( |
|
||||||
getApplicationContext()); |
|
||||||
boolean enabled = preferences.getBoolean("notificationsEnabled", true); |
|
||||||
if (!enabled) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
createMastodonApi(); |
|
||||||
|
|
||||||
mastodonAPI.notifications(null, null, null).enqueue(new Callback<List<Notification>>() { |
|
||||||
@Override |
|
||||||
public void onResponse(Call<List<Notification>> call, |
|
||||||
Response<List<Notification>> response) { |
|
||||||
if (response.isSuccessful()) { |
|
||||||
onNotificationsReceived(response.body()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onFailure(Call<List<Notification>> call, Throwable t) {} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
private void onNotificationsReceived(List<Notification> notificationList) { |
|
||||||
SharedPreferences notificationsPreferences = getSharedPreferences( |
|
||||||
"Notifications", Context.MODE_PRIVATE); |
|
||||||
Set<String> currentIds = notificationsPreferences.getStringSet( |
|
||||||
"current_ids", new HashSet<String>()); |
|
||||||
for (Notification notification : notificationList) { |
|
||||||
String id = notification.id; |
|
||||||
if (!currentIds.contains(id)) { |
|
||||||
currentIds.add(id); |
|
||||||
NotificationMaker.make(this, NOTIFY_ID, notification); |
|
||||||
} |
|
||||||
} |
|
||||||
notificationsPreferences.edit() |
|
||||||
.putStringSet("current_ids", currentIds) |
|
||||||
.apply(); |
|
||||||
} |
|
||||||
|
|
||||||
public static String getInstanceToken() { |
|
||||||
// This is only used for the "google" build flavor, so this version is just a stub method.
|
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
@ -1,18 +0,0 @@ |
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||||
package="com.keylesspalace.tusky"> |
|
||||||
<application> |
|
||||||
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" /> |
|
||||||
|
|
||||||
<service android:name=".MyFirebaseInstanceIdService" android:exported="true"> |
|
||||||
<intent-filter> |
|
||||||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" /> |
|
||||||
</intent-filter> |
|
||||||
</service> |
|
||||||
|
|
||||||
<service android:name=".MessagingService" android:exported="true"> |
|
||||||
<intent-filter> |
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" /> |
|
||||||
</intent-filter> |
|
||||||
</service> |
|
||||||
</application> |
|
||||||
</manifest> |
|
@ -1,133 +0,0 @@ |
|||||||
/* 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>.
|
|
||||||
* |
|
||||||
* If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud |
|
||||||
* Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing |
|
||||||
* parts covered by the Google APIs Terms of Service, the licensors of this Program grant you |
|
||||||
* additional permission to convey the resulting work. */ |
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import android.content.SharedPreferences; |
|
||||||
import android.preference.PreferenceManager; |
|
||||||
import android.text.Spanned; |
|
||||||
|
|
||||||
import com.google.firebase.iid.FirebaseInstanceId; |
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService; |
|
||||||
import com.google.firebase.messaging.RemoteMessage; |
|
||||||
import com.google.gson.Gson; |
|
||||||
import com.google.gson.GsonBuilder; |
|
||||||
|
|
||||||
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 java.io.IOException; |
|
||||||
|
|
||||||
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 MessagingService extends FirebaseMessagingService { |
|
||||||
private MastodonAPI mastodonAPI; |
|
||||||
private static final String TAG = "MessagingService"; |
|
||||||
public static final int NOTIFY_ID = 666; |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onMessageReceived(RemoteMessage remoteMessage) { |
|
||||||
Log.d(TAG, remoteMessage.getFrom()); |
|
||||||
Log.d(TAG, remoteMessage.toString()); |
|
||||||
|
|
||||||
String notificationId = remoteMessage.getData().get("notification_id"); |
|
||||||
|
|
||||||
if (notificationId == null) { |
|
||||||
Log.e(TAG, "No notification ID in payload!!"); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
Log.d(TAG, 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(MessagingService.this, NOTIFY_ID, response.body()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onFailure(Call<Notification> call, Throwable t) {} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
public static String getInstanceToken() { |
|
||||||
return FirebaseInstanceId.getInstance().getToken(); |
|
||||||
} |
|
||||||
} |
|
@ -1,86 +0,0 @@ |
|||||||
/* 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>.
|
|
||||||
* |
|
||||||
* If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud |
|
||||||
* Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing |
|
||||||
* parts covered by the Google APIs Terms of Service, the licensors of this Program grant you |
|
||||||
* additional permission to convey the resulting work. */ |
|
||||||
|
|
||||||
package com.keylesspalace.tusky; |
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import android.content.SharedPreferences; |
|
||||||
|
|
||||||
import com.google.firebase.iid.FirebaseInstanceId; |
|
||||||
import com.google.firebase.iid.FirebaseInstanceIdService; |
|
||||||
import com.keylesspalace.tusky.network.TuskyAPI; |
|
||||||
import com.keylesspalace.tusky.util.Log; |
|
||||||
import com.keylesspalace.tusky.util.OkHttpUtils; |
|
||||||
|
|
||||||
import okhttp3.ResponseBody; |
|
||||||
import retrofit2.Call; |
|
||||||
import retrofit2.Callback; |
|
||||||
import retrofit2.Response; |
|
||||||
import retrofit2.Retrofit; |
|
||||||
|
|
||||||
public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService { |
|
||||||
private static final String TAG = "com.keylesspalace.tusky.MyFirebaseInstanceIdService"; |
|
||||||
|
|
||||||
private TuskyAPI tuskyAPI; |
|
||||||
|
|
||||||
protected void createTuskyAPI() { |
|
||||||
Retrofit retrofit = new Retrofit.Builder() |
|
||||||
.baseUrl(getString(R.string.tusky_api_url)) |
|
||||||
.client(OkHttpUtils.getCompatibleClient()) |
|
||||||
.build(); |
|
||||||
|
|
||||||
tuskyAPI = retrofit.create(TuskyAPI.class); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onTokenRefresh() { |
|
||||||
createTuskyAPI(); |
|
||||||
|
|
||||||
String refreshedToken = FirebaseInstanceId.getInstance().getToken(); |
|
||||||
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE); |
|
||||||
String accessToken = preferences.getString("accessToken", null); |
|
||||||
String domain = preferences.getString("domain", null); |
|
||||||
|
|
||||||
if (accessToken != null && domain != null) { |
|
||||||
tuskyAPI.unregister("https://" + domain, accessToken).enqueue(new Callback<ResponseBody>() { |
|
||||||
@Override |
|
||||||
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { |
|
||||||
Log.d(TAG, response.message()); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onFailure(Call<ResponseBody> call, Throwable t) { |
|
||||||
Log.d(TAG, t.getMessage()); |
|
||||||
} |
|
||||||
}); |
|
||||||
tuskyAPI.register("https://" + domain, accessToken, refreshedToken).enqueue(new Callback<ResponseBody>() { |
|
||||||
@Override |
|
||||||
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { |
|
||||||
Log.d(TAG, response.message()); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onFailure(Call<ResponseBody> call, Throwable t) { |
|
||||||
Log.d(TAG, t.getMessage()); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,237 @@ |
|||||||
|
package com.keylesspalace.tusky.util; |
||||||
|
|
||||||
|
import android.app.NotificationManager; |
||||||
|
import android.content.Context; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
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 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.MqttClient; |
||||||
|
import org.eclipse.paho.client.mqttv3.MqttConnectOptions; |
||||||
|
import org.eclipse.paho.client.mqttv3.MqttException; |
||||||
|
import org.eclipse.paho.client.mqttv3.MqttMessage; |
||||||
|
|
||||||
|
import java.io.InputStream; |
||||||
|
import java.util.ArrayDeque; |
||||||
|
import java.util.ArrayList; |
||||||
|
|
||||||
|
import static android.content.Context.NOTIFICATION_SERVICE; |
||||||
|
|
||||||
|
public class PushNotificationClient { |
||||||
|
private static final String TAG = "PushNotificationClient"; |
||||||
|
private static final int NOTIFY_ID = 666; |
||||||
|
|
||||||
|
private static class QueuedAction { |
||||||
|
enum Type { |
||||||
|
SUBSCRIBE, |
||||||
|
UNSUBSCRIBE, |
||||||
|
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 ArrayDeque<QueuedAction> queuedActions; |
||||||
|
private ArrayList<String> subscribedTopics; |
||||||
|
|
||||||
|
public PushNotificationClient(final @NonNull Context applicationContext, |
||||||
|
@NonNull String serverUri) { |
||||||
|
queuedActions = new ArrayDeque<>(); |
||||||
|
subscribedTopics = new ArrayList<>(); |
||||||
|
|
||||||
|
// Create the MQTT client.
|
||||||
|
String clientId = MqttClient.generateClientId(); |
||||||
|
mqttAndroidClient = new MqttAndroidClient(applicationContext, serverUri, clientId); |
||||||
|
mqttAndroidClient.setCallback(new MqttCallbackExtended() { |
||||||
|
@Override |
||||||
|
public void connectComplete(boolean reconnect, String serverURI) { |
||||||
|
if (reconnect) { |
||||||
|
flushQueuedActions(); |
||||||
|
for (String topic : subscribedTopics) { |
||||||
|
subscribeToTopic(topic); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectionLost(Throwable cause) { |
||||||
|
onConnectionLost(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void messageArrived(String topic, MqttMessage message) throws Exception { |
||||||
|
onMessageReceived(applicationContext, new String(message.getPayload())); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void deliveryComplete(IMqttDeliveryToken token) { |
||||||
|
// This client is read-only, so this is unused.
|
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
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(Context context) { |
||||||
|
MqttConnectOptions options = new MqttConnectOptions(); |
||||||
|
options.setAutomaticReconnect(true); |
||||||
|
options.setCleanSession(false); |
||||||
|
try { |
||||||
|
String password = context.getString(R.string.tusky_api_keystore_password); |
||||||
|
InputStream keystore = context.getResources().openRawResource(R.raw.keystore_tusky_api); |
||||||
|
try { |
||||||
|
options.setSocketFactory(mqttAndroidClient.getSSLSocketFactory(keystore, password)); |
||||||
|
} finally { |
||||||
|
IOUtils.closeQuietly(keystore); |
||||||
|
} |
||||||
|
mqttAndroidClient.connect(options).setActionCallback(new IMqttActionListener() { |
||||||
|
@Override |
||||||
|
public void onSuccess(IMqttToken asyncActionToken) { |
||||||
|
DisconnectedBufferOptions bufferOptions = new DisconnectedBufferOptions(); |
||||||
|
bufferOptions.setBufferEnabled(true); |
||||||
|
bufferOptions.setBufferSize(100); |
||||||
|
bufferOptions.setPersistBuffer(false); |
||||||
|
bufferOptions.setDeleteOldestMessages(false); |
||||||
|
mqttAndroidClient.setBufferOpts(bufferOptions); |
||||||
|
onConnectionSuccess(); |
||||||
|
flushQueuedActions(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onFailure(IMqttToken asyncActionToken, Throwable exception) { |
||||||
|
Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage() |
||||||
|
+ " " + exception.getCause()); |
||||||
|
onConnectionFailure(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} catch (MqttException e) { |
||||||
|
Log.e(TAG, "An exception occurred while connecpting. " + e.getMessage()); |
||||||
|
onConnectionFailure(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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."); |
||||||
|
} |
||||||
|
|
||||||
|
/** Disconnect from the MQTT broker. */ |
||||||
|
public void disconnect() { |
||||||
|
if (!mqttAndroidClient.isConnected()) { |
||||||
|
queuedActions.add(new QueuedAction(QueuedAction.Type.DISCONNECT)); |
||||||
|
return; |
||||||
|
} |
||||||
|
try { |
||||||
|
mqttAndroidClient.disconnect(); |
||||||
|
} catch (MqttException ex) { |
||||||
|
Log.e(TAG, "An exception occurred while disconnecting."); |
||||||
|
onDisconnectFailed(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void onDisconnectFailed() { |
||||||
|
Log.v(TAG, "Failed while disconnecting from the broker."); |
||||||
|
} |
||||||
|
|
||||||
|
/** Subscribe to the push notification topic. */ |
||||||
|
public void subscribeToTopic(final String topic) { |
||||||
|
if (!mqttAndroidClient.isConnected()) { |
||||||
|
queuedActions.add(new QueuedAction(QueuedAction.Type.SUBSCRIBE, topic)); |
||||||
|
return; |
||||||
|
} |
||||||
|
try { |
||||||
|
mqttAndroidClient.subscribe(topic, 0, null, new IMqttActionListener() { |
||||||
|
@Override |
||||||
|
public void onSuccess(IMqttToken asyncActionToken) { |
||||||
|
subscribedTopics.add(topic); |
||||||
|
onConnectionSuccess(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onFailure(IMqttToken asyncActionToken, Throwable exception) { |
||||||
|
Log.e(TAG, "An exception occurred while subscribing." + exception.getMessage()); |
||||||
|
onConnectionFailure(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} catch (MqttException e) { |
||||||
|
Log.e(TAG, "An exception occurred while subscribing." + e.getMessage()); |
||||||
|
onConnectionFailure(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Unsubscribe from the push notification topic. */ |
||||||
|
public void unsubscribeToTopic(String topic) { |
||||||
|
if (!mqttAndroidClient.isConnected()) { |
||||||
|
queuedActions.add(new QueuedAction(QueuedAction.Type.UNSUBSCRIBE, topic)); |
||||||
|
return; |
||||||
|
} |
||||||
|
try { |
||||||
|
mqttAndroidClient.unsubscribe(topic); |
||||||
|
subscribedTopics.remove(topic); |
||||||
|
} catch (MqttException e) { |
||||||
|
Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); |
||||||
|
onConnectionFailure(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void onMessageReceived(final Context context, String message) { |
||||||
|
Log.v(TAG, "Notification received: " + message); |
||||||
|
|
||||||
|
Gson gson = new GsonBuilder() |
||||||
|
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter()) |
||||||
|
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter()) |
||||||
|
.create(); |
||||||
|
Notification notification = gson.fromJson(message, Notification.class); |
||||||
|
|
||||||
|
NotificationMaker.make(context, NOTIFY_ID, notification); |
||||||
|
} |
||||||
|
|
||||||
|
public void clearNotifications(Context context) { |
||||||
|
((NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE))).cancel(NOTIFY_ID); |
||||||
|
} |
||||||
|
|
||||||
|
public String getDeviceToken() { |
||||||
|
return mqttAndroidClient.getClientId(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue