1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 2 * vim: ts=4 sw=4 expandtab: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 package org.mozilla.geckoview; 8 9 import org.mozilla.gecko.EventDispatcher; 10 import org.mozilla.gecko.GeckoThread; 11 import org.mozilla.gecko.util.BundleEventListener; 12 import org.mozilla.gecko.util.EventCallback; 13 import org.mozilla.gecko.util.GeckoBundle; 14 import org.mozilla.gecko.util.ThreadUtils; 15 16 import androidx.annotation.NonNull; 17 import androidx.annotation.Nullable; 18 import androidx.annotation.UiThread; 19 import android.util.Log; 20 21 public class WebPushController { 22 private static final String LOGTAG = "WebPushController"; 23 24 private WebPushDelegate mDelegate; 25 private BundleEventListener mEventListener; 26 WebPushController()27 /* package */ WebPushController() { 28 mEventListener = new EventListener(); 29 EventDispatcher.getInstance().registerUiThreadListener(mEventListener, 30 "GeckoView:PushSubscribe", 31 "GeckoView:PushUnsubscribe", 32 "GeckoView:PushGetSubscription"); 33 } 34 35 /** 36 * Sets the {@link WebPushDelegate} for this instance. 37 * 38 * @param delegate The {@link WebPushDelegate} instance. 39 */ 40 @UiThread setDelegate(final @Nullable WebPushDelegate delegate)41 public void setDelegate(final @Nullable WebPushDelegate delegate) { 42 ThreadUtils.assertOnUiThread(); 43 mDelegate = delegate; 44 } 45 46 /** 47 * Send a push event for a given subscription. 48 * 49 * @param scope The Service Worker scope associated with this subscription. 50 */ 51 @UiThread onPushEvent(final @NonNull String scope)52 public void onPushEvent(final @NonNull String scope) { 53 ThreadUtils.assertOnUiThread(); 54 onPushEvent(scope, null); 55 } 56 57 /** 58 * Send a push event with a payload for a given subscription. 59 * @param scope The Service Worker scope associated with this subscription. 60 * @param data The unencrypted payload. 61 */ 62 @UiThread onPushEvent(final @NonNull String scope, final @Nullable byte[] data)63 public void onPushEvent(final @NonNull String scope, final @Nullable byte[] data) { 64 ThreadUtils.assertOnUiThread(); 65 66 GeckoThread.waitForState(GeckoThread.State.JNI_READY).accept(val -> { 67 final GeckoBundle msg = new GeckoBundle(2); 68 msg.putString("scope", scope); 69 msg.putString("data", Base64Utils.encode(data)); 70 EventDispatcher.getInstance().dispatch("GeckoView:PushEvent", msg); 71 }, e -> Log.e(LOGTAG, "Unable to deliver Web Push message", e)); 72 } 73 74 /** 75 * Notify that a given subscription has changed. This is normally a signal to the content 76 * that it needs to re-subscribe. 77 * 78 * @param scope The Service Worker scope associated with this subscription. 79 */ 80 @UiThread onSubscriptionChanged(final @NonNull String scope)81 public void onSubscriptionChanged(final @NonNull String scope) { 82 ThreadUtils.assertOnUiThread(); 83 84 final GeckoBundle msg = new GeckoBundle(1); 85 msg.putString("scope", scope); 86 EventDispatcher.getInstance().dispatch("GeckoView:PushSubscriptionChanged", msg); 87 } 88 89 private class EventListener implements BundleEventListener { 90 91 @Override handleMessage(final String event, final GeckoBundle message, final EventCallback callback)92 public void handleMessage(final String event, final GeckoBundle message, final EventCallback callback) { 93 if (mDelegate == null) { 94 callback.sendError("Not allowed"); 95 return; 96 } 97 98 switch (event) { 99 case "GeckoView:PushSubscribe": { 100 byte[] appServerKey = null; 101 if (message.containsKey("appServerKey")) { 102 appServerKey = Base64Utils.decode(message.getString("appServerKey")); 103 } 104 105 final GeckoResult<WebPushSubscription> result = 106 mDelegate.onSubscribe(message.getString("scope"), appServerKey); 107 108 if (result == null) { 109 callback.sendSuccess(null); 110 return; 111 } 112 113 result.accept(subscription -> callback.sendSuccess(subscription != null ? subscription.toBundle() : null), 114 error -> callback.sendSuccess(null)); 115 break; 116 } 117 case "GeckoView:PushUnsubscribe": { 118 final GeckoResult<Void> result = mDelegate.onUnsubscribe(message.getString("scope")); 119 if (result == null) { 120 callback.sendSuccess(null); 121 return; 122 } 123 124 callback.resolveTo(result.map(val -> null)); 125 break; 126 } 127 case "GeckoView:PushGetSubscription": { 128 final GeckoResult<WebPushSubscription> result = mDelegate.onGetSubscription(message.getString("scope")); 129 if (result == null) { 130 callback.sendSuccess(null); 131 return; 132 } 133 134 callback.resolveTo(result.map(subscription -> 135 subscription != null ? subscription.toBundle() : null)); 136 break; 137 } 138 } 139 } 140 } 141 } 142