UP: add custom extensions for app<->distributor interaction

Daniel Gultsch created

On registration the app can pass in a 'Messenger' to get a direct response
instead of having to somehow wait for the broadcast receiver to fire.

The app name can be passed as a pending intent which allows the distributor
to validate the sender.

Change summary

src/main/AndroidManifest.xml                                              |  2 
src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java      | 73 
src/main/java/eu/siacs/conversations/services/UnifiedPushDistributor.java | 23 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java  | 21 
4 files changed, 99 insertions(+), 20 deletions(-)

Detailed changes

src/main/AndroidManifest.xml 🔗

@@ -114,6 +114,8 @@
                 <action android:name="org.unifiedpush.android.distributor.REGISTER" />
                 <action android:name="org.unifiedpush.android.distributor.UNREGISTER" />
                 <action android:name="org.unifiedpush.android.distributor.feature.BYTES_MESSAGE" />
+                <action android:name="org.unifiedpush.android.distributor.feature.MESSENGER" />
+                <action android:name="org.unifiedpush.android.distributor.feature.APP_VALIDATION" />
             </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />

src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java 🔗

@@ -4,6 +4,9 @@ import android.content.ComponentName;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
 import android.preference.PreferenceManager;
 import android.util.Log;
 import com.google.common.base.Optional;
@@ -62,7 +65,7 @@ public class UnifiedPushBroker {
                 Log.d(
                         Config.LOGTAG,
                         account.getJid().asBareJid() + ": trigger endpoint renewal on bind");
-                renewUnifiedEndpoint(transportOptional.get());
+                renewUnifiedEndpoint(transportOptional.get(), null);
             }
         }
     }
