aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--CHANGELOG.md4
-rw-r--r--build.gradle44
-rw-r--r--src/free/java/eu/siacs/conversations/services/PushManagementService.java20
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java3
-rw-r--r--src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java2
-rw-r--r--src/main/java/eu/siacs/conversations/generator/IqGenerator.java29
-rw-r--r--src/main/java/eu/siacs/conversations/parser/IqParser.java9
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java91
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java79
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java72
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/forms/Data.java9
-rw-r--r--src/main/res/layout/activity_edit_account.xml21
-rw-r--r--src/main/res/menu/editaccount.xml35
-rw-r--r--src/main/res/values/arrays.xml6
-rw-r--r--src/main/res/values/strings.xml4
-rw-r--r--src/playstore/AndroidManifest.xml35
-rw-r--r--src/playstore/java/eu/siacs/conversations/services/InstanceIdService.java15
-rw-r--r--src/playstore/java/eu/siacs/conversations/services/PushManagementService.java113
-rw-r--r--src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java20
20 files changed, 521 insertions, 94 deletions
diff --git a/.travis.yml b/.travis.yml
index 2930c2509..0b3dd195b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,3 +11,7 @@ android:
- build-tools-19.1.0
- android-23
- extra-android-m2repository
+ - extra-google-m2repository
+ - extra-google-google_play_services
+ licenses:
+ - '.+'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9cddca48e..8cb4b6f49 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
###Changelog
+####Version 1.9.4
+* prevent cleared Conversations from reloading history with MAM
+* various MAM fixes
+
####Version 1.9.3
* expert setting that enables host and port configuration
* expert setting opt-out of bookmark autojoin handling
diff --git a/build.gradle b/build.gradle
index 0d285079d..c8d116c4f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.3.1'
+ classpath 'com.android.tools.build:gradle:1.5.0'
}
}
@@ -27,11 +27,15 @@ repositories {
mavenCentral()
}
+configurations {
+ playstoreCompile
+}
+
dependencies {
compile project(':libs:MemorizingTrustManager')
compile 'org.sufficientlysecure:openpgp-api:10.0'
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
- compile 'com.android.support:support-v13:23.0.1'
+ compile 'com.android.support:support-v13:23.1.1'
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
compile 'org.bouncycastle:bcmail-jdk15on:1.52'
compile 'org.jitsi:org.otr4j:0.22'
@@ -47,6 +51,7 @@ dependencies {
compile 'jetty:javax.servlet:5.1.12'
compile 'com.google.code.gson:gson:2.3.1'
compile 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2'
+ playstoreCompile 'com.google.android.gms:play-services-gcm:8.3.0'
}
android {
@@ -57,10 +62,9 @@ android {
minSdkVersion 14
targetSdkVersion 23
- versionCode 122
- versionName "1.9.3"
-
- project.ext.set(archivesBaseName, archivesBaseName + "-" + versionName);
+ versionCode 123
+ versionName "1.9.4"
+ archivesBaseName += "-$versionName"
}
compileOptions {
@@ -68,15 +72,10 @@ android {
targetCompatibility JavaVersion.VERSION_1_7
}
- //
- // To sign release builds, create the file `gradle.properties` in
- // $HOME/.gradle or in your project directory with this content:
- //
- // mStoreFile=/path/to/key.store
- // mStorePassword=xxx
- // mKeyAlias=alias
- // mKeyPassword=xxx
- //
+ productFlavors {
+ playstore
+ free
+ }
if (project.hasProperty('mStoreFile') &&
project.hasProperty('mStorePassword') &&
project.hasProperty('mKeyAlias') &&
@@ -94,16 +93,6 @@ android {
buildTypes.release.signingConfig = null
}
- applicationVariants.all { variant ->
- if (variant.name.equals('release')) {
- variant.outputs.each { output ->
- if (output.zipAlign != null) {
- output.zipAlign.outputFile = new File(output.outputFile.parent, rootProject.name + "-${variant.versionName}.apk")
- }
- }
- }
- }
-
lintOptions {
disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource'
}
@@ -121,4 +110,9 @@ android {
}
}
+
+ packagingOptions {
+ exclude 'META-INF/BCKEY.DSA'
+ exclude 'META-INF/BCKEY.SF'
+ }
}
diff --git a/src/free/java/eu/siacs/conversations/services/PushManagementService.java b/src/free/java/eu/siacs/conversations/services/PushManagementService.java
new file mode 100644
index 000000000..ff97a5fb9
--- /dev/null
+++ b/src/free/java/eu/siacs/conversations/services/PushManagementService.java
@@ -0,0 +1,20 @@
+package eu.siacs.conversations.services;
+
+import eu.siacs.conversations.entities.Account;
+
+public class PushManagementService {
+
+ protected final XmppConnectionService mXmppConnectionService;
+
+ public PushManagementService(XmppConnectionService service) {
+ this.mXmppConnectionService = service;
+ }
+
+ public void registerPushTokenOnServer(Account account) {
+ //stub implementation. only affects playstore flavor
+ }
+
+ public boolean available(Account account) {
+ return false;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index 125facf07..f7ed9f96d 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -37,6 +37,8 @@ public final class Config {
public static final int CARBON_GRACE_PERIOD = 90;
public static final int MINI_GRACE_PERIOD = 750;
+ public static final boolean CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND = false;
+
public static final int AVATAR_SIZE = 640;
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP;
@@ -118,6 +120,5 @@ public final class Config {
};
private Config() {
-
}
}
diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
index 835eef36b..b9c95e6e5 100644
--- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
@@ -15,6 +15,8 @@ import java.util.TimeZone;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.PhoneHelper;
+import eu.siacs.conversations.xmpp.jid.Jid;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public abstract class AbstractGenerator {
private final String[] FEATURES = {
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index 3b9f8ce06..bb373c75c 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -25,6 +25,7 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.forms.Data;
+import eu.siacs.conversations.xmpp.forms.Field;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
@@ -290,7 +291,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(host);
- Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD);
+ Element request = packet.addChild("request", Xmlns.HTTP_UPLOAD);
request.addChild("filename").setContent(file.getName());
request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
if (mime != null) {
@@ -308,4 +309,30 @@ public class IqGenerator extends AbstractGenerator {
return register;
}
+
+ public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
+ IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ packet.setTo(appServer);
+ Element command = packet.addChild("command", "http://jabber.org/protocol/commands");
+ command.setAttribute("node","register-push-gcm");
+ command.setAttribute("action","execute");
+ Data data = new Data();
+ data.put("token", token);
+ data.put("device-id", deviceId);
+ data.submit();
+ command.addChild(data);
+ return packet;
+ }
+
+ public IqPacket enablePush(Jid jid, String node, String secret) {
+ IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ Element enable = packet.addChild("enable","urn:xmpp:push:0");
+ enable.setAttribute("jid",jid.toString());
+ enable.setAttribute("node", node);
+ Data data = new Data();
+ data.setFormType("http://jabber.org/protocol/pubsub#publish-options");
+ data.put("secret",secret);
+ enable.addChild(data);
+ return packet;
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java
index 09bbabebe..5903d5116 100644
--- a/src/main/java/eu/siacs/conversations/parser/IqParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java
@@ -6,7 +6,6 @@ import android.util.Log;
import android.util.Pair;
import org.whispersystems.libaxolotl.IdentityKey;
-import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.state.PreKeyBundle;
@@ -142,7 +141,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
try {
publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0);
- } catch (InvalidKeyException | IllegalArgumentException | NullPointerException e) {
+ } catch (Throwable e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage());
}
return publicKey;
@@ -155,7 +154,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
try {
return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT);
- } catch (IllegalArgumentException e) {
+ } catch (Throwable e) {
Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature");
return null;
}
@@ -169,7 +168,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
try {
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
- } catch (InvalidKeyException | IllegalArgumentException | NullPointerException e) {
+ } catch (Throwable e) {
Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
}
return identityKey;
@@ -200,7 +199,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
try {
ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0);
preKeyRecords.put(preKeyId, preKeyPublic);
- } catch (InvalidKeyException | IllegalArgumentException | NullPointerException e) {
+ } catch (Throwable e) {
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping...");
continue;
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 4fa5ddfff..16d7f139c 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -73,7 +73,6 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
import eu.siacs.conversations.entities.Presence;
-import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.entities.Roster;
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
import eu.siacs.conversations.entities.Transferable;
@@ -127,6 +126,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public static final String ACTION_TRY_AGAIN = "try_again";
public static final String ACTION_DISABLE_ACCOUNT = "disable_account";
private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
+ public static final String ACTION_GCM_TOKEN_REFRESH = "gcm_token_refresh";
+ public static final String ACTION_GCM_MESSAGE_RECEIVED = "gcm_message_received";
private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor();
private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor();
private final IBinder mBinder = new XmppConnectionBinder();
@@ -198,6 +199,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
this);
private AvatarService mAvatarService = new AvatarService(this);
private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
+ private PushManagementService mPushManagementService = new PushManagementService(this);
private OnConversationUpdate mOnConversationUpdate = null;
private final FileObserver fileObserver = new FileObserver(
FileBackend.getConversationsImageDirectory()) {
@@ -257,6 +259,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
fetchRosterFromServer(account);
fetchBookmarks(account);
sendPresence(account);
+ if (mPushManagementService.available(account)) {
+ mPushManagementService.registerPushTokenOnServer(account);
+ }
mMessageArchiveService.executePendingQueries(account);
connectMultiModeConversations(account);
syncDirtyContacts(account);
@@ -265,7 +270,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private OnStatusChanged statusListener = new OnStatusChanged() {
@Override
- public void onStatusChanged(Account account) {
+ public void onStatusChanged(final Account account) {
XmppConnection connection = account.getXmppConnection();
if (mOnAccountUpdate != null) {
mOnAccountUpdate.onAccountUpdate();
@@ -282,7 +287,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
List<Conversation> conversations = getConversations();
for (Conversation conversation : conversations) {
- if (conversation.getAccount() == account) {
+ if (conversation.getAccount() == account && conversation.getMode() == Conversation.MODE_SINGLE) {
conversation.startOtrIfNeeded();
sendUnsentMessages(conversation);
}
@@ -298,7 +303,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode());
} else if (account.getStatus() == Account.State.OFFLINE) {
resetSendingToWaiting(account);
- if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ final boolean disabled = account.isOptionSet(Account.OPTION_DISABLED);
+ final boolean pushMode = Config.CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND
+ && mPushManagementService.available(account)
+ && checkListeners();
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": push mode "+Boolean.toString(pushMode));
+ if (!disabled && !pushMode) {
int timeToReconnect = mRandom.nextInt(20) + 10;
scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
}
@@ -464,6 +474,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
final String action = intent == null ? null : intent.getAction();
boolean interactive = false;
if (action != null) {
+ Log.d(Config.LOGTAG,"start reason: "+action);
switch (action) {
case ConnectivityManager.CONNECTIVITY_ACTION:
if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) {
@@ -512,6 +523,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
refreshAllPresences();
}
break;
+ case ACTION_GCM_TOKEN_REFRESH:
+ refreshAllGcmTokens();
+ break;
+ case ACTION_GCM_MESSAGE_RECEIVED:
+ Log.d(Config.LOGTAG,"gcm push message arrived in service. extras="+intent.getExtras());
}
}
this.wakeLock.acquire();
@@ -572,7 +588,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
reconnectAccount(account, true, interactive);
}
}
-
}
if (mOnAccountUpdate != null) {
mOnAccountUpdate.onAccountUpdate();
@@ -753,18 +768,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
disconnect(account, false);
}
}).start();
-
+ cancelWakeUpCall(account.getUuid().hashCode());
}
}
- Context context = getApplicationContext();
- AlarmManager alarmManager = (AlarmManager) context
- .getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(context, EventReceiver.class);
- alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
Log.d(Config.LOGTAG, "good bye");
stopSelf();
}
+ private void cancelWakeUpCall(int requestCode) {
+ final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ final Intent intent = new Intent(this, EventReceiver.class);
+ intent.setAction("ping");
+ alarmManager.cancel(PendingIntent.getBroadcast(this, requestCode, intent, 0));
+ }
+
public void scheduleWakeUpCall(int seconds, int requestCode) {
final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000;
@@ -1710,8 +1727,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (Account account : getAccounts()) {
if (account.getStatus() == Account.State.ONLINE) {
XmppConnection connection = account.getXmppConnection();
- if (connection != null && connection.getFeatures().csi()) {
- connection.sendInactive();
+ if (connection != null) {
+ if (connection.getFeatures().csi()) {
+ connection.sendInactive();
+ }
+ if (Config.CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND && mPushManagementService.available(account)) {
+ connection.waitForPush();
+ cancelWakeUpCall(account.getUuid().hashCode());
+ }
}
}
}
@@ -1776,6 +1799,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (conversation.getMucOptions().mamSupport()) {
getMessageArchiveService().catchupMUC(conversation);
}
+ sendUnsentMessages(conversation);
}
@Override
@@ -2844,6 +2868,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ private void refreshAllGcmTokens() {
+ for(Account account : getAccounts()) {
+ if (account.isOnlineAndConnected() && mPushManagementService.available(account)) {
+ mPushManagementService.registerPushTokenOnServer(account);
+ }
+ }
+ }
+
public void sendOfflinePresence(final Account account) {
sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account));
}
@@ -3004,7 +3036,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
databaseBackend.insertDiscoveryResult(disco);
injectServiceDiscorveryResult(account.getRoster(), presence.getHash(), presence.getVer(), disco);
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": mismatch in caps for contact " + jid+" "+presence.getVer()+" vs "+disco.getVer());
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + disco.getVer());
}
}
account.inProgressDiscoFetches.remove(key);
@@ -3024,6 +3056,37 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void fetchMamPreferences(Account account, final OnMamPreferencesFetched callback) {
+ IqPacket request = new IqPacket(IqPacket.TYPE.GET);
+ request.addChild("prefs","urn:xmpp:mam:0");
+ sendIqPacket(account, request, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ Element prefs = packet.findChild("prefs","urn:xmpp:mam:0");
+ if (packet.getType() == IqPacket.TYPE.RESULT && prefs != null) {
+ callback.onPreferencesFetched(prefs);
+ } else {
+ callback.onPreferencesFetchFailed();
+ }
+ }
+ });
+ }
+
+ public PushManagementService getPushManagementService() {
+ return mPushManagementService;
+ }
+
+ public interface OnMamPreferencesFetched {
+ void onPreferencesFetched(Element prefs);
+ void onPreferencesFetchFailed();
+ }
+
+ public void pushMamPreferences(Account account, Element prefs) {
+ IqPacket set = new IqPacket(IqPacket.TYPE.SET);
+ set.addChild(prefs);
+ sendIqPacket(account, set, null);
+ }
+
public interface OnAccountCreated {
void onAccountCreated(Account account);
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index a126b344d..d3899c29f 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -29,11 +29,15 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TableLayout;
+import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;
-import eu.siacs.conversations.Config;
import org.whispersystems.libaxolotl.IdentityKey;
import java.util.Set;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -44,6 +48,7 @@ import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.XmppConnection.Features;
import eu.siacs.conversations.xmpp.forms.Data;
@@ -52,7 +57,7 @@ import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
public class EditAccountActivity extends XmppActivity implements OnAccountUpdate,
- OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast {
+ OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
private AutoCompleteTextView mAccountJid;
private EditText mPassword;
@@ -73,6 +78,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
private TextView mServerInfoBlocking;
private TextView mServerInfoPep;
private TextView mServerInfoHttpUpload;
+ private TextView mServerInfoPush;
private TextView mSessionEst;
private TextView mOtrFingerprint;
private TextView mAxolotlFingerprint;
@@ -218,6 +224,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
finish();
}
};
+ private Toast mFetchingMamPrefsToast;
+ private TableRow mPushRow;
public void refreshUiReal() {
invalidateOptionsMenu();
@@ -417,6 +425,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm);
this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep);
this.mServerInfoHttpUpload = (TextView) findViewById(R.id.server_info_http_upload);
+ this.mPushRow = (TableRow) findViewById(R.id.push_row);
+ this.mServerInfoPush = (TextView) findViewById(R.id.server_info_push);
this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box);
this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
@@ -465,6 +475,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices);
final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
+ final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
@@ -475,6 +486,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (!mAccount.getXmppConnection().getFeatures().register()) {
changePassword.setVisible(false);
}
+ mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
if (otherDevices == null || otherDevices.isEmpty()) {
clearDevices.setVisible(false);
@@ -485,6 +497,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
showMoreInfo.setVisible(false);
changePassword.setVisible(false);
clearDevices.setVisible(false);
+ mamPrefs.setVisible(false);
}
return true;
}
@@ -568,6 +581,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
startActivity(changePasswordIntent);
break;
+ case R.id.action_mam_prefs:
+ editMamPrefs();
+ break;
case R.id.action_clear_devices:
showWipePepDialog();
break;
@@ -669,6 +685,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} else {
this.mServerInfoHttpUpload.setText(R.string.server_info_unavailable);
}
+
+ this.mPushRow.setVisibility(xmppConnectionService.getPushManagementService().available(mAccount) ? View.VISIBLE : View.GONE);
+
+ if (features.push()) {
+ this.mServerInfoPush.setText(R.string.server_info_available);
+ } else {
+ this.mServerInfoPush.setText(R.string.server_info_unavailable);
+ }
final String otrFingerprint = this.mAccount.getOtrFingerprint();
if (otrFingerprint != null) {
this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
@@ -799,6 +823,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
builder.create().show();
}
+ private void editMamPrefs() {
+ this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
+ this.mFetchingMamPrefsToast.show();
+ xmppConnectionService.fetchMamPreferences(mAccount, this);
+ }
+
@Override
public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
refreshUi();
@@ -878,4 +908,49 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
});
}
+
+ @Override
+ public void onPreferencesFetched(final Element prefs) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mFetchingMamPrefsToast != null) {
+ mFetchingMamPrefsToast.cancel();
+ }
+ AlertDialog.Builder builder = new Builder(EditAccountActivity.this);
+ builder.setTitle(R.string.mam_prefs);
+ String defaultAttr = prefs.getAttribute("default");
+ final List<String> defaults = Arrays.asList("never", "roster", "always");
+ final AtomicInteger choice = new AtomicInteger(Math.max(0,defaults.indexOf(defaultAttr)));
+ builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ choice.set(which);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ prefs.setAttribute("default",defaults.get(choice.get()));
+ xmppConnectionService.pushMamPreferences(mAccount, prefs);
+ }
+ });
+ builder.create().show();
+ }
+ });
+ }
+
+ @Override
+ public void onPreferencesFetchFailed() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mFetchingMamPrefsToast != null) {
+ mFetchingMamPrefsToast.cancel();
+ }
+ Toast.makeText(EditAccountActivity.this,R.string.unable_to_fetch_mam_prefs,Toast.LENGTH_LONG).show();
+ }
+ });
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 7f987ae9f..3ddad1fdb 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -61,7 +61,6 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
import eu.siacs.conversations.generator.IqGenerator;
import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.DNSHelper;
import eu.siacs.conversations.utils.SSLSocketHelper;
import eu.siacs.conversations.utils.SocksSocketFactory;
@@ -352,13 +351,7 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.OFFLINE);
this.attempt--; //don't count attempt when reconnecting instantly anyway
} finally {
- if (socket != null) {
- try {
- socket.close();
- } catch (IOException e) {
-
- }
- }
+ forceCloseSocket();
if (wakeLock.isHeld()) {
try {
wakeLock.release();
@@ -430,13 +423,7 @@ public class XmppConnection implements Runnable {
@Override
public void run() {
- try {
- if (socket != null) {
- socket.close();
- }
- } catch (final IOException ignored) {
-
- }
+ forceCloseSocket();
connect();
}
@@ -1283,14 +1270,45 @@ public class XmppConnection implements Runnable {
}
}
- public void disconnect(final boolean force) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force));
- if (force) {
+ public void waitForPush() {
+ if (tagWriter.isActive()) {
+ tagWriter.finish();
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ while(!tagWriter.finished()) {
+ Thread.sleep(10);
+ }
+ socket.close();
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ } else {
+ forceCloseSocket();
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
+ }
+ }
+
+ private void forceCloseSocket() {
+ if (socket != null) {
try {
socket.close();
- } catch(Exception e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": exception during force close ("+e.getMessage()+")");
+ } catch (IOException e) {
+ e.printStackTrace();
}
+ }
+ }
+
+ public void disconnect(final boolean force) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force));
+ if (force) {
+ forceCloseSocket();
return;
} else {
if (tagWriter.isActive()) {
@@ -1495,17 +1513,13 @@ public class XmppConnection implements Runnable {
}
public boolean mam() {
- if (hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0")) {
- return true;
- } else {
- return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0");
- }
+ return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0")
+ || hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0");
}
- public boolean advancedStreamFeaturesLoaded() {
- synchronized (XmppConnection.this.disco) {
- return disco.containsKey(account.getServer());
- }
+ public boolean push() {
+ return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:push:0")
+ || hasDiscoFeature(account.getServer(), "urn:xmpp:push:0");
}
public boolean rosterVersioning() {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java
index 50a418921..380f02808 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java
@@ -80,8 +80,13 @@ public class Data extends Element {
}
public String getFormType() {
- Field typeFiled = this.getFieldByName("FORM_TYPE");
- return typeFiled == null ? "" : typeFiled.getValue();
+ String type = getValue("FORM_TYPE");
+ return type == null ? "" : type;
+ }
+
+ public String getValue(String name) {
+ Field field = this.getFieldByName(name);
+ return field == null ? null : field.getValue();
}
public String getTitle() {
diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml
index b3b939624..8038e633a 100644
--- a/src/main/res/layout/activity_edit_account.xml
+++ b/src/main/res/layout/activity_edit_account.xml
@@ -416,6 +416,26 @@
</TableRow>
<TableRow
+ android:id="@+id/push_row"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/server_info_push"
+ android:textColor="@color/black87"
+ android:textSize="?attr/TextSizeBody"/>
+
+ <TextView
+ android:id="@+id/server_info_push"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:textColor="@color/black87"
+ android:textSize="?attr/TextSizeBody"/>
+ </TableRow>
+ <TableRow
android:layout_width="fill_parent"
android:layout_height="wrap_content">
@@ -434,6 +454,7 @@
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
tools:ignore="RtlHardcoded" />
+
</TableRow>
</TableLayout>
diff --git a/src/main/res/menu/editaccount.xml b/src/main/res/menu/editaccount.xml
index b7a404182..9f06fc4c4 100644
--- a/src/main/res/menu/editaccount.xml
+++ b/src/main/res/menu/editaccount.xml
@@ -2,35 +2,40 @@
<item
android:id="@+id/action_show_qr_code"
- android:title="@string/show_qr_code"
- android:showAsAction="never" />
+ android:showAsAction="never"
+ android:title="@string/show_qr_code"/>
<item
android:id="@+id/action_show_block_list"
- android:title="@string/show_block_list"
- android:showAsAction="never" />
+ android:showAsAction="never"
+ android:title="@string/show_block_list"/>
<item
android:id="@+id/action_renew_certificate"
+ android:showAsAction="never"
android:title="@string/action_renew_certificate"
- android:visible="false"
- android:showAsAction="never" />
- />
+ android:visible="false"/>
<item
android:id="@+id/action_server_info_show_more"
- android:title="@string/server_info_show_more"
android:checkable="true"
android:checked="false"
- android:showAsAction="never" />
+ android:showAsAction="never"
+ android:title="@string/server_info_show_more"/>
+
+ <item
+ android:id="@+id/action_mam_prefs"
+ android:title="@string/mam_prefs"/>
- <item android:id="@+id/action_change_password_on_server"
- android:title="@string/change_password"
- android:showAsAction="never" />
+ <item
+ android:id="@+id/action_change_password_on_server"
+ android:showAsAction="never"
+ android:title="@string/change_password"/>
- <item android:id="@+id/action_clear_devices"
- android:title="@string/clear_other_devices"
- android:showAsAction="never"/>
+ <item
+ android:id="@+id/action_clear_devices"
+ android:showAsAction="never"
+ android:title="@string/clear_other_devices"/>
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml
index 0f2dbb1a4..cc04d98ba 100644
--- a/src/main/res/values/arrays.xml
+++ b/src/main/res/values/arrays.xml
@@ -69,4 +69,10 @@
<item>@string/automatically</item>
<item>@string/always</item>
</string-array>
+
+ <string-array name="mam_prefs">
+ <item>@string/never</item>
+ <item>@string/contacts</item>
+ <item>@string/always</item>
+ </string-array>
</resources>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 081dc8779..404e7a02a 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -193,6 +193,7 @@
<string name="server_info_stream_management">XEP-0198: Stream Management</string>
<string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string>
<string name="server_info_http_upload">XEP-0363: HTTP File Upload</string>
+ <string name="server_info_push">XEP-0357: Push</string>
<string name="server_info_available">available</string>
<string name="server_info_unavailable">unavailable</string>
<string name="missing_public_keys">Missing public key announcements</string>
@@ -552,6 +553,9 @@
<string name="action_add_account_with_certificate">Add account with certificate</string>
<string name="unable_to_parse_certificate">Unable to parse certificate</string>
<string name="authenticate_with_certificate">Leave empty to authenticate w/ certificate</string>
+ <string name="mam_prefs">Archiving preferences</string>
+ <string name="fetching_mam_prefs">Fetching archiving preferences. Please wait…</string>
+ <string name="unable_to_fetch_mam_prefs">Unable to fetch archiving preferences</string>
<string name="captcha_ocr">Captcha text</string>
<string name="captcha_required">Captcha required</string>
<string name="captcha_hint">enter the text from the image</string>
diff --git a/src/playstore/AndroidManifest.xml b/src/playstore/AndroidManifest.xml
new file mode 100644
index 000000000..a1b91be35
--- /dev/null
+++ b/src/playstore/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest
+ package="eu.siacs.conversations"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <permission android:name="eu.siacs.conversations.permission.C2D_MESSAGE"
+ android:protectionLevel="signature"/>
+ <uses-permission android:name="eu.siacs.conversations.permission.C2D_MESSAGE"/>
+
+ <application>
+
+ <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=".services.PushMessageReceiver"
+ android:exported="false" >
+ <intent-filter>
+ <action android:name="com.google.android.c2dm.intent.RECEIVE" />
+ </intent-filter>
+ </service>
+
+ <service android:name=".services.InstanceIdService" android:exported="false">
+ <intent-filter>
+ <action android:name="com.google.android.gms.iid.InstanceID"/>
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/src/playstore/java/eu/siacs/conversations/services/InstanceIdService.java b/src/playstore/java/eu/siacs/conversations/services/InstanceIdService.java
new file mode 100644
index 000000000..dc0804305
--- /dev/null
+++ b/src/playstore/java/eu/siacs/conversations/services/InstanceIdService.java
@@ -0,0 +1,15 @@
+package eu.siacs.conversations.services;
+
+import android.content.Intent;
+
+import com.google.android.gms.iid.InstanceIDListenerService;
+
+public class InstanceIdService extends InstanceIDListenerService {
+
+ @Override
+ public void onTokenRefresh() {
+ Intent intent = new Intent(this, XmppConnectionService.class);
+ intent.setAction(XmppConnectionService.ACTION_GCM_TOKEN_REFRESH);
+ startService(intent);
+ }
+}
diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java
new file mode 100644
index 000000000..f613fe5b7
--- /dev/null
+++ b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java
@@ -0,0 +1,113 @@
+package eu.siacs.conversations.services;
+
+import android.provider.Settings;
+import android.util.Log;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GoogleApiAvailability;
+import com.google.android.gms.gcm.GoogleCloudMessaging;
+import com.google.android.gms.iid.InstanceID;
+
+import java.io.IOException;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.forms.Data;
+import eu.siacs.conversations.xmpp.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+public class PushManagementService {
+
+ private static final String APP_SERVER = "push.siacs.eu";
+
+ protected final XmppConnectionService mXmppConnectionService;
+
+ public PushManagementService(XmppConnectionService service) {
+ this.mXmppConnectionService = service;
+ }
+
+ public void registerPushTokenOnServer(final Account account) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": has push support");
+ retrieveGcmInstanceToken(new OnGcmInstanceTokenRetrieved() {
+ @Override
+ public void onGcmInstanceTokenRetrieved(String token) {
+ try {
+ final String deviceId = Settings.Secure.getString(mXmppConnectionService.getContentResolver(), Settings.Secure.ANDROID_ID);
+ IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(Jid.fromString(APP_SERVER), token, deviceId);
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ Element command = packet.findChild("command","http://jabber.org/protocol/commands");
+ if (packet.getType() == IqPacket.TYPE.RESULT && command != null) {
+ Element x = command.findChild("x","jabber:x:data");
+ if (x != null) {
+ Data data = Data.parse(x);
+ try {
+ String node = data.getValue("node");
+ String secret = data.getValue("secret");
+ Jid jid = Jid.fromString(data.getValue("jid"));
+ if (node != null && secret != null) {
+ enablePushOnServer(account, jid, node, secret);
+ }
+ } catch (InvalidJidException e) {
+ e.printStackTrace();
+ }
+ }
+ } else {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": invalid response from app server");
+ }
+ }
+ });
+ } catch (InvalidJidException ignored) {
+
+ }
+ }
+ });
+ }
+
+ private void enablePushOnServer(final Account account, final Jid jid, final String node, final String secret) {
+ IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(jid, node, secret);
+ mXmppConnectionService.sendIqPacket(account, enable, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": successfully enabled push on server");
+ } else if (packet.getType() == IqPacket.TYPE.ERROR) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": enabling push on server failed");
+ }
+ }
+ });
+ }
+
+ private void retrieveGcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ InstanceID instanceID = InstanceID.getInstance(mXmppConnectionService);
+ try {
+ String token = instanceID.getToken(mXmppConnectionService.getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
+ instanceTokenRetrieved.onGcmInstanceTokenRetrieved(token);
+ } catch (IOException e) {
+ }
+ }
+ }).start();
+
+ }
+
+
+ public boolean available(Account account) {
+ return account.getXmppConnection().getFeatures().push() && playServicesAvailable();
+ }
+
+ private boolean playServicesAvailable() {
+ return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS;
+ }
+
+ interface OnGcmInstanceTokenRetrieved {
+ void onGcmInstanceTokenRetrieved(String token);
+ }
+}
diff --git a/src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java b/src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java
new file mode 100644
index 000000000..37c95e135
--- /dev/null
+++ b/src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java
@@ -0,0 +1,20 @@
+package eu.siacs.conversations.services;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.gcm.GcmListenerService;
+
+import eu.siacs.conversations.Config;
+
+public class PushMessageReceiver extends GcmListenerService {
+
+ @Override
+ public void onMessageReceived(String from, Bundle data) {
+ Intent intent = new Intent(this, XmppConnectionService.class);
+ intent.setAction(XmppConnectionService.ACTION_GCM_MESSAGE_RECEIVED);
+ intent.replaceExtras(data);
+ startService(intent);
+ }
+}