@capgo/capacitor-media-session 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CapgoMediaSession.podspec +17 -0
  2. package/Package.swift +28 -0
  3. package/README.md +164 -0
  4. package/android/build.gradle +60 -0
  5. package/android/src/main/AndroidManifest.xml +26 -0
  6. package/android/src/main/java/com/capgo/mediasession/MediaSessionCallback.java +55 -0
  7. package/android/src/main/java/com/capgo/mediasession/MediaSessionPlugin.java +277 -0
  8. package/android/src/main/java/com/capgo/mediasession/MediaSessionService.java +366 -0
  9. package/android/src/main/res/.gitkeep +0 -0
  10. package/android/src/main/res/drawable/ic_baseline_forward_30_24.xml +16 -0
  11. package/android/src/main/res/drawable/ic_baseline_pause_24.xml +10 -0
  12. package/android/src/main/res/drawable/ic_baseline_play_arrow_24.xml +10 -0
  13. package/android/src/main/res/drawable/ic_baseline_replay_30_24.xml +16 -0
  14. package/android/src/main/res/drawable/ic_baseline_skip_next_24.xml +10 -0
  15. package/android/src/main/res/drawable/ic_baseline_skip_previous_24.xml +10 -0
  16. package/android/src/main/res/drawable/ic_baseline_stop_24.xml +10 -0
  17. package/android/src/main/res/drawable/ic_baseline_volume_up_24.xml +10 -0
  18. package/dist/docs.json +326 -0
  19. package/dist/esm/definitions.d.ts +48 -0
  20. package/dist/esm/definitions.js +2 -0
  21. package/dist/esm/definitions.js.map +1 -0
  22. package/dist/esm/index.d.ts +4 -0
  23. package/dist/esm/index.js +8 -0
  24. package/dist/esm/index.js.map +1 -0
  25. package/dist/esm/web.d.ts +8 -0
  26. package/dist/esm/web.js +38 -0
  27. package/dist/esm/web.js.map +1 -0
  28. package/dist/plugin.cjs.js +53 -0
  29. package/dist/plugin.cjs.js.map +1 -0
  30. package/dist/plugin.js +56 -0
  31. package/dist/plugin.js.map +1 -0
  32. package/ios/Sources/MediaSessionPlugin/MediaSessionPlugin.swift +9 -0
  33. package/ios/Tests/MediaSessionPluginTests/MediaSessionPluginTests.swift +15 -0
  34. package/package.json +83 -0
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapgoMediaSession'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '14.0'
15
+ s.dependency 'Capacitor'
16
+ s.swift_version = '5.1'
17
+ end
package/Package.swift ADDED
@@ -0,0 +1,28 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CapgoMediaSession",
6
+ platforms: [.iOS(.v14)],
7
+ products: [
8
+ .library(
9
+ name: "CapgoMediaSession",
10
+ targets: ["MediaSessionPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0")
14
+ ],
15
+ targets: [
16
+ .target(
17
+ name: "MediaSessionPlugin",
18
+ dependencies: [
19
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
+ .product(name: "Cordova", package: "capacitor-swift-pm")
21
+ ],
22
+ path: "ios/Sources/MediaSessionPlugin"),
23
+ .testTarget(
24
+ name: "MediaSessionPluginTests",
25
+ dependencies: ["MediaSessionPlugin"],
26
+ path: "ios/Tests/MediaSessionPluginTests")
27
+ ]
28
+ )
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # @capgo/capacitor-media-session
2
+ <a href="https://capgo.app/"><img src='https://raw.githubusercontent.com/Cap-go/capgo/main/assets/capgo_banner.png' alt='Capgo - Instant updates for capacitor'/></a>
3
+
4
+ <div align="center">
5
+ <h2><a href="https://capgo.app/?ref=plugin"> ➡️ Get Instant updates for your App with Capgo</a></h2>
6
+ <h2><a href="https://capgo.app/consulting/?ref=plugin"> Missing a feature? We’ll build the plugin for you 💪</a></h2>
7
+ </div>
8
+ Expose media session controls for Capacitor apps
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @capgo/capacitor-media-session
14
+ npx cap sync
15
+ ```
16
+
17
+ ## API
18
+
19
+ <docgen-index>
20
+
21
+ * [`setMetadata(...)`](#setmetadata)
22
+ * [`setPlaybackState(...)`](#setplaybackstate)
23
+ * [`setActionHandler(...)`](#setactionhandler)
24
+ * [`setPositionState(...)`](#setpositionstate)
25
+ * [Interfaces](#interfaces)
26
+ * [Type Aliases](#type-aliases)
27
+
28
+ </docgen-index>
29
+
30
+ <docgen-api>
31
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
32
+
33
+ ### setMetadata(...)
34
+
35
+ ```typescript
36
+ setMetadata(options: MetadataOptions) => Promise<void>
37
+ ```
38
+
39
+ Sets metadata of the currently playing media.
40
+
41
+ | Param | Type |
42
+ | ------------- | ----------------------------------------------------------- |
43
+ | **`options`** | <code><a href="#metadataoptions">MetadataOptions</a></code> |
44
+
45
+ --------------------
46
+
47
+
48
+ ### setPlaybackState(...)
49
+
50
+ ```typescript
51
+ setPlaybackState(options: PlaybackStateOptions) => Promise<void>
52
+ ```
53
+
54
+ Updates the playback state of the media session.
55
+
56
+ | Param | Type |
57
+ | ------------- | --------------------------------------------------------------------- |
58
+ | **`options`** | <code><a href="#playbackstateoptions">PlaybackStateOptions</a></code> |
59
+
60
+ --------------------
61
+
62
+
63
+ ### setActionHandler(...)
64
+
65
+ ```typescript
66
+ setActionHandler(options: ActionHandlerOptions, handler: ActionHandler | null) => Promise<void>
67
+ ```
68
+
69
+ Registers a handler for a media session action.
70
+
71
+ | Param | Type |
72
+ | ------------- | --------------------------------------------------------------------- |
73
+ | **`options`** | <code><a href="#actionhandleroptions">ActionHandlerOptions</a></code> |
74
+ | **`handler`** | <code><a href="#actionhandler">ActionHandler</a> \| null</code> |
75
+
76
+ --------------------
77
+
78
+
79
+ ### setPositionState(...)
80
+
81
+ ```typescript
82
+ setPositionState(options: PositionStateOptions) => Promise<void>
83
+ ```
84
+
85
+ Updates position state for the active media session.
86
+
87
+ | Param | Type |
88
+ | ------------- | --------------------------------------------------------------------- |
89
+ | **`options`** | <code><a href="#positionstateoptions">PositionStateOptions</a></code> |
90
+
91
+ --------------------
92
+
93
+
94
+ ### Interfaces
95
+
96
+
97
+ #### MetadataOptions
98
+
99
+ | Prop | Type |
100
+ | ------------- | ------------------------- |
101
+ | **`album`** | <code>string</code> |
102
+ | **`artist`** | <code>string</code> |
103
+ | **`artwork`** | <code>MediaImage[]</code> |
104
+ | **`title`** | <code>string</code> |
105
+
106
+
107
+ #### MediaImage
108
+
109
+ | Prop | Type |
110
+ | ----------- | ------------------- |
111
+ | **`src`** | <code>string</code> |
112
+ | **`sizes`** | <code>string</code> |
113
+ | **`type`** | <code>string</code> |
114
+
115
+
116
+ #### PlaybackStateOptions
117
+
118
+ | Prop | Type |
119
+ | ------------------- | ------------------------------------------------------------------------------- |
120
+ | **`playbackState`** | <code><a href="#mediasessionplaybackstate">MediaSessionPlaybackState</a></code> |
121
+
122
+
123
+ #### ActionHandlerOptions
124
+
125
+ | Prop | Type |
126
+ | ------------ | ----------------------------------------------------------------- |
127
+ | **`action`** | <code><a href="#mediasessionaction">MediaSessionAction</a></code> |
128
+
129
+
130
+ #### ActionDetails
131
+
132
+ | Prop | Type |
133
+ | -------------- | ----------------------------------------------------------------- |
134
+ | **`action`** | <code><a href="#mediasessionaction">MediaSessionAction</a></code> |
135
+ | **`seekTime`** | <code>number \| null</code> |
136
+
137
+
138
+ #### PositionStateOptions
139
+
140
+ | Prop | Type |
141
+ | ------------------ | ------------------- |
142
+ | **`duration`** | <code>number</code> |
143
+ | **`playbackRate`** | <code>number</code> |
144
+ | **`position`** | <code>number</code> |
145
+
146
+
147
+ ### Type Aliases
148
+
149
+
150
+ #### MediaSessionPlaybackState
151
+
152
+ <code>'none' | 'paused' | 'playing'</code>
153
+
154
+
155
+ #### MediaSessionAction
156
+
157
+ <code>'play' | 'pause' | 'seekbackward' | 'seekforward' | 'previoustrack' | 'nexttrack' | 'seekto' | 'stop'</code>
158
+
159
+
160
+ #### ActionHandler
161
+
162
+ <code>(details: <a href="#actiondetails">ActionDetails</a>): void</code>
163
+
164
+ </docgen-api>
@@ -0,0 +1,60 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
6
+ androidxMediaVersion = project.hasProperty('androidxMediaVersion') ? rootProject.ext.androidxMediaVersion : '1.7.0'
7
+ }
8
+
9
+ buildscript {
10
+ repositories {
11
+ google()
12
+ mavenCentral()
13
+ }
14
+ dependencies {
15
+ classpath 'com.android.tools.build:gradle:8.7.2'
16
+ }
17
+ }
18
+
19
+ apply plugin: 'com.android.library'
20
+
21
+ android {
22
+ namespace "com.capgo.mediasession"
23
+ compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
24
+ defaultConfig {
25
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
26
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
27
+ versionCode 1
28
+ versionName "1.0"
29
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
30
+ }
31
+ buildTypes {
32
+ release {
33
+ minifyEnabled false
34
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
35
+ }
36
+ }
37
+ lintOptions {
38
+ abortOnError false
39
+ }
40
+ compileOptions {
41
+ sourceCompatibility JavaVersion.VERSION_21
42
+ targetCompatibility JavaVersion.VERSION_21
43
+ }
44
+ }
45
+
46
+ repositories {
47
+ google()
48
+ mavenCentral()
49
+ }
50
+
51
+
52
+ dependencies {
53
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
54
+ implementation project(':capacitor-android')
55
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
56
+ implementation "androidx.media:media:$androidxMediaVersion"
57
+ testImplementation "junit:junit:$junitVersion"
58
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
59
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
60
+ }
@@ -0,0 +1,26 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+
4
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
5
+
6
+ <application>
7
+ <service
8
+ android:name=".MediaSessionService"
9
+ android:enabled="true"
10
+ android:exported="true"
11
+ android:foregroundServiceType="mediaPlayback">
12
+ <intent-filter>
13
+ <action android:name="android.intent.action.MEDIA_BUTTON" />
14
+ </intent-filter>
15
+ </service>
16
+
17
+ <receiver
18
+ android:name="androidx.media.session.MediaButtonReceiver"
19
+ android:exported="true">
20
+ <intent-filter>
21
+ <action android:name="android.intent.action.MEDIA_BUTTON" />
22
+ </intent-filter>
23
+ </receiver>
24
+ </application>
25
+
26
+ </manifest>
@@ -0,0 +1,55 @@
1
+ package com.capgo.mediasession;
2
+
3
+ import android.support.v4.media.session.MediaSessionCompat;
4
+ import com.getcapacitor.JSObject;
5
+
6
+ public class MediaSessionCallback extends MediaSessionCompat.Callback {
7
+
8
+ private final MediaSessionPlugin plugin;
9
+
10
+ MediaSessionCallback(MediaSessionPlugin plugin) {
11
+ this.plugin = plugin;
12
+ }
13
+
14
+ @Override
15
+ public void onPlay() {
16
+ plugin.actionCallback("play");
17
+ }
18
+
19
+ @Override
20
+ public void onPause() {
21
+ plugin.actionCallback("pause");
22
+ }
23
+
24
+ @Override
25
+ public void onSeekTo(long pos) {
26
+ JSObject data = new JSObject();
27
+ data.put("seekTime", (double) pos / 1000.0);
28
+ plugin.actionCallback("seekto", data);
29
+ }
30
+
31
+ @Override
32
+ public void onRewind() {
33
+ plugin.actionCallback("seekbackward");
34
+ }
35
+
36
+ @Override
37
+ public void onFastForward() {
38
+ plugin.actionCallback("seekforward");
39
+ }
40
+
41
+ @Override
42
+ public void onSkipToPrevious() {
43
+ plugin.actionCallback("previoustrack");
44
+ }
45
+
46
+ @Override
47
+ public void onSkipToNext() {
48
+ plugin.actionCallback("nexttrack");
49
+ }
50
+
51
+ @Override
52
+ public void onStop() {
53
+ plugin.actionCallback("stop");
54
+ }
55
+ }
@@ -0,0 +1,277 @@
1
+ package com.capgo.mediasession;
2
+
3
+ import android.content.ComponentName;
4
+ import android.content.Context;
5
+ import android.content.Intent;
6
+ import android.content.ServiceConnection;
7
+ import android.graphics.Bitmap;
8
+ import android.graphics.BitmapFactory;
9
+ import android.os.IBinder;
10
+ import android.support.v4.media.session.PlaybackStateCompat;
11
+ import android.util.Base64;
12
+ import android.util.Log;
13
+ import androidx.core.content.ContextCompat;
14
+ import com.getcapacitor.JSArray;
15
+ import com.getcapacitor.JSObject;
16
+ import com.getcapacitor.Plugin;
17
+ import com.getcapacitor.PluginCall;
18
+ import com.getcapacitor.PluginMethod;
19
+ import com.getcapacitor.annotation.CapacitorPlugin;
20
+ import java.io.IOException;
21
+ import java.io.InputStream;
22
+ import java.net.HttpURLConnection;
23
+ import java.net.URL;
24
+ import java.util.HashMap;
25
+ import java.util.List;
26
+ import java.util.Map;
27
+ import org.json.JSONException;
28
+ import org.json.JSONObject;
29
+
30
+ @CapacitorPlugin(name = "MediaSession")
31
+ public class MediaSessionPlugin extends Plugin {
32
+
33
+ private static final String TAG = "CapgoMediaSession";
34
+
35
+ private boolean startServiceOnlyDuringPlayback = true;
36
+
37
+ private String title = "";
38
+ private String artist = "";
39
+ private String album = "";
40
+ private Bitmap artwork;
41
+ private String playbackState = "none";
42
+ private double duration = 0.0;
43
+ private double position = 0.0;
44
+ private double playbackRate = 1.0;
45
+
46
+ private final Map<String, PluginCall> actionHandlers = new HashMap<>();
47
+
48
+ private MediaSessionService service;
49
+
50
+ private final ServiceConnection serviceConnection = new ServiceConnection() {
51
+ @Override
52
+ public void onServiceConnected(ComponentName componentName, IBinder binder) {
53
+ MediaSessionService.LocalBinder localBinder = (MediaSessionService.LocalBinder) binder;
54
+ service = localBinder.getService();
55
+ if (getActivity() == null) {
56
+ return;
57
+ }
58
+ Intent launchIntent = new Intent(getActivity(), getActivity().getClass());
59
+ service.connectAndInitialize(MediaSessionPlugin.this, launchIntent);
60
+ updateServiceMetadata();
61
+ updateServicePlaybackState();
62
+ updateServicePositionState();
63
+ }
64
+
65
+ @Override
66
+ public void onServiceDisconnected(ComponentName componentName) {
67
+ Log.d(TAG, "Disconnected from MediaSessionService");
68
+ service = null;
69
+ }
70
+ };
71
+
72
+ @Override
73
+ public void load() {
74
+ super.load();
75
+
76
+ String foregroundServiceConfig = getConfig().getString("foregroundService", "");
77
+ if ("always".equals(foregroundServiceConfig)) {
78
+ startServiceOnlyDuringPlayback = false;
79
+ }
80
+
81
+ if (!startServiceOnlyDuringPlayback) {
82
+ startMediaService();
83
+ }
84
+ }
85
+
86
+ @Override
87
+ protected void handleAppStop() {
88
+ super.handleAppStop();
89
+
90
+ if (startServiceOnlyDuringPlayback && service != null && !isPlaybackActive()) {
91
+ stopMediaService();
92
+ }
93
+ }
94
+
95
+ private void startMediaService() {
96
+ Context context = getContext();
97
+ if (context == null || getActivity() == null) {
98
+ return;
99
+ }
100
+
101
+ Intent intent = new Intent(context, MediaSessionService.class);
102
+ ContextCompat.startForegroundService(context, intent);
103
+ context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
104
+ }
105
+
106
+ private void stopMediaService() {
107
+ Context context = getContext();
108
+ if (context == null || service == null) {
109
+ return;
110
+ }
111
+
112
+ try {
113
+ context.unbindService(serviceConnection);
114
+ } catch (IllegalArgumentException ex) {
115
+ Log.w(TAG, "Service already unbound", ex);
116
+ }
117
+ service = null;
118
+ }
119
+
120
+ private boolean isPlaybackActive() {
121
+ return "playing".equals(playbackState) || "paused".equals(playbackState);
122
+ }
123
+
124
+ private void updateServiceMetadata() {
125
+ if (service == null) {
126
+ return;
127
+ }
128
+ service.setTitle(title);
129
+ service.setArtist(artist);
130
+ service.setAlbum(album);
131
+ service.setArtwork(artwork);
132
+ service.update();
133
+ }
134
+
135
+ private void updateServicePlaybackState() {
136
+ if (service == null) {
137
+ return;
138
+ }
139
+
140
+ if ("playing".equals(playbackState)) {
141
+ service.setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
142
+ } else if ("paused".equals(playbackState)) {
143
+ service.setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
144
+ } else {
145
+ service.setPlaybackState(PlaybackStateCompat.STATE_NONE);
146
+ }
147
+ service.update();
148
+ }
149
+
150
+ private void updateServicePositionState() {
151
+ if (service == null) {
152
+ return;
153
+ }
154
+
155
+ service.setDuration(Math.round(duration * 1000));
156
+ service.setPosition(Math.round(position * 1000));
157
+ float playbackSpeed = playbackRate == 0.0 ? 1.0F : (float) playbackRate;
158
+ service.setPlaybackSpeed(playbackSpeed);
159
+ service.update();
160
+ }
161
+
162
+ private Bitmap urlToBitmap(String url) throws IOException {
163
+ if (url == null || url.isEmpty()) {
164
+ return null;
165
+ }
166
+
167
+ boolean blobUrl = url.startsWith("blob:");
168
+ if (blobUrl) {
169
+ Log.i(TAG, "Blob URLs are not supported for media artwork");
170
+ return null;
171
+ }
172
+
173
+ boolean httpUrl = url.startsWith("http");
174
+ if (httpUrl) {
175
+ HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
176
+ connection.setDoInput(true);
177
+ connection.connect();
178
+ try (InputStream inputStream = connection.getInputStream()) {
179
+ return BitmapFactory.decodeStream(inputStream);
180
+ }
181
+ }
182
+
183
+ int base64Index = url.indexOf(";base64,");
184
+ if (base64Index != -1) {
185
+ String base64Data = url.substring(base64Index + 8);
186
+ byte[] decoded = Base64.decode(base64Data, Base64.DEFAULT);
187
+ return BitmapFactory.decodeByteArray(decoded, 0, decoded.length);
188
+ }
189
+
190
+ return null;
191
+ }
192
+
193
+ @PluginMethod
194
+ public void setMetadata(PluginCall call) {
195
+ title = call.getString("title", title);
196
+ artist = call.getString("artist", artist);
197
+ album = call.getString("album", album);
198
+
199
+ try {
200
+ JSArray artworkArray = call.getArray("artwork");
201
+ if (artworkArray != null) {
202
+ List<JSONObject> artworkList = artworkArray.toList();
203
+ for (JSONObject artworkJson : artworkList) {
204
+ String src = artworkJson.optString("src", null);
205
+ if (src != null) {
206
+ artwork = urlToBitmap(src);
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ } catch (JSONException | IOException ex) {
212
+ Log.w(TAG, "Unable to parse artwork", ex);
213
+ }
214
+
215
+ updateServiceMetadata();
216
+ call.resolve();
217
+ }
218
+
219
+ @PluginMethod
220
+ public void setPlaybackState(PluginCall call) {
221
+ playbackState = call.getString("playbackState", playbackState);
222
+
223
+ boolean playbackActive = isPlaybackActive();
224
+ if (startServiceOnlyDuringPlayback && service == null && playbackActive) {
225
+ startMediaService();
226
+ } else if (startServiceOnlyDuringPlayback && service != null && !playbackActive) {
227
+ stopMediaService();
228
+ } else {
229
+ updateServicePlaybackState();
230
+ }
231
+
232
+ call.resolve();
233
+ }
234
+
235
+ @PluginMethod
236
+ public void setPositionState(PluginCall call) {
237
+ duration = call.getDouble("duration", duration);
238
+ position = call.getDouble("position", position);
239
+ playbackRate = call.getFloat("playbackRate", (float) playbackRate);
240
+
241
+ updateServicePositionState();
242
+ call.resolve();
243
+ }
244
+
245
+ @PluginMethod(returnType = PluginMethod.RETURN_CALLBACK)
246
+ public void setActionHandler(PluginCall call) {
247
+ call.setKeepAlive(true);
248
+ String action = call.getString("action");
249
+ if (action != null) {
250
+ actionHandlers.put(action, call);
251
+ if (service != null) {
252
+ service.updatePossibleActions();
253
+ }
254
+ } else {
255
+ call.resolve();
256
+ }
257
+ }
258
+
259
+ public boolean hasActionHandler(String action) {
260
+ PluginCall handler = actionHandlers.get(action);
261
+ return handler != null && !PluginCall.CALLBACK_ID_DANGLING.equals(handler.getCallbackId());
262
+ }
263
+
264
+ public void actionCallback(String action) {
265
+ actionCallback(action, new JSObject());
266
+ }
267
+
268
+ public void actionCallback(String action, JSObject data) {
269
+ PluginCall handler = actionHandlers.get(action);
270
+ if (handler != null && !PluginCall.CALLBACK_ID_DANGLING.equals(handler.getCallbackId())) {
271
+ data.put("action", action);
272
+ handler.resolve(data);
273
+ } else {
274
+ Log.d(TAG, "No handler for action " + action);
275
+ }
276
+ }
277
+ }