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

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.
This commit is contained in:
Daniel Gultsch 2023-06-26 16:08:57 +02:00
parent ca1ee4a565
commit e3a121121b
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
4 changed files with 99 additions and 20 deletions

View file

@ -114,6 +114,8 @@
<action android:name="org.unifiedpush.android.distributor.REGISTER" /> <action android:name="org.unifiedpush.android.distributor.REGISTER" />
<action android:name="org.unifiedpush.android.distributor.UNREGISTER" /> <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.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>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" /> <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />

View file

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

View file

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

View file

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