Commit b228661c authored by Yusuke Iwaki's avatar Yusuke Iwaki Committed by GitHub

Merge pull request #124 from RocketChat/feature/gcm-push

Push notifications using the Cordova plugin code
parents 0b7927a0 37586be8
......@@ -105,6 +105,8 @@ dependencies {
compile 'com.google.firebase:firebase-core:10.0.0'
compile 'com.google.firebase:firebase-crash:10.0.0'
compile 'com.google.android.gms:play-services-gcm:10.0.0'
compile rootProject.ext.okhttp3
compile rootProject.ext.picasso
......
......@@ -4,6 +4,12 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<permission
android:name="chat.rocket.android.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="chat.rocket.android.permission.C2D_MESSAGE" />
<application
android:name=".RocketChatApplication"
......@@ -35,6 +41,34 @@
<service android:name=".service.RocketChatService" />
<service android:name=".service.notification.NotificationDismissalCallbackService" />
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.example.gcm" />
</intent-filter>
</receiver>
<service
android:name=".push.gcm.GCMIntentService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
<service
android:name=".push.gcm.GcmInstanceIDListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID" />
</intent-filter>
</service>
<service
android:name=".push.gcm.GcmRegistrationIntentService"
android:exported="false" />
</application>
</manifest>
......@@ -3,6 +3,8 @@ package chat.rocket.android;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.UUID;
/**
* sharedpreference-based cache.
*/
......@@ -10,10 +12,45 @@ public class RocketChatCache {
public static final String KEY_SELECTED_SERVER_CONFIG_ID = "selectedServerConfigId";
public static final String KEY_SELECTED_ROOM_ID = "selectedRoomId";
private static final String PUSH_ID = "pushId";
/**
* get SharedPreference instance for RocketChat application cache.
*/
public static SharedPreferences get(Context context) {
return context.getSharedPreferences("cache", Context.MODE_PRIVATE);
}
public static String getSelectedServerConfigId(Context context) {
return get(context).getString(KEY_SELECTED_SERVER_CONFIG_ID, "");
}
public static void setSelectedServerConfigId(Context context, String serverConfigId) {
setString(get(context), KEY_SELECTED_SERVER_CONFIG_ID, serverConfigId);
}
public static String getSelectedRoomId(Context context) {
return get(context).getString(KEY_SELECTED_ROOM_ID, "");
}
public static void setSelectedRoomId(Context context, String roomId) {
setString(get(context), KEY_SELECTED_ROOM_ID, roomId);
}
public static String getPushId(Context context) {
SharedPreferences preferences = get(context);
String pushId = null;
if (!preferences.contains(PUSH_ID)) {
// generates one and save
pushId = UUID.randomUUID().toString().replace("-", "");
setString(preferences, PUSH_ID, pushId);
}
return preferences.getString(PUSH_ID, pushId);
}
private static void setString(SharedPreferences preferences, String key, String value) {
SharedPreferences.Editor editor = preferences.edit();
editor.putString(key, value);
editor.apply();
}
}
......@@ -9,6 +9,7 @@ import chat.rocket.android.LaunchUtil;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.push.PushConstants;
import chat.rocket.android.realm_helper.RealmListObserver;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android.service.RocketChatService;
......@@ -39,19 +40,31 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent intent = getIntent();
if (intent != null) {
if (intent.hasExtra(ServerConfig.ID)) {
SharedPreferences.Editor editor = RocketChatCache.get(this).edit();
editor.putString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID,
intent.getStringExtra(ServerConfig.ID));
if (intent.hasExtra("roomId")) {
editor.putString(RocketChatCache.KEY_SELECTED_ROOM_ID, intent.getStringExtra("roomId"));
}
editor.apply();
}
onIntent(getIntent());
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
onIntent(intent);
}
private void onIntent(Intent intent) {
if (intent == null) {
return;
}
if (intent.hasExtra(PushConstants.SERVER_CONFIG_ID)) {
SharedPreferences.Editor editor = RocketChatCache.get(this).edit();
editor.putString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID,
intent.getStringExtra(PushConstants.SERVER_CONFIG_ID));
if (intent.hasExtra(PushConstants.ROOM_ID)) {
editor.putString(RocketChatCache.KEY_SELECTED_ROOM_ID,
intent.getStringExtra(PushConstants.ROOM_ID));
}
editor.apply();
}
}
......
......@@ -24,6 +24,7 @@ import hugo.weaving.DebugLog;
/**
* Utility class for creating/handling MethodCall or RPC.
*
* TODO: separate method into several manager classes (SubscriptionManager, MessageManager, ...).
*/
public class MethodCallHelper {
......@@ -37,9 +38,8 @@ public class MethodCallHelper {
protected final RealmHelper realmHelper;
protected final DDPClientWrapper ddpClient;
@Deprecated
/**
* Deprecated. use MethodCall(Context, String) instead.
* initialize with ServerConfigId.
*/
public MethodCallHelper(String serverConfigId) {
this(null, serverConfigId);
......
package chat.rocket.android.api;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import bolts.Task;
import chat.rocket.android.realm_helper.RealmHelper;
public class PushHelper extends MethodCallHelper {
public PushHelper(String serverConfigId) {
super(serverConfigId);
}
public PushHelper(Context context, String serverConfigId) {
super(context, serverConfigId);
}
public PushHelper(RealmHelper realmHelper,
DDPClientWrapper ddpClient) {
super(realmHelper, ddpClient);
}
public Task<Void> pushUpdate(@NonNull String pushId, @NonNull String token,
@Nullable String userId) {
return call("raix:push-update", TIMEOUT_MS, () -> {
JSONObject param = new PushUpdate(pushId, token, userId).toJson();
return new JSONArray().put(param);
}).onSuccessTask(task -> Task.forResult(null));
}
public Task<Void> pushSetUser(String pushId) {
return call("raix:push-setuser", TIMEOUT_MS, () -> new JSONArray().put(pushId))
.onSuccessTask(task -> Task.forResult(null));
}
private static class PushUpdate {
private String pushId;
private String gcmToken;
private String userId;
PushUpdate(@NonNull String pushId, @NonNull String gcmToken, @Nullable String userId) {
this.pushId = pushId;
this.gcmToken = gcmToken;
this.userId = userId;
}
JSONObject toJson() throws JSONException {
JSONObject param = new JSONObject();
param.put("id", pushId);
param.put("appName", "main");
param.put("userId", userId != null ? userId : JSONObject.NULL);
param.put("metadata", new JSONObject());
JSONObject tokenParam = new JSONObject();
tokenParam.put("gcm", gcmToken);
param.put("token", tokenParam);
return param;
}
}
}
......@@ -2,7 +2,6 @@ package chat.rocket.android.fragment.server_config;
import android.support.design.widget.Snackbar;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONObject;
import chat.rocket.android.R;
......@@ -52,17 +51,11 @@ public class InputHostnameFragment extends AbstractServerConfigFragment {
@Override
public void isNotValid() {
getActivity().runOnUiThread(() ->
Toast.makeText(getActivity(), R.string.input_hostname_invalid_server_message,
Toast.LENGTH_SHORT).show());
showError(getString(R.string.input_hostname_invalid_server_message)));
}
});
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onDestroyView() {
serverConfigObserver.unsub();
......@@ -75,7 +68,7 @@ public class InputHostnameFragment extends AbstractServerConfigFragment {
return TextUtils.or(TextUtils.or(editor.getText(), editor.getHint()), "").toString();
}
private void onServerValid(String hostname) {
private void onServerValid(final String hostname) {
RocketChatCache.get(getContext()).edit()
.putString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID, serverConfigId)
.apply();
......
......@@ -62,11 +62,9 @@ public class LoginFragment extends AbstractServerConfigFragment {
});
final View btnUserRegistration = rootView.findViewById(R.id.btn_user_registration);
btnUserRegistration.setOnClickListener(view -> {
UserRegistrationDialogFragment.create(serverConfigId,
txtUsername.getText().toString(), txtPasswd.getText().toString())
.show(getFragmentManager(), UserRegistrationDialogFragment.class.getSimpleName());
});
btnUserRegistration.setOnClickListener(view -> UserRegistrationDialogFragment.create(serverConfigId,
txtUsername.getText().toString(), txtPasswd.getText().toString())
.show(getFragmentManager(), UserRegistrationDialogFragment.class.getSimpleName()));
}
private void showError(String errString) {
......
......@@ -22,7 +22,7 @@ public class ServerPolicyHelper {
return "demo.rocket.chat";
}
return removeProtocol(enforceDefaultHost(hostname));
return removeTrailingSlash(removeProtocol(enforceDefaultHost(hostname)));
}
public static void isApiVersionValid(@NonNull OkHttpClient client, @NonNull String host,
......@@ -71,6 +71,15 @@ public class ServerPolicyHelper {
return hostname.replace("http://", "").replace("https://", "");
}
private static String removeTrailingSlash(String hostname) {
if (hostname.charAt(hostname.length() - 1) != '/') {
// no need for a regex - just return it
return hostname;
}
return hostname.replaceAll("/+$", "");
}
private static boolean isValid(ResponseBody body) {
if (body == null || body.contentLength() == 0) {
return false;
......
......@@ -20,6 +20,7 @@ public class ServerConfig extends RealmObject {
public static final String STATE = "state";
public static final String SESSION = "session";
public static final String ERROR = "error";
public static final String SYNC_PUSH_TOKEN = "syncPushToken";
public static final int STATE_READY = 0;
public static final int STATE_CONNECTING = 1;
......@@ -31,6 +32,7 @@ public class ServerConfig extends RealmObject {
private int state;
private String session;
private String error;
private boolean syncPushToken;
/**
* Log the server connection is lost due to some exception.
......@@ -100,4 +102,12 @@ public class ServerConfig extends RealmObject {
public void setError(String error) {
this.error = error;
}
public boolean shouldSyncPushToken() {
return syncPushToken;
}
public void setSyncPushToken(boolean syncPushToken) {
this.syncPushToken = syncPushToken;
}
}
package chat.rocket.android.push;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.support.v4.app.RemoteInput;
import chat.rocket.android.push.gcm.GCMIntentService;
public class BackgroundActionButtonHandler extends BroadcastReceiver implements PushConstants {
private static final String LOG_TAG = "BgActionButtonHandler";
@Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
Log.d(LOG_TAG, "BackgroundActionButtonHandler = " + extras);
int notId = intent.getIntExtra(NOT_ID, 0);
Log.d(LOG_TAG, "not id = " + notId);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(GCMIntentService.getAppName(context), notId);
if (extras == null) {
return;
}
Bundle originalExtras = extras.getBundle(PUSH_BUNDLE);
originalExtras.putBoolean(FOREGROUND, false);
originalExtras.putBoolean(COLDSTART, false);
originalExtras.putString(ACTION_CALLBACK, extras.getString(CALLBACK));
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
String inputString = remoteInput.getCharSequence(INLINE_REPLY).toString();
Log.d(LOG_TAG, "response: " + inputString);
originalExtras.putString(INLINE_REPLY, inputString);
}
}
}
package chat.rocket.android.push;
public interface PushConstants {
String COM_ADOBE_PHONEGAP_PUSH = "com.adobe.phonegap.push";
String REGISTRATION_ID = "registrationId";
String FOREGROUND = "foreground";
String TITLE = "title";
String NOT_ID = "notId";
String PUSH_BUNDLE = "pushBundle";
String ICON = "icon";
String ICON_COLOR = "iconColor";
String SOUND = "sound";
String SOUND_DEFAULT = "default";
String SOUND_RINGTONE = "ringtone";
String VIBRATE = "vibrate";
String ACTIONS = "actions";
String CALLBACK = "callback";
String ACTION_CALLBACK = "actionCallback";
String DRAWABLE = "drawable";
String MSGCNT = "msgcnt";
String VIBRATION_PATTERN = "vibrationPattern";
String STYLE = "style";
String SUMMARY_TEXT = "summaryText";
String PICTURE = "picture";
String GCM_N = "gcm.n.";
String GCM_NOTIFICATION = "gcm.notification";
String GCM_NOTIFICATION_BODY = "gcm.notification.body";
String UA_PREFIX = "com.urbanairship.push";
String PARSE_COM_DATA = "data";
String ALERT = "alert";
String MESSAGE = "message";
String BODY = "body";
String SOUNDNAME = "soundname";
String LED_COLOR = "ledColor";
String PRIORITY = "priority";
String IMAGE = "image";
String STYLE_INBOX = "inbox";
String STYLE_PICTURE = "picture";
String STYLE_TEXT = "text";
String BADGE = "badge";
String INITIALIZE = "init";
String SUBSCRIBE = "subscribe";
String UNSUBSCRIBE = "unsubscribe";
String UNREGISTER = "unregister";
String EXIT = "exit";
String FINISH = "finish";
String HAS_PERMISSION = "hasPermission";
String ANDROID = "android";
String SENDER_ID = "senderID";
String CLEAR_NOTIFICATIONS = "clearNotifications";
String COLDSTART = "coldstart";
String ADDITIONAL_DATA = "additionalData";
String COUNT = "count";
String FROM = "from";
String COLLAPSE_KEY = "collapse_key";
String FORCE_SHOW = "forceShow";
String GCM = "GCM";
String CONTENT_AVAILABLE = "content-available";
String TOPICS = "topics";
String SET_APPLICATION_ICON_BADGE_NUMBER = "setApplicationIconBadgeNumber";
String CLEAR_ALL_NOTIFICATIONS = "clearAllNotifications";
String VISIBILITY = "visibility";
String INLINE_REPLY = "inlineReply";
String LOC_KEY = "locKey";
String LOC_DATA = "locData";
String TWILIO_BODY = "twi_body";
String TWILIO_TITLE = "twi_title";
String TWILIO_SOUND = "twi_sound";
String START_IN_BACKGROUND = "cdvStartInBackground";
String FORCE_START = "force-start";
// RC specific constants
String SERVER_CONFIG_ID = "serverConfigId";
String ROOM_ID = "roomId";
}
\ No newline at end of file
package chat.rocket.android.push.gcm;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.gcm.GcmListenerService;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Iterator;
import chat.rocket.android.push.PushConstants;
import chat.rocket.android.push.PushNotificationHandler;
import chat.rocket.android.push.interactors.DefaultPushInteractor;
import chat.rocket.android.push.interactors.PushInteractor;
@SuppressLint("NewApi")
public class GCMIntentService extends GcmListenerService implements PushConstants {
private static final String LOG_TAG = "GCMIntentService";
@Override
public void onMessageReceived(String from, Bundle extras) {
Log.d(LOG_TAG, "onMessage - from: " + from);
if (extras == null) {
return;
}
Context applicationContext = getApplicationContext();
PushInteractor pushInteractor = new DefaultPushInteractor();
extras = normalizeExtras(applicationContext, extras);
PushNotificationHandler pushNotificationHandler = new PushNotificationHandler();
pushNotificationHandler.showNotificationIfPossible(applicationContext, pushInteractor, extras);
}
/*
* Change a values key in the extras bundle
*/
private void replaceKey(Context context, String oldKey, String newKey, Bundle extras,
Bundle newExtras) {
Object value = extras.get(oldKey);
if (value == null) {
return;
}
if (value instanceof String) {
value = localizeKey(context, newKey, (String) value);
newExtras.putString(newKey, (String) value);
} else if (value instanceof Boolean) {
newExtras.putBoolean(newKey, (Boolean) value);
} else if (value instanceof Number) {
newExtras.putDouble(newKey, ((Number) value).doubleValue());
} else {
newExtras.putString(newKey, String.valueOf(value));
}
}
/*
* Normalize localization for key
*/
private String localizeKey(Context context, String key, String value) {
if (key.equals(TITLE) || key.equals(MESSAGE) || key.equals(SUMMARY_TEXT)) {
try {
JSONObject localeObject = new JSONObject(value);
String localeKey = localeObject.getString(LOC_KEY);
ArrayList<String> localeFormatData = new ArrayList<>();
if (!localeObject.isNull(LOC_DATA)) {
String localeData = localeObject.getString(LOC_DATA);
JSONArray localeDataArray = new JSONArray(localeData);
for (int i = 0, size = localeDataArray.length(); i < size; i++) {
localeFormatData.add(localeDataArray.getString(i));
}
}
String packageName = context.getPackageName();
Resources resources = context.getResources();
int resourceId = resources.getIdentifier(localeKey, "string", packageName);
if (resourceId != 0) {
return resources.getString(resourceId, localeFormatData.toArray());
} else {
Log.d(LOG_TAG, "can't find resource for locale key = " + localeKey);
return value;
}
} catch (JSONException e) {
Log.d(LOG_TAG, "no locale found for key = " + key + ", error " + e.getMessage());
return value;
}
}
return value;
}
/*
* Replace alternate keys with our canonical value
*/
private String normalizeKey(String key) {
if (key.equals(BODY) || key.equals(ALERT) || key.equals(GCM_NOTIFICATION_BODY) || key
.equals(TWILIO_BODY)) {
return MESSAGE;
} else if (key.equals(TWILIO_TITLE)) {
return TITLE;
} else if (key.equals(MSGCNT) || key.equals(BADGE)) {
return COUNT;
} else if (key.equals(SOUNDNAME) || key.equals(TWILIO_SOUND)) {
return SOUND;
} else if (key.startsWith(GCM_NOTIFICATION)) {
return key.substring(GCM_NOTIFICATION.length() + 1, key.length());
} else if (key.startsWith(GCM_N)) {
return key.substring(GCM_N.length() + 1, key.length());
} else if (key.startsWith(UA_PREFIX)) {
key = key.substring(UA_PREFIX.length() + 1, key.length());
return key.toLowerCase();
} else {
return key;
}
}
/*
* Parse bundle into normalized keys.
*/
private Bundle normalizeExtras(Context context, Bundle extras) {
Log.d(LOG_TAG, "normalize extras");
Iterator<String> keyIterator = extras.keySet().iterator();
Bundle newExtras = new Bundle();
while (keyIterator.hasNext()) {
String key = keyIterator.next();
Log.d(LOG_TAG, "key = " + key);
// If normalizeKeythe key is "data" or "message" and the value is a json object extract
// This is to support parse.com and other services. Issue #147 and pull #218
if (key.equals(PARSE_COM_DATA) || key.equals(MESSAGE)) {
Object json = extras.get(key);
// Make sure data is json object stringified
if (json instanceof String && ((String) json).startsWith("{")) {
Log.d(LOG_TAG, "extracting nested message data from key = " + key);
try {
// If object contains message keys promote each value to the root of the bundle
JSONObject data = new JSONObject((String) json);
if (data.has(ALERT) || data.has(MESSAGE) || data.has(BODY) || data.has(TITLE)) {
Iterator<String> jsonIter = data.keys();
while (jsonIter.hasNext()) {
String jsonKey = jsonIter.next();
Log.d(LOG_TAG, "key = data/" + jsonKey);
String value = data.getString(jsonKey);
jsonKey = normalizeKey(jsonKey);
value = localizeKey(context, jsonKey, value);
newExtras.putString(jsonKey, value);
}
}
} catch (JSONException e) {
Log.e(LOG_TAG, "normalizeExtras: JSON exception");
}
}
} else if (key.equals(("notification"))) {
Bundle value = extras.getBundle(key);
Iterator<String> iterator = value.keySet().iterator();
while (iterator.hasNext()) {
String notifkey = iterator.next();
Log.d(LOG_TAG, "notifkey = " + notifkey);
String newKey = normalizeKey(notifkey);
Log.d(LOG_TAG, "replace key " + notifkey + " with " + newKey);
String valueData = value.getString(notifkey);
valueData = localizeKey(context, newKey, valueData);
newExtras.putString(newKey, valueData);
}
continue;
}
String newKey = normalizeKey(key);
Log.d(LOG_TAG, "replace key " + key + " with " + newKey);
replaceKey(context, key, newKey, extras, newExtras);
} // while
return newExtras;
}
public static String getAppName(Context context) {
CharSequence appName = context.getPackageManager()
.getApplicationLabel(context.getApplicationInfo());
return (String) appName;
}
}
\ No newline at end of file
package chat.rocket.android.push.gcm;
import android.content.Intent;
import com.google.android.gms.iid.InstanceIDListenerService;
import java.util.List;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ddp.PublicSetting;
import chat.rocket.android.model.ddp.PublicSettingsConstants;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore;
public class GcmInstanceIDListenerService extends InstanceIDListenerService {
@Override
public void onTokenRefresh() {
List<ServerConfig> serverConfigs = getServerConfigs();
updateSyncPushToken(serverConfigs);
if (!shouldRefreshToken(serverConfigs)) {
return;
}
Intent intent = new Intent(this, GcmRegistrationIntentService.class);
startService(intent);
}
private List<ServerConfig> getServerConfigs() {
return RealmStore.getDefault().executeTransactionForReadResults(
realm -> realm.where(ServerConfig.class).findAll());
}
private void updateSyncPushToken(List<ServerConfig> serverConfigs) {
final RealmHelper realmHelper = RealmStore.getDefault();
for (final ServerConfig serverConfig : serverConfigs) {
final RealmHelper serverRealmHelper = RealmStore
.getOrCreate(serverConfig.getServerConfigId());
boolean isPushEnable = PublicSetting
.getBoolean(serverRealmHelper, PublicSettingsConstants.Push.ENABLE, false);
String senderId = PublicSetting
.getString(serverRealmHelper, PublicSettingsConstants.Push.GCM_PROJECT_NUMBER, "").trim();
serverConfig.setSyncPushToken(isPushEnable && !"".equals(senderId));
realmHelper.executeTransaction(realm -> realm.copyToRealmOrUpdate(serverConfig));
}
}
private boolean shouldRefreshToken(List<ServerConfig> serverConfigs) {
for (ServerConfig serverConfig : serverConfigs) {
if (serverConfig.shouldSyncPushToken()) {
return true;
}
}
return false;
}
}
\ No newline at end of file
package chat.rocket.android.push.gcm;
import android.app.IntentService;
import android.content.Intent;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;
import java.io.IOException;
import java.util.List;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.PushHelper;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ddp.PublicSetting;
import chat.rocket.android.model.ddp.PublicSettingsConstants;
import chat.rocket.android.model.ddp.User;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore;
public class GcmRegistrationIntentService extends IntentService {
public GcmRegistrationIntentService() {
super("GcmRegistrationIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
final List<ServerConfig> serverConfigs = getServerConfigs();
for (ServerConfig serverConfig : serverConfigs) {
sendTokenTo(serverConfig);
}
}
private List<ServerConfig> getServerConfigs() {
return RealmStore.getDefault().executeTransactionForReadResults(
realm -> realm.where(ServerConfig.class).findAll());
}
private void sendTokenTo(final ServerConfig serverConfig) {
if (!serverConfig.shouldSyncPushToken()) {
return;
}
final RealmHelper realmHelper = RealmStore.get(serverConfig.getServerConfigId());
if (realmHelper == null) {
return;
}
final String senderId = PublicSetting
.getString(realmHelper, PublicSettingsConstants.Push.GCM_PROJECT_NUMBER, "").trim();
if ("".equals(senderId)) {
markRefreshAsDone(serverConfig);
return;
}
try {
final String token = getToken(senderId);
final User currentUser = realmHelper.executeTransactionForRead(realm ->
User.queryCurrentUser(realm).findFirst());
new PushHelper(serverConfig.getServerConfigId()).pushUpdate(
RocketChatCache.getPushId(this), token, currentUser != null ? currentUser.getId() : null)
.onSuccess(task -> {
markRefreshAsDone(serverConfig);
return task;
});
} catch (Exception e) {
}
}
private String getToken(String senderId) throws IOException {
return InstanceID.getInstance(this)
.getToken(senderId, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
}
private void markRefreshAsDone(ServerConfig serverConfig) {
serverConfig.setSyncPushToken(false);
RealmStore.getDefault().executeTransaction(realm -> realm.copyToRealm(serverConfig));
}
}
\ No newline at end of file
package chat.rocket.android.push.interactors;
import chat.rocket.android.helper.ServerPolicyHelper;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.realm_helper.RealmStore;
public class DefaultPushInteractor implements PushInteractor {
@Override
public String getServerConfigId(String hostname) {
final ServerConfig serverConfig = RealmStore.getDefault()
.executeTransactionForRead(
realm -> realm.where(ServerConfig.class)
.equalTo(ServerConfig.HOSTNAME, ServerPolicyHelper.enforceHostname(hostname))
.findFirst());
return serverConfig != null ? serverConfig.getServerConfigId() : "";
}
}
package chat.rocket.android.push.interactors;
public interface PushInteractor {
String getServerConfigId(String hostname);
}
......@@ -12,6 +12,7 @@ import bolts.Continuation;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android.api.DDPClientWrapper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
......@@ -29,7 +30,7 @@ import chat.rocket.android.service.observer.GetUsersOfRoomsProcedureObserver;
import chat.rocket.android.service.observer.LoadMessageProcedureObserver;
import chat.rocket.android.service.observer.MethodCallObserver;
import chat.rocket.android.service.observer.NewMessageObserver;
import chat.rocket.android.service.observer.NotificationItemObserver;
import chat.rocket.android.service.observer.PushSettingsObserver;
import chat.rocket.android.service.observer.ReactiveNotificationManager;
import chat.rocket.android.service.observer.SessionObserver;
import chat.rocket.android.service.observer.TokenLoginObserver;
......@@ -52,9 +53,9 @@ public class RocketChatWebSocketThread extends HandlerThread {
NewMessageObserver.class,
CurrentUserObserver.class,
ReactiveNotificationManager.class,
NotificationItemObserver.class,
FileUploadingToS3Observer.class,
FileUploadingWithUfsObserver.class
FileUploadingWithUfsObserver.class,
PushSettingsObserver.class
};
private final Context appContext;
private final String serverConfigId;
......@@ -167,62 +168,68 @@ public class RocketChatWebSocketThread extends HandlerThread {
realm.where(ServerConfig.class).equalTo(ServerConfig.ID, serverConfigId).findFirst());
prepareWebSocket(config.getHostname());
return ddpClient.connect(config.getSession()).onSuccessTask(task -> {
final String session = task.getResult().session;
defaultRealm.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("serverConfigId", serverConfigId)
.put("session", session))
).onSuccess(_task -> serverConfigRealm.executeTransaction(realm -> {
Session sessionObj = Session.queryDefaultSession(realm).findFirst();
return ddpClient.connect(config.getSession())
.onSuccessTask(task -> {
final String session = task.getResult().session;
defaultRealm.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("serverConfigId", serverConfigId)
.put("session", session))
).onSuccess(_task -> serverConfigRealm.executeTransaction(realm -> {
Session sessionObj = Session.queryDefaultSession(realm).findFirst();
if (sessionObj == null) {
realm.createOrUpdateObjectFromJson(Session.class,
new JSONObject().put("sessionId", Session.DEFAULT_ID));
}
return null;
})).continueWith(new LogcatIfError());
return task;
}).onSuccess(new Continuation<DDPClientCallback.Connect, Void>() {
// TODO type detection doesn't work due to retrolambda's bug...
@Override
public Void then(Task<DDPClientCallback.Connect> task)
throws Exception {
registerListeners();
if (sessionObj == null) {
realm.createOrUpdateObjectFromJson(Session.class,
new JSONObject().put("sessionId", Session.DEFAULT_ID));
}
return null;
})).continueWith(new LogcatIfError());
return task;
})
.onSuccess(new Continuation<DDPClientCallback.Connect, Void>() {
// TODO type detection doesn't work due to retrolambda's bug...
@Override
public Void then(Task<DDPClientCallback.Connect> task)
throws Exception {
fetchPublicSettings();
registerListeners();
// handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
quit();
return null;
}).continueWithTask(_task -> {
if (_task.isFaulted()) {
ServerConfig.logConnectionError(serverConfigId, _task.getError());
// handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
quit();
return null;
}).continueWithTask(_task -> {
if (_task.isFaulted()) {
ServerConfig.logConnectionError(serverConfigId, _task.getError());
}
return _task;
});
return null;
}
})
.continueWithTask(task -> {
if (task.isFaulted()) {
Exception error = task.getError();
if (error instanceof DDPClientCallback.Connect.Timeout) {
ServerConfig.logConnectionError(serverConfigId, new Exception("Connection Timeout"));
} else {
ServerConfig.logConnectionError(serverConfigId, task.getError());
}
}
return _task;
return task;
});
}
return null;
}
}).continueWithTask(task -> {
if (task.isFaulted()) {
Exception error = task.getError();
if (error instanceof DDPClientCallback.Connect.Timeout) {
ServerConfig.logConnectionError(serverConfigId, new Exception("Connection Timeout"));
} else {
ServerConfig.logConnectionError(serverConfigId, task.getError());
}
}
return task;
});
private Task<Void> fetchPublicSettings() {
return new MethodCallHelper(serverConfigRealm, ddpClient).getPublicSettings();
}
//@DebugLog
private void registerListeners() {
if (!Thread.currentThread().getName().equals("RC_thread_" + serverConfigId)) {
// execute in Looper.
new Handler(getLooper()).post(() -> {
registerListeners();
});
new Handler(getLooper()).post(this::registerListeners);
return;
}
......
......@@ -6,8 +6,10 @@ import io.realm.RealmResults;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.DDPClientWrapper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.api.PushHelper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.ddp.User;
import chat.rocket.android.realm_helper.RealmHelper;
......@@ -20,6 +22,7 @@ import hugo.weaving.DebugLog;
*/
public class CurrentUserObserver extends AbstractModelObserver<User> {
private final MethodCallHelper methodCall;
private final PushHelper pushHelper;
private boolean currentUserExists;
private ArrayList<Registrable> listeners;
......@@ -27,6 +30,7 @@ public class CurrentUserObserver extends AbstractModelObserver<User> {
RealmHelper realmHelper, DDPClientWrapper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient);
pushHelper = new PushHelper(realmHelper, ddpClient);
currentUserExists = false;
}
......@@ -58,6 +62,9 @@ public class CurrentUserObserver extends AbstractModelObserver<User> {
final String userId = user.getId();
// update push info
pushHelper.pushSetUser(RocketChatCache.getPushId(context)).continueWith(new LogcatIfError());
// get and observe Room subscriptions.
methodCall.getRoomSubscriptions().onSuccess(task -> {
if (listeners != null) {
......
package chat.rocket.android.service.observer;
import android.content.Context;
import android.content.Intent;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import chat.rocket.android.api.DDPClientWrapper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ddp.PublicSetting;
import chat.rocket.android.model.ddp.PublicSettingsConstants;
import chat.rocket.android.push.gcm.GcmRegistrationIntentService;
import chat.rocket.android.push.interactors.DefaultPushInteractor;
import chat.rocket.android.push.interactors.PushInteractor;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore;
public class PushSettingsObserver extends AbstractModelObserver<PublicSetting> {
public PushSettingsObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientWrapper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
}
@Override
public void onUpdateResults(List<PublicSetting> results) {
RealmHelper realmHelper = RealmStore.getDefault();
PushInteractor interactor = new DefaultPushInteractor();
String serverConfigId = interactor.getServerConfigId(hostname);
final ServerConfig serverConfig = realmHelper.executeTransactionForRead(
realm -> realm.where(ServerConfig.class).equalTo(ServerConfig.ID, serverConfigId)
.findFirst());
serverConfig.setSyncPushToken(isPushEnabled(results));
realmHelper
.executeTransaction(realm -> realm.copyToRealmOrUpdate(serverConfig))
.continueWith(task -> {
if (serverConfig.shouldSyncPushToken()) {
Intent intent = new Intent(
context.getApplicationContext(), GcmRegistrationIntentService.class);
context.getApplicationContext().startService(intent);
}
return task;
})
.continueWith(new LogcatIfError());
}
@Override
public RealmResults<PublicSetting> queryItems(Realm realm) {
return realm.where(PublicSetting.class)
.equalTo(PublicSetting.ID, PublicSettingsConstants.Push.ENABLE)
.or()
.equalTo(PublicSetting.ID, PublicSettingsConstants.Push.GCM_PROJECT_NUMBER)
.findAll();
}
private boolean isPushEnabled(List<PublicSetting> results) {
return isPushEnable(results) && isGcmValid(results);
}
private boolean isPushEnable(List<PublicSetting> results) {
for (PublicSetting setting : results) {
if (PublicSettingsConstants.Push.ENABLE.equals(setting.getId())) {
return "true".equals(setting.getValue());
}
}
return false;
}
private boolean isGcmValid(List<PublicSetting> results) {
for (PublicSetting setting : results) {
if (PublicSettingsConstants.Push.GCM_PROJECT_NUMBER.equals(setting.getId())) {
return setting.getValue() != null && !"".equals(setting.getValue());
}
}
return false;
}
}
......@@ -6,9 +6,7 @@ import io.realm.RealmResults;
import java.util.List;
import chat.rocket.android.api.DDPClientWrapper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.ddp.PublicSetting;
import chat.rocket.android.model.internal.GetUsersOfRoomsProcedure;
import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.model.internal.MethodCall;
......@@ -67,8 +65,6 @@ public class SessionObserver extends AbstractModelObserver<Session> {
@DebugLog
private void onLogin() {
streamNotifyMessage.register();
new MethodCallHelper(realmHelper, ddpClient).getPublicSettings()
.continueWith(new LogcatIfError());
}
@DebugLog
......@@ -77,7 +73,6 @@ public class SessionObserver extends AbstractModelObserver<Session> {
realmHelper.executeTransaction(realm -> {
// remove all tables. ONLY INTERNAL TABLES!.
realm.delete(PublicSetting.class);
realm.delete(MethodCall.class);
realm.delete(LoadMessageProcedure.class);
realm.delete(GetUsersOfRoomsProcedure.class);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment