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 { ...@@ -105,6 +105,8 @@ dependencies {
compile 'com.google.firebase:firebase-core:10.0.0' compile 'com.google.firebase:firebase-core:10.0.0'
compile 'com.google.firebase:firebase-crash: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.okhttp3
compile rootProject.ext.picasso compile rootProject.ext.picasso
......
...@@ -4,6 +4,12 @@ ...@@ -4,6 +4,12 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <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 <application
android:name=".RocketChatApplication" android:name=".RocketChatApplication"
...@@ -35,6 +41,34 @@ ...@@ -35,6 +41,34 @@
<service android:name=".service.RocketChatService" /> <service android:name=".service.RocketChatService" />
<service android:name=".service.notification.NotificationDismissalCallbackService" /> <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> </application>
</manifest> </manifest>
...@@ -3,6 +3,8 @@ package chat.rocket.android; ...@@ -3,6 +3,8 @@ package chat.rocket.android;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import java.util.UUID;
/** /**
* sharedpreference-based cache. * sharedpreference-based cache.
*/ */
...@@ -10,10 +12,45 @@ public class RocketChatCache { ...@@ -10,10 +12,45 @@ public class RocketChatCache {
public static final String KEY_SELECTED_SERVER_CONFIG_ID = "selectedServerConfigId"; public static final String KEY_SELECTED_SERVER_CONFIG_ID = "selectedServerConfigId";
public static final String KEY_SELECTED_ROOM_ID = "selectedRoomId"; public static final String KEY_SELECTED_ROOM_ID = "selectedRoomId";
private static final String PUSH_ID = "pushId";
/** /**
* get SharedPreference instance for RocketChat application cache. * get SharedPreference instance for RocketChat application cache.
*/ */
public static SharedPreferences get(Context context) { public static SharedPreferences get(Context context) {
return context.getSharedPreferences("cache", Context.MODE_PRIVATE); 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; ...@@ -9,6 +9,7 @@ import chat.rocket.android.LaunchUtil;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
import chat.rocket.android.model.ServerConfig; import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ddp.RoomSubscription; 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.RealmListObserver;
import chat.rocket.android.realm_helper.RealmStore; import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android.service.RocketChatService; import chat.rocket.android.service.RocketChatService;
...@@ -39,19 +40,31 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity { ...@@ -39,19 +40,31 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState == null) { if (savedInstanceState == null) {
Intent intent = getIntent(); onIntent(getIntent());
if (intent != null) { }
if (intent.hasExtra(ServerConfig.ID)) { }
SharedPreferences.Editor editor = RocketChatCache.get(this).edit();
editor.putString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID, @Override
intent.getStringExtra(ServerConfig.ID)); protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.hasExtra("roomId")) { onIntent(intent);
editor.putString(RocketChatCache.KEY_SELECTED_ROOM_ID, intent.getStringExtra("roomId")); }
}
editor.apply(); 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; ...@@ -24,6 +24,7 @@ import hugo.weaving.DebugLog;
/** /**
* Utility class for creating/handling MethodCall or RPC. * Utility class for creating/handling MethodCall or RPC.
*
* TODO: separate method into several manager classes (SubscriptionManager, MessageManager, ...). * TODO: separate method into several manager classes (SubscriptionManager, MessageManager, ...).
*/ */
public class MethodCallHelper { public class MethodCallHelper {
...@@ -37,9 +38,8 @@ public class MethodCallHelper { ...@@ -37,9 +38,8 @@ public class MethodCallHelper {
protected final RealmHelper realmHelper; protected final RealmHelper realmHelper;
protected final DDPClientWrapper ddpClient; protected final DDPClientWrapper ddpClient;
@Deprecated
/** /**
* Deprecated. use MethodCall(Context, String) instead. * initialize with ServerConfigId.
*/ */
public MethodCallHelper(String serverConfigId) { public MethodCallHelper(String serverConfigId) {
this(null, 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; ...@@ -2,7 +2,6 @@ package chat.rocket.android.fragment.server_config;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONObject; import org.json.JSONObject;
import chat.rocket.android.R; import chat.rocket.android.R;
...@@ -52,17 +51,11 @@ public class InputHostnameFragment extends AbstractServerConfigFragment { ...@@ -52,17 +51,11 @@ public class InputHostnameFragment extends AbstractServerConfigFragment {
@Override @Override
public void isNotValid() { public void isNotValid() {
getActivity().runOnUiThread(() -> getActivity().runOnUiThread(() ->
Toast.makeText(getActivity(), R.string.input_hostname_invalid_server_message, showError(getString(R.string.input_hostname_invalid_server_message)));
Toast.LENGTH_SHORT).show());
} }
}); });
} }
@Override
public void onResume() {
super.onResume();
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
serverConfigObserver.unsub(); serverConfigObserver.unsub();
...@@ -75,7 +68,7 @@ public class InputHostnameFragment extends AbstractServerConfigFragment { ...@@ -75,7 +68,7 @@ public class InputHostnameFragment extends AbstractServerConfigFragment {
return TextUtils.or(TextUtils.or(editor.getText(), editor.getHint()), "").toString(); 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() RocketChatCache.get(getContext()).edit()
.putString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID, serverConfigId) .putString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID, serverConfigId)
.apply(); .apply();
......
...@@ -62,11 +62,9 @@ public class LoginFragment extends AbstractServerConfigFragment { ...@@ -62,11 +62,9 @@ public class LoginFragment extends AbstractServerConfigFragment {
}); });
final View btnUserRegistration = rootView.findViewById(R.id.btn_user_registration); final View btnUserRegistration = rootView.findViewById(R.id.btn_user_registration);
btnUserRegistration.setOnClickListener(view -> { btnUserRegistration.setOnClickListener(view -> UserRegistrationDialogFragment.create(serverConfigId,
UserRegistrationDialogFragment.create(serverConfigId, txtUsername.getText().toString(), txtPasswd.getText().toString())
txtUsername.getText().toString(), txtPasswd.getText().toString()) .show(getFragmentManager(), UserRegistrationDialogFragment.class.getSimpleName()));
.show(getFragmentManager(), UserRegistrationDialogFragment.class.getSimpleName());
});
} }
private void showError(String errString) { private void showError(String errString) {
......
...@@ -22,7 +22,7 @@ public class ServerPolicyHelper { ...@@ -22,7 +22,7 @@ public class ServerPolicyHelper {
return "demo.rocket.chat"; return "demo.rocket.chat";
} }
return removeProtocol(enforceDefaultHost(hostname)); return removeTrailingSlash(removeProtocol(enforceDefaultHost(hostname)));
} }
public static void isApiVersionValid(@NonNull OkHttpClient client, @NonNull String host, public static void isApiVersionValid(@NonNull OkHttpClient client, @NonNull String host,
...@@ -71,6 +71,15 @@ public class ServerPolicyHelper { ...@@ -71,6 +71,15 @@ public class ServerPolicyHelper {
return hostname.replace("http://", "").replace("https://", ""); 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) { private static boolean isValid(ResponseBody body) {
if (body == null || body.contentLength() == 0) { if (body == null || body.contentLength() == 0) {
return false; return false;
......
...@@ -20,6 +20,7 @@ public class ServerConfig extends RealmObject { ...@@ -20,6 +20,7 @@ public class ServerConfig extends RealmObject {
public static final String STATE = "state"; public static final String STATE = "state";
public static final String SESSION = "session"; public static final String SESSION = "session";
public static final String ERROR = "error"; 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_READY = 0;
public static final int STATE_CONNECTING = 1; public static final int STATE_CONNECTING = 1;
...@@ -31,6 +32,7 @@ public class ServerConfig extends RealmObject { ...@@ -31,6 +32,7 @@ public class ServerConfig extends RealmObject {
private int state; private int state;
private String session; private String session;
private String error; private String error;
private boolean syncPushToken;
/** /**
* Log the server connection is lost due to some exception. * Log the server connection is lost due to some exception.
...@@ -100,4 +102,12 @@ public class ServerConfig extends RealmObject { ...@@ -100,4 +102,12 @@ public class ServerConfig extends RealmObject {
public void setError(String error) { public void setError(String error) {
this.error = 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; ...@@ -12,6 +12,7 @@ import bolts.Continuation;
import bolts.Task; import bolts.Task;
import bolts.TaskCompletionSource; import bolts.TaskCompletionSource;
import chat.rocket.android.api.DDPClientWrapper; import chat.rocket.android.api.DDPClientWrapper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogcatIfError; import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog; import chat.rocket.android.log.RCLog;
...@@ -29,7 +30,7 @@ import chat.rocket.android.service.observer.GetUsersOfRoomsProcedureObserver; ...@@ -29,7 +30,7 @@ import chat.rocket.android.service.observer.GetUsersOfRoomsProcedureObserver;
import chat.rocket.android.service.observer.LoadMessageProcedureObserver; import chat.rocket.android.service.observer.LoadMessageProcedureObserver;
import chat.rocket.android.service.observer.MethodCallObserver; import chat.rocket.android.service.observer.MethodCallObserver;
import chat.rocket.android.service.observer.NewMessageObserver; 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.ReactiveNotificationManager;
import chat.rocket.android.service.observer.SessionObserver; import chat.rocket.android.service.observer.SessionObserver;
import chat.rocket.android.service.observer.TokenLoginObserver; import chat.rocket.android.service.observer.TokenLoginObserver;
...@@ -52,9 +53,9 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -52,9 +53,9 @@ public class RocketChatWebSocketThread extends HandlerThread {
NewMessageObserver.class, NewMessageObserver.class,
CurrentUserObserver.class, CurrentUserObserver.class,
ReactiveNotificationManager.class, ReactiveNotificationManager.class,
NotificationItemObserver.class,
FileUploadingToS3Observer.class, FileUploadingToS3Observer.class,
FileUploadingWithUfsObserver.class FileUploadingWithUfsObserver.class,
PushSettingsObserver.class
}; };
private final Context appContext; private final Context appContext;
private final String serverConfigId; private final String serverConfigId;
...@@ -167,62 +168,68 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -167,62 +168,68 @@ public class RocketChatWebSocketThread extends HandlerThread {
realm.where(ServerConfig.class).equalTo(ServerConfig.ID, serverConfigId).findFirst()); realm.where(ServerConfig.class).equalTo(ServerConfig.ID, serverConfigId).findFirst());
prepareWebSocket(config.getHostname()); prepareWebSocket(config.getHostname());
return ddpClient.connect(config.getSession()).onSuccessTask(task -> { return ddpClient.connect(config.getSession())
final String session = task.getResult().session; .onSuccessTask(task -> {
defaultRealm.executeTransaction(realm -> final String session = task.getResult().session;
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject() defaultRealm.executeTransaction(realm ->
.put("serverConfigId", serverConfigId) realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("session", session)) .put("serverConfigId", serverConfigId)
).onSuccess(_task -> serverConfigRealm.executeTransaction(realm -> { .put("session", session))
Session sessionObj = Session.queryDefaultSession(realm).findFirst(); ).onSuccess(_task -> serverConfigRealm.executeTransaction(realm -> {
Session sessionObj = Session.queryDefaultSession(realm).findFirst();
if (sessionObj == null) { if (sessionObj == null) {
realm.createOrUpdateObjectFromJson(Session.class, realm.createOrUpdateObjectFromJson(Session.class,
new JSONObject().put("sessionId", Session.DEFAULT_ID)); new JSONObject().put("sessionId", Session.DEFAULT_ID));
} }
return null; return null;
})).continueWith(new LogcatIfError()); })).continueWith(new LogcatIfError());
return task; return task;
}).onSuccess(new Continuation<DDPClientCallback.Connect, Void>() { })
// TODO type detection doesn't work due to retrolambda's bug... .onSuccess(new Continuation<DDPClientCallback.Connect, Void>() {
@Override // TODO type detection doesn't work due to retrolambda's bug...
public Void then(Task<DDPClientCallback.Connect> task) @Override
throws Exception { public Void then(Task<DDPClientCallback.Connect> task)
registerListeners(); throws Exception {
fetchPublicSettings();
registerListeners();
// handling WebSocket#onClose() callback. // handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> { task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
quit(); quit();
return null; return null;
}).continueWithTask(_task -> { }).continueWithTask(_task -> {
if (_task.isFaulted()) { if (_task.isFaulted()) {
ServerConfig.logConnectionError(serverConfigId, _task.getError()); 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; private Task<Void> fetchPublicSettings() {
} return new MethodCallHelper(serverConfigRealm, ddpClient).getPublicSettings();
}).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;
});
} }
//@DebugLog //@DebugLog
private void registerListeners() { private void registerListeners() {
if (!Thread.currentThread().getName().equals("RC_thread_" + serverConfigId)) { if (!Thread.currentThread().getName().equals("RC_thread_" + serverConfigId)) {
// execute in Looper. // execute in Looper.
new Handler(getLooper()).post(() -> { new Handler(getLooper()).post(this::registerListeners);
registerListeners();
});
return; return;
} }
......
...@@ -6,8 +6,10 @@ import io.realm.RealmResults; ...@@ -6,8 +6,10 @@ import io.realm.RealmResults;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.DDPClientWrapper; import chat.rocket.android.api.DDPClientWrapper;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.api.PushHelper;
import chat.rocket.android.helper.LogcatIfError; import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.ddp.User; import chat.rocket.android.model.ddp.User;
import chat.rocket.android.realm_helper.RealmHelper; import chat.rocket.android.realm_helper.RealmHelper;
...@@ -20,6 +22,7 @@ import hugo.weaving.DebugLog; ...@@ -20,6 +22,7 @@ import hugo.weaving.DebugLog;
*/ */
public class CurrentUserObserver extends AbstractModelObserver<User> { public class CurrentUserObserver extends AbstractModelObserver<User> {
private final MethodCallHelper methodCall; private final MethodCallHelper methodCall;
private final PushHelper pushHelper;
private boolean currentUserExists; private boolean currentUserExists;
private ArrayList<Registrable> listeners; private ArrayList<Registrable> listeners;
...@@ -27,6 +30,7 @@ public class CurrentUserObserver extends AbstractModelObserver<User> { ...@@ -27,6 +30,7 @@ public class CurrentUserObserver extends AbstractModelObserver<User> {
RealmHelper realmHelper, DDPClientWrapper ddpClient) { RealmHelper realmHelper, DDPClientWrapper ddpClient) {
super(context, hostname, realmHelper, ddpClient); super(context, hostname, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient); methodCall = new MethodCallHelper(realmHelper, ddpClient);
pushHelper = new PushHelper(realmHelper, ddpClient);
currentUserExists = false; currentUserExists = false;
} }
...@@ -58,6 +62,9 @@ public class CurrentUserObserver extends AbstractModelObserver<User> { ...@@ -58,6 +62,9 @@ public class CurrentUserObserver extends AbstractModelObserver<User> {
final String userId = user.getId(); final String userId = user.getId();
// update push info
pushHelper.pushSetUser(RocketChatCache.getPushId(context)).continueWith(new LogcatIfError());
// get and observe Room subscriptions. // get and observe Room subscriptions.
methodCall.getRoomSubscriptions().onSuccess(task -> { methodCall.getRoomSubscriptions().onSuccess(task -> {
if (listeners != null) { 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; ...@@ -6,9 +6,7 @@ import io.realm.RealmResults;
import java.util.List; import java.util.List;
import chat.rocket.android.api.DDPClientWrapper; import chat.rocket.android.api.DDPClientWrapper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogcatIfError; 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.GetUsersOfRoomsProcedure;
import chat.rocket.android.model.internal.LoadMessageProcedure; import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.model.internal.MethodCall; import chat.rocket.android.model.internal.MethodCall;
...@@ -67,8 +65,6 @@ public class SessionObserver extends AbstractModelObserver<Session> { ...@@ -67,8 +65,6 @@ public class SessionObserver extends AbstractModelObserver<Session> {
@DebugLog @DebugLog
private void onLogin() { private void onLogin() {
streamNotifyMessage.register(); streamNotifyMessage.register();
new MethodCallHelper(realmHelper, ddpClient).getPublicSettings()
.continueWith(new LogcatIfError());
} }
@DebugLog @DebugLog
...@@ -77,7 +73,6 @@ public class SessionObserver extends AbstractModelObserver<Session> { ...@@ -77,7 +73,6 @@ public class SessionObserver extends AbstractModelObserver<Session> {
realmHelper.executeTransaction(realm -> { realmHelper.executeTransaction(realm -> {
// remove all tables. ONLY INTERNAL TABLES!. // remove all tables. ONLY INTERNAL TABLES!.
realm.delete(PublicSetting.class);
realm.delete(MethodCall.class); realm.delete(MethodCall.class);
realm.delete(LoadMessageProcedure.class); realm.delete(LoadMessageProcedure.class);
realm.delete(GetUsersOfRoomsProcedure.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