@@ -74,11 +77,15 @@ public class UnifiedPushBroker {
     }
 
     public Optional<Transport> renewUnifiedPushEndpoints() {
+        return renewUnifiedPushEndpoints(null);
+    }
+
+    public Optional<Transport> renewUnifiedPushEndpoints(final PushTargetMessenger pushTargetMessenger) {
         final Optional<Transport> transportOptional = getTransport();
         if (transportOptional.isPresent()) {
             final Transport transport = transportOptional.get();
             if (transport.account.isEnabled()) {
-                renewUnifiedEndpoint(transportOptional.get());
+                renewUnifiedEndpoint(transportOptional.get(), pushTargetMessenger);
             } else {
                 Log.d(Config.LOGTAG, "skipping UnifiedPush endpoint renewal. Account is disabled");
             }
@@ -88,7 +95,7 @@ public class UnifiedPushBroker {
         return transportOptional;
     }
 
-    private void renewUnifiedEndpoint(final Transport transport) {
+    private void renewUnifiedEndpoint(final Transport transport, final PushTargetMessenger pushTargetMessenger) {
         final Account account = transport.account;
         final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(service);
         final List<UnifiedPushDatabase.PushTarget> renewals =
@@ -114,16 +121,23 @@ public class UnifiedPushBroker {
             final Element register = registration.addChild("register", Namespace.UNIFIED_PUSH);
             register.setAttribute("application", hashedApplication);
             register.setAttribute("instance", hashedInstance);
+            final Messenger messenger;
+            if (pushTargetMessenger != null && renewal.equals(pushTargetMessenger.pushTarget)) {
+                messenger = pushTargetMessenger.messenger;
+            } else {
+                messenger = null;
+            }
             this.service.sendIqPacket(
                     account,
                     registration,
-                    (a, response) -> processRegistration(transport, renewal, response));
+                    (a, response) -> processRegistration(transport, renewal, messenger, response));
         }
     }
 
     private void processRegistration(
             final Transport transport,
             final UnifiedPushDatabase.PushTarget renewal,
+            final Messenger messenger,
             final IqPacket response) {
         if (response.getType() == IqPacket.TYPE.RESULT) {
             final Element registered = response.findChild("registered", Namespace.UNIFIED_PUSH);
@@ -142,7 +156,7 @@ public class UnifiedPushBroker {
                 Log.d(Config.LOGTAG, "could not parse expiration", e);
                 return;
             }
-            renewUnifiedPushEndpoint(transport, renewal, endpoint, expiration);
+            renewUnifiedPushEndpoint(transport, renewal, messenger, endpoint, expiration);
         } else {
             Log.d(Config.LOGTAG, "could not register UP endpoint " + response.getErrorCondition());
         }
@@ -151,6 +165,7 @@ public class UnifiedPushBroker {
     private void renewUnifiedPushEndpoint(
             final Transport transport,
             final UnifiedPushDatabase.PushTarget renewal,
+            final Messenger messenger,
             final String endpoint,
             final long expiration) {
         Log.d(Config.LOGTAG, "registered endpoint " + endpoint + " expiration=" + expiration);
@@ -171,9 +186,24 @@ public class UnifiedPushBroker {
                             + renewal.instance
                             + " was updated to "
                             + endpoint);
-            broadcastEndpoint(
-                    renewal.instance,
-                    new UnifiedPushDatabase.ApplicationEndpoint(renewal.application, endpoint));
+            final UnifiedPushDatabase.ApplicationEndpoint applicationEndpoint = new UnifiedPushDatabase.ApplicationEndpoint(renewal.application, endpoint);
+            sendEndpoint(messenger, renewal.instance, applicationEndpoint);
+        }
+    }
+
+    private void sendEndpoint(final Messenger messenger, String instance, final UnifiedPushDatabase.ApplicationEndpoint applicationEndpoint) {
+        if (messenger != null) {
+            Log.d(Config.LOGTAG,"using messenger instead of broadcast to communicate endpoint to "+applicationEndpoint.application);
+            final Message message = new Message();
+            message.obj = endpointIntent(instance, applicationEndpoint);
+            try {
+                messenger.send(message);
+            } catch (final RemoteException e) {
+                Log.d(Config.LOGTAG,"messenger failed. falling back to broadcast");
+                broadcastEndpoint(instance, applicationEndpoint);
+            }
+        } else {
+            broadcastEndpoint(instance, applicationEndpoint);
         }
     }
 
@@ -302,14 +332,19 @@ public class UnifiedPushBroker {
     private void broadcastEndpoint(
             final String instance, final UnifiedPushDatabase.ApplicationEndpoint endpoint) {
         Log.d(Config.LOGTAG, "broadcasting endpoint to " + endpoint.application);
-        final Intent updateIntent = new Intent(UnifiedPushDistributor.ACTION_NEW_ENDPOINT);
-        updateIntent.setPackage(endpoint.application);
-        updateIntent.putExtra("token", instance);
-        updateIntent.putExtra("endpoint", endpoint.endpoint);
+        final Intent updateIntent = endpointIntent(instance, endpoint);
         service.sendBroadcast(updateIntent);
     }
 
-    public void rebroadcastEndpoint(final String instance, final Transport transport) {
+    private static Intent endpointIntent(final String instance, final UnifiedPushDatabase.ApplicationEndpoint endpoint) {
+        final Intent intent = new Intent(UnifiedPushDistributor.ACTION_NEW_ENDPOINT);
+        intent.setPackage(endpoint.application);
+        intent.putExtra("token", instance);
+        intent.putExtra("endpoint", endpoint.endpoint);
+        return intent;
+    }
+
+    public void rebroadcastEndpoint(final Messenger messenger, final String instance, final Transport transport) {
         final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(service);
         final UnifiedPushDatabase.ApplicationEndpoint endpoint =
                 unifiedPushDatabase.getEndpoint(
@@ -317,7 +352,7 @@ public class UnifiedPushBroker {
                         transport.transport.toEscapedString(),
                         instance);
         if (endpoint != null) {
-            broadcastEndpoint(instance, endpoint);
+            sendEndpoint(messenger, instance, endpoint);
         }
     }
 
@@ -330,4 +365,14 @@ public class UnifiedPushBroker {
             this.transport = transport;
         }
     }
+
+    public static class PushTargetMessenger {
+        private final UnifiedPushDatabase.PushTarget pushTarget;
+        private final Messenger messenger;
+
+        public PushTargetMessenger(UnifiedPushDatabase.PushTarget pushTarget, Messenger messenger) {
+            this.pushTarget = pushTarget;
+            this.messenger = messenger;
+        }
+    }
 }

src/main/java/eu/siacs/conversations/services/UnifiedPushDistributor.java 🔗

@@ -1,10 +1,13 @@
 package eu.siacs.conversations.services;
 
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.os.Messenger;
+import android.os.Parcelable;
 import android.util.Log;
 
 import com.google.common.base.Charsets;
@@ -46,12 +49,21 @@ public class UnifiedPushDistributor extends BroadcastReceiver {
             return;
         }
         final String action = intent.getAction();
-        final String application = intent.getStringExtra("application");
+        final String application;
+        final Parcelable appByPendingIntent = intent.getParcelableExtra("app");
+        if (appByPendingIntent instanceof PendingIntent) {
+            final PendingIntent pendingIntent = (PendingIntent) appByPendingIntent;
+            application = pendingIntent.getIntentSender().getCreatorPackage();
+            Log.d(Config.LOGTAG,"received application name via pending intent "+ application);
+        } else {
+            application = intent.getStringExtra("application");
+        }
+        final Parcelable messenger = intent.getParcelableExtra("messenger");
         final String instance = intent.getStringExtra("token");
         final List<String> features = intent.getStringArrayListExtra("features");
         switch (Strings.nullToEmpty(action)) {
             case ACTION_REGISTER:
-                register(context, application, instance, features);
+                register(context, application, instance, features, messenger);
                 break;
             case ACTION_UNREGISTER:
                 unregister(context, instance);
@@ -69,7 +81,8 @@ public class UnifiedPushDistributor extends BroadcastReceiver {
             final Context context,
             final String application,
             final String instance,
-            final Collection<String> features) {
+            final Collection<String> features,
+            final Parcelable messenger) {
         if (Strings.isNullOrEmpty(application) || Strings.isNullOrEmpty(instance)) {
             Log.w(Config.LOGTAG, "ignoring invalid UnifiedPush registration");
             return;
@@ -92,6 +105,10 @@ public class UnifiedPushDistributor extends BroadcastReceiver {
                 final Intent serviceIntent = new Intent(context, XmppConnectionService.class);
                 serviceIntent.setAction(XmppConnectionService.ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS);
                 serviceIntent.putExtra("instance", instance);
+                serviceIntent.putExtra("application", application);
+                if (messenger instanceof Messenger) {
+                    serviceIntent.putExtra("messenger", messenger);
+                }
                 Compatibility.startService(context, serviceIntent);
             } else {
                 Log.d(Config.LOGTAG, "not successful. sending error message back to application");

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -32,6 +32,8 @@ import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.IBinder;
+import android.os.Messenger;
+import android.os.Parcelable;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
@@ -815,9 +817,18 @@ public class XmppConnectionService extends Service {
                     break;
                 case ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS:
                     final String instance = intent.getStringExtra("instance");
-                    final Optional<UnifiedPushBroker.Transport> transport = renewUnifiedPushEndpoints();
+                    final String application = intent.getStringExtra("application");
+                    final Messenger messenger = intent.getParcelableExtra("messenger");
+                    final UnifiedPushBroker.PushTargetMessenger pushTargetMessenger;
+                    if (messenger != null && application != null && instance != null) {
+                        pushTargetMessenger = new UnifiedPushBroker.PushTargetMessenger(new UnifiedPushDatabase.PushTarget(application, instance),messenger);
+                        Log.d(Config.LOGTAG,"found push target messenger");
+                    } else {
+                        pushTargetMessenger = null;
+                    }
+                    final Optional<UnifiedPushBroker.Transport> transport = renewUnifiedPushEndpoints(pushTargetMessenger);
                     if (instance != null && transport.isPresent()) {
-                        unifiedPushBroker.rebroadcastEndpoint(instance, transport.get());
+                        unifiedPushBroker.rebroadcastEndpoint(messenger, instance, transport.get());
                     }
                     break;
                 case ACTION_IDLE_PING:
@@ -2363,8 +2374,12 @@ public class XmppConnectionService extends Service {
         return this.unifiedPushBroker.reconfigurePushDistributor();
     }
 
+    private Optional<UnifiedPushBroker.Transport> renewUnifiedPushEndpoints(final UnifiedPushBroker.PushTargetMessenger pushTargetMessenger) {
+        return this.unifiedPushBroker.renewUnifiedPushEndpoints(pushTargetMessenger);
+    }
+
     public Optional<UnifiedPushBroker.Transport> renewUnifiedPushEndpoints() {
-        return this.unifiedPushBroker.renewUnifiedPushEndpoints();
+        return this.unifiedPushBroker.renewUnifiedPushEndpoints(null);
     }
 
     private void provisionAccount(final String address, final String password) {