UnifiedPush: send unregistered to apps when 'none' account is selected

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/persistance/UnifiedPushDatabase.java | 18 
src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java      | 62 
src/main/java/eu/siacs/conversations/services/UnifiedPushDistributor.java |  8 
3 files changed, 88 insertions(+)

Detailed changes

src/main/java/eu/siacs/conversations/persistance/UnifiedPushDatabase.java 🔗

@@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList;
 
 import org.jetbrains.annotations.NotNull;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import eu.siacs.conversations.Config;
@@ -129,6 +130,23 @@ public class UnifiedPushDatabase extends SQLiteOpenHelper {
         return null;
     }
 
+    public List<PushTarget> deletePushTargets() {
+        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
+        final ImmutableList.Builder<PushTarget> builder = new ImmutableList.Builder<>();
+        try (final Cursor cursor = sqLiteDatabase.query("push",new String[]{"application","instance"},null,null,null,null,null)) {
+            if (cursor != null && cursor.moveToFirst()) {
+                builder.add(new PushTarget(
+                        cursor.getString(cursor.getColumnIndexOrThrow("application")),
+                        cursor.getString(cursor.getColumnIndexOrThrow("instance"))));
+            }
+        } catch (final Exception e) {
+            Log.d(Config.LOGTAG,"unable to retrieve push targets",e);
+            return builder.build();
+        }
+        sqLiteDatabase.delete("push",null,null);
+        return builder.build();
+    }
+
     public boolean hasEndpoints(final UnifiedPushBroker.Transport transport) {
         final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
         try (final Cursor cursor =

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

@@ -10,10 +10,18 @@ import android.os.Messenger;
 import android.os.RemoteException;
 import android.preference.PreferenceManager;
 import android.util.Log;
+
+import androidx.annotation.NonNull;
+
 import com.google.common.base.Optional;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.common.io.BaseEncoding;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
@@ -239,6 +247,9 @@ public class UnifiedPushBroker {
     public boolean reconfigurePushDistributor() {
         final boolean enabled = getTransport().isPresent();
         setUnifiedPushDistributorEnabled(enabled);
+        if (!enabled) {
+            unregisterCurrentPushTargets();
+        }
         return enabled;
     }
 
@@ -261,6 +272,43 @@ public class UnifiedPushBroker {
         }
     }
 
+    private void unregisterCurrentPushTargets() {
+        final var future = deletePushTargets();
+        Futures.addCallback(
+                future,
+                new FutureCallback<>() {
+                    @Override
+                    public void onSuccess(
+                            final List<UnifiedPushDatabase.PushTarget> pushTargets) {
+                        broadcastUnregistered(pushTargets);
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable throwable) {
+                        Log.d(
+                                Config.LOGTAG,
+                                "could not delete endpoints after UnifiedPushDistributor was disabled");
+                    }
+                },
+                MoreExecutors.directExecutor());
+    }
+
+    private ListenableFuture<List<UnifiedPushDatabase.PushTarget>> deletePushTargets() {
+        return Futures.submit(() -> UnifiedPushDatabase.getInstance(service).deletePushTargets(),SCHEDULER);
+    }
+
+    private void broadcastUnregistered(final List<UnifiedPushDatabase.PushTarget> pushTargets) {
+        for(final UnifiedPushDatabase.PushTarget pushTarget : pushTargets) {
+            Log.d(Config.LOGTAG,"sending unregistered to "+pushTarget);
+            broadcastUnregistered(pushTarget);
+        }
+    }
+
+    private void broadcastUnregistered(final UnifiedPushDatabase.PushTarget pushTarget) {
+        final var intent = unregisteredIntent(pushTarget);
+        service.sendBroadcast(intent);
+    }
+
     public boolean processPushMessage(
             final Account account, final Jid transport, final Element push) {
         final String instance = push.getAttribute("instance");
@@ -355,6 +403,7 @@ public class UnifiedPushBroker {
         updateIntent.putExtra("token", target.instance);
         updateIntent.putExtra("bytesMessage", payload);
         updateIntent.putExtra("message", new String(payload, StandardCharsets.UTF_8));
+        // TODO add distributor verification?
         service.sendBroadcast(updateIntent);
     }
 
@@ -379,6 +428,19 @@ public class UnifiedPushBroker {
         return intent;
     }
 
+    private Intent unregisteredIntent(final UnifiedPushDatabase.PushTarget pushTarget) {
+        final Intent intent = new Intent(UnifiedPushDistributor.ACTION_UNREGISTERED);
+        intent.setPackage(pushTarget.application);
+        intent.putExtra("token", pushTarget.instance);
+        final var distributorVerificationIntent = new Intent();
+        distributorVerificationIntent.setPackage(service.getPackageName());
+        final var pendingIntent =
+                PendingIntent.getBroadcast(
+                        service, 0, distributorVerificationIntent, PendingIntent.FLAG_IMMUTABLE);
+        intent.putExtra("distributor", pendingIntent);
+        return intent;
+    }
+
     public void rebroadcastEndpoint(final Messenger messenger, final String instance, final Transport transport) {
         final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(service);
         final UnifiedPushDatabase.ApplicationEndpoint endpoint =

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

@@ -29,8 +29,15 @@ import eu.siacs.conversations.utils.Compatibility;
 
 public class UnifiedPushDistributor extends BroadcastReceiver {
 
+    // distributor actions (these are actios used for connector->distributor broadcasts)
+    // we, the distributor, have a broadcast receiver listening for those actions
+
     public static final String ACTION_REGISTER = "org.unifiedpush.android.distributor.REGISTER";
     public static final String ACTION_UNREGISTER = "org.unifiedpush.android.distributor.UNREGISTER";
+
+
+    // connector actions (these are actions used for distributor->connector broadcasts)
+    public static final String ACTION_UNREGISTERED = "org.unifiedpush.android.connector.UNREGISTERED";
     public static final String ACTION_BYTE_MESSAGE =
             "org.unifiedpush.android.distributor.feature.BYTES_MESSAGE";
     public static final String ACTION_REGISTRATION_FAILED =
@@ -172,6 +179,7 @@ public class UnifiedPushDistributor extends BroadcastReceiver {
         if (unifiedPushDatabase.deleteInstance(instance)) {
             quickLog(context, String.format("successfully unregistered token %s from UnifiedPushed (application requested unregister)", instance));
             Log.d(Config.LOGTAG, "successfully removed " + instance + " from UnifiedPush");
+            // TODO send UNREGISTERED broadcast back to app?!
         }
     }