Commit 95beabc6 authored by Tiago Cunha's avatar Tiago Cunha

Push notifications based on the Cordova plugin

Trying to keep up with the same behavior
parent 6b8122be
...@@ -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"
...@@ -34,6 +40,34 @@ ...@@ -34,6 +40,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.PushInstanceIDListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID" />
</intent-filter>
</service>
<service
android:name=".push.gcm.RegistrationIntentService"
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();
} }
} }
......
...@@ -5,14 +5,21 @@ import android.os.Bundle; ...@@ -5,14 +5,21 @@ import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import bolts.Task;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.server_config.LoginFragment; import chat.rocket.android.fragment.server_config.LoginFragment;
import chat.rocket.android.fragment.server_config.RetryConnectFragment; import chat.rocket.android.fragment.server_config.RetryConnectFragment;
import chat.rocket.android.fragment.server_config.RetryLoginFragment; import chat.rocket.android.fragment.server_config.RetryLoginFragment;
import chat.rocket.android.fragment.server_config.WaitingFragment; import chat.rocket.android.fragment.server_config.WaitingFragment;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfig; 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.internal.Session; import chat.rocket.android.model.internal.Session;
import chat.rocket.android.push.gcm.RegistrationIntentService;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmObjectObserver; import chat.rocket.android.realm_helper.RealmObjectObserver;
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;
...@@ -50,13 +57,12 @@ public class ServerConfigActivity extends AbstractFragmentActivity { ...@@ -50,13 +57,12 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
serverConfigErrorObserver = RealmStore.getDefault() serverConfigErrorObserver = RealmStore.getDefault()
.createObjectObserver(realm -> .createObjectObserver(realm ->
realm.where(ServerConfig.class) realm.where(ServerConfig.class)
.equalTo(ServerConfig.ID, serverConfigId) .equalTo(ServerConfig.ID, serverConfigId))
.equalTo(ServerConfig.STATE, ServerConfig.STATE_CONNECTION_ERROR))
.setOnUpdateListener(this::onRenderServerConfigError); .setOnUpdateListener(this::onRenderServerConfigError);
sessionObserver = RealmStore.get(serverConfigId) sessionObserver = RealmStore.get(serverConfigId)
.createObjectObserver(Session::queryDefaultSession) .createObjectObserver(Session::queryDefaultSession)
.setOnUpdateListener(this::onRenderServerConfigSession); .setOnUpdateListener(this::continueWithServerConfigSession);
setContentView(R.layout.simple_screen); setContentView(R.layout.simple_screen);
showFragment(new WaitingFragment()); showFragment(new WaitingFragment());
...@@ -77,7 +83,7 @@ public class ServerConfigActivity extends AbstractFragmentActivity { ...@@ -77,7 +83,7 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
} }
private void onRenderServerConfigError(ServerConfig config) { private void onRenderServerConfigError(ServerConfig config) {
if (config != null) { if (config.getState() == ServerConfig.STATE_CONNECTION_ERROR) {
sessionObserver.unsub(); sessionObserver.unsub();
showFragment(new RetryConnectFragment()); showFragment(new RetryConnectFragment());
} else { } else {
...@@ -85,6 +91,19 @@ public class ServerConfigActivity extends AbstractFragmentActivity { ...@@ -85,6 +91,19 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
} }
} }
private void continueWithServerConfigSession(final Session session) {
fetchPublicSettings()
.continueWith(task -> {
registerForPush();
return task;
})
.continueWith(task -> {
onRenderServerConfigSession(session);
return task;
})
.continueWith(new LogcatIfError());
}
private void onRenderServerConfigSession(Session session) { private void onRenderServerConfigSession(Session session) {
if (session == null) { if (session == null) {
showFragment(new LoginFragment()); showFragment(new LoginFragment());
...@@ -132,6 +151,44 @@ public class ServerConfigActivity extends AbstractFragmentActivity { ...@@ -132,6 +151,44 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
fragment.setArguments(args); fragment.setArguments(args);
} }
private Task<Void> fetchPublicSettings() {
return new MethodCallHelper(this, serverConfigId).getPublicSettings();
}
private void registerForPush() {
RealmHelper realmHelper = RealmStore.getDefault();
final ServerConfig serverConfig = realmHelper.executeTransactionForRead(
realm -> realm.where(ServerConfig.class).equalTo(ServerConfig.ID, serverConfigId)
.findFirst());
serverConfig.setSyncPushToken(isPushEnabled());
realmHelper
.executeTransaction(realm -> realm.copyToRealmOrUpdate(serverConfig))
.continueWith(task -> {
if (serverConfig.shouldSyncPushToken()) {
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);
}
return task;
})
.continueWith(new LogcatIfError());
}
private boolean isPushEnabled() {
RealmHelper realmHelper = RealmStore.getOrCreate(serverConfigId);
boolean isPushEnable = PublicSetting
.getBoolean(realmHelper, PublicSettingsConstants.Push.ENABLE, false);
String senderId = PublicSetting
.getString(realmHelper, PublicSettingsConstants.Push.GCM_PROJECT_NUMBER, "").trim();
return isPushEnable && !"".equals(senderId);
}
@Override @Override
protected void onBackPressedNotHandled() { protected void onBackPressedNotHandled() {
moveTaskToBack(true); moveTaskToBack(true);
......
package chat.rocket.android.api; package chat.rocket.android.api;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Patterns; import android.util.Patterns;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
...@@ -17,6 +19,7 @@ import chat.rocket.android.model.ddp.PublicSetting; ...@@ -17,6 +19,7 @@ import chat.rocket.android.model.ddp.PublicSetting;
import chat.rocket.android.model.ddp.RoomSubscription; import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.model.internal.MethodCall; import chat.rocket.android.model.internal.MethodCall;
import chat.rocket.android.model.internal.Session; import chat.rocket.android.model.internal.Session;
import chat.rocket.android.model.params.PushUpdate;
import chat.rocket.android.realm_helper.RealmHelper; import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore; import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android_ddp.DDPClientCallback; import chat.rocket.android_ddp.DDPClientCallback;
...@@ -24,6 +27,7 @@ import hugo.weaving.DebugLog; ...@@ -24,6 +27,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 +41,8 @@ public class MethodCallHelper { ...@@ -37,9 +41,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);
...@@ -304,6 +307,19 @@ public class MethodCallHelper { ...@@ -304,6 +307,19 @@ public class MethodCallHelper {
.onSuccessTask(task -> Task.forResult(null)); .onSuccessTask(task -> Task.forResult(null));
} }
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));
}
/** /**
* send message. * send message.
*/ */
......
...@@ -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) {
......
...@@ -5,6 +5,7 @@ import android.support.annotation.NonNull; ...@@ -5,6 +5,7 @@ import android.support.annotation.NonNull;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException; import java.io.IOException;
import java.util.regex.Pattern;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
...@@ -22,7 +23,7 @@ public class ServerPolicyHelper { ...@@ -22,7 +23,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 +72,14 @@ public class ServerPolicyHelper { ...@@ -71,6 +72,14 @@ 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) != '/') {
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.model.params;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
public class PushUpdate {
private String pushId;
private String gcmToken;
private String userId;
public PushUpdate(@NonNull String pushId, @NonNull String gcmToken, @Nullable String userId) {
this.pushId = pushId;
this.gcmToken = gcmToken;
this.userId = userId;
}
public 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;
}
}
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 SERVER_CONFIG_ID = "serverConfigId";
String ROOM_ID = "roomId";
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";
}
\ 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> it = extras.keySet().iterator();
Bundle newExtras = new Bundle();
while (it.hasNext()) {
String key = it.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 PushInstanceIDListenerService extends InstanceIDListenerService {
@Override
public void onTokenRefresh() {
List<ServerConfig> serverConfigs = getServerConfigs();
flagForRefresh(serverConfigs);
if (!shouldRefreshToken(serverConfigs)) {
return;
}
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);
}
private List<ServerConfig> getServerConfigs() {
return RealmStore.getDefault().executeTransactionForReadResults(
realm -> realm.where(ServerConfig.class).findAll());
}
private void flagForRefresh(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.MethodCallHelper;
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 RegistrationIntentService extends IntentService {
public RegistrationIntentService() {
super("RegistrationIntentService");
}
@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 MethodCallHelper(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);
}
...@@ -29,7 +29,6 @@ import chat.rocket.android.service.observer.GetUsersOfRoomsProcedureObserver; ...@@ -29,7 +29,6 @@ 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.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,7 +51,6 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -52,7 +51,6 @@ 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
}; };
......
...@@ -6,6 +6,7 @@ import io.realm.RealmResults; ...@@ -6,6 +6,7 @@ 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.helper.LogcatIfError; import chat.rocket.android.helper.LogcatIfError;
...@@ -58,6 +59,9 @@ public class CurrentUserObserver extends AbstractModelObserver<User> { ...@@ -58,6 +59,9 @@ public class CurrentUserObserver extends AbstractModelObserver<User> {
final String userId = user.getId(); final String userId = user.getId();
// update push info
methodCall.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) {
......
...@@ -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