@capgo/capacitor-updater 7.4.0 → 7.5.1

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.
package/README.md CHANGED
@@ -252,6 +252,7 @@ CapacitorUpdater can be configured with these options:
252
252
  | **`appId`** | <code>string</code> | Configure the app id for the app in the config. | <code>undefined</code> | 6.0.0 |
253
253
  | **`keepUrlPathAfterReload`** | <code>boolean</code> | Configure the plugin to keep the URL path after a reload. WARNING: When a reload is triggered, 'window.history' will be cleared. | <code>false</code> | 6.8.0 |
254
254
  | **`disableJSLogging`** | <code>boolean</code> | Disable the JavaScript logging of the plugin. if true, the plugin will not log to the JavaScript console. only the native log will be done | <code>false</code> | 7.3.0 |
255
+ | **`shakeMenu`** | <code>boolean</code> | Enable shake gesture to show update menu for debugging/testing purposes | <code>false</code> | 7.5.0 |
255
256
 
256
257
  ### Examples
257
258
 
@@ -285,7 +286,8 @@ In `capacitor.config.json`:
285
286
  "defaultChannel": undefined,
286
287
  "appId": undefined,
287
288
  "keepUrlPathAfterReload": undefined,
288
- "disableJSLogging": undefined
289
+ "disableJSLogging": undefined,
290
+ "shakeMenu": undefined
289
291
  }
290
292
  }
291
293
  }
@@ -326,6 +328,7 @@ const config: CapacitorConfig = {
326
328
  appId: undefined,
327
329
  keepUrlPathAfterReload: undefined,
328
330
  disableJSLogging: undefined,
331
+ shakeMenu: undefined,
329
332
  },
330
333
  },
331
334
  };
@@ -357,6 +360,7 @@ export default config;
357
360
  * [`setChannel(...)`](#setchannel)
358
361
  * [`unsetChannel(...)`](#unsetchannel)
359
362
  * [`getChannel()`](#getchannel)
363
+ * [`listChannels()`](#listchannels)
360
364
  * [`setCustomId(...)`](#setcustomid)
361
365
  * [`getBuiltinVersion()`](#getbuiltinversion)
362
366
  * [`getDeviceId()`](#getdeviceid)
@@ -374,6 +378,8 @@ export default config;
374
378
  * [`addListener('appReady', ...)`](#addlistenerappready-)
375
379
  * [`isAutoUpdateAvailable()`](#isautoupdateavailable)
376
380
  * [`getNextBundle()`](#getnextbundle)
381
+ * [`setShakeMenu(...)`](#setshakemenu)
382
+ * [`isShakeMenuEnabled()`](#isshakemenuenabled)
377
383
  * [Interfaces](#interfaces)
378
384
  * [Type Aliases](#type-aliases)
379
385
 
@@ -678,6 +684,21 @@ Get the channel for this device
678
684
  --------------------
679
685
 
680
686
 
687
+ ### listChannels()
688
+
689
+ ```typescript
690
+ listChannels() => Promise<ListChannelsResult>
691
+ ```
692
+
693
+ List all channels available for this device that allow self-assignment
694
+
695
+ **Returns:** <code>Promise&lt;<a href="#listchannelsresult">ListChannelsResult</a>&gt;</code>
696
+
697
+ **Since:** 7.5.0
698
+
699
+ --------------------
700
+
701
+
681
702
  ### setCustomId(...)
682
703
 
683
704
  ```typescript
@@ -972,6 +993,38 @@ Returns null if no next bundle is set.
972
993
  --------------------
973
994
 
974
995
 
996
+ ### setShakeMenu(...)
997
+
998
+ ```typescript
999
+ setShakeMenu(options: SetShakeMenuOptions) => Promise<void>
1000
+ ```
1001
+
1002
+ Enable or disable the shake menu for debugging/testing purposes
1003
+
1004
+ | Param | Type | Description |
1005
+ | ------------- | ------------------------------------------------------------------- | -------------------------------------------------------- |
1006
+ | **`options`** | <code><a href="#setshakemenuoptions">SetShakeMenuOptions</a></code> | Contains enabled boolean to enable or disable shake menu |
1007
+
1008
+ **Since:** 7.5.0
1009
+
1010
+ --------------------
1011
+
1012
+
1013
+ ### isShakeMenuEnabled()
1014
+
1015
+ ```typescript
1016
+ isShakeMenuEnabled() => Promise<ShakeMenuEnabled>
1017
+ ```
1018
+
1019
+ Get the current state of the shake menu
1020
+
1021
+ **Returns:** <code>Promise&lt;<a href="#shakemenuenabled">ShakeMenuEnabled</a>&gt;</code>
1022
+
1023
+ **Since:** 7.5.0
1024
+
1025
+ --------------------
1026
+
1027
+
975
1028
  ### Interfaces
976
1029
 
977
1030
 
@@ -1145,6 +1198,23 @@ If you don't use backend, you need to provide the URL and version of the bundle.
1145
1198
  | **`allowSet`** | <code>boolean</code> | | |
1146
1199
 
1147
1200
 
1201
+ #### ListChannelsResult
1202
+
1203
+ | Prop | Type | Description | Since |
1204
+ | -------------- | -------------------------- | -------------------------- | ----- |
1205
+ | **`channels`** | <code>ChannelInfo[]</code> | List of available channels | 7.5.0 |
1206
+
1207
+
1208
+ #### ChannelInfo
1209
+
1210
+ | Prop | Type | Description | Since |
1211
+ | -------------------- | -------------------- | ----------------------------------------------- | ----- |
1212
+ | **`id`** | <code>string</code> | The channel ID | 7.5.0 |
1213
+ | **`name`** | <code>string</code> | The channel name | 7.5.0 |
1214
+ | **`public`** | <code>boolean</code> | Whether this is a public channel | 7.5.0 |
1215
+ | **`allow_self_set`** | <code>boolean</code> | Whether devices can self-assign to this channel | 7.5.0 |
1216
+
1217
+
1148
1218
  #### SetCustomIdOptions
1149
1219
 
1150
1220
  | Prop | Type |
@@ -1252,6 +1322,20 @@ If you don't use backend, you need to provide the URL and version of the bundle.
1252
1322
  | **`available`** | <code>boolean</code> |
1253
1323
 
1254
1324
 
1325
+ #### SetShakeMenuOptions
1326
+
1327
+ | Prop | Type |
1328
+ | ------------- | -------------------- |
1329
+ | **`enabled`** | <code>boolean</code> |
1330
+
1331
+
1332
+ #### ShakeMenuEnabled
1333
+
1334
+ | Prop | Type |
1335
+ | ------------- | -------------------- |
1336
+ | **`enabled`** | <code>boolean</code> |
1337
+
1338
+
1255
1339
  ### Type Aliases
1256
1340
 
1257
1341
 
@@ -60,7 +60,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
60
60
  private static final String statsUrlDefault = "https://plugin.capgo.app/stats";
61
61
  private static final String channelUrlDefault = "https://plugin.capgo.app/channel_self";
62
62
 
63
- private final String PLUGIN_VERSION = "7.4.0";
63
+ private final String PLUGIN_VERSION = "7.5.1";
64
64
  private static final String DELAY_CONDITION_PREFERENCES = "";
65
65
 
66
66
  private SharedPreferences.Editor editor;
@@ -78,6 +78,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
78
78
  private Thread backgroundTask;
79
79
  private Boolean taskRunning = false;
80
80
  private Boolean keepUrlPathAfterReload = false;
81
+ Boolean shakeMenuEnabled = false;
81
82
 
82
83
  private Boolean isPreviousMainActivity = true;
83
84
 
@@ -91,6 +92,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
91
92
 
92
93
  private DelayUpdateUtils delayUpdateUtils;
93
94
 
95
+ private ShakeMenu shakeMenu;
96
+
94
97
  private JSObject mapToJSObject(Map<String, Object> map) {
95
98
  JSObject jsObject = new JSObject();
96
99
  for (Map.Entry<String, Object> entry : map.entrySet()) {
@@ -224,6 +227,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
224
227
  this.appReadyTimeout = this.getConfig().getInt("appReadyTimeout", 10000);
225
228
  this.keepUrlPathAfterReload = this.getConfig().getBoolean("keepUrlPathAfterReload", false);
226
229
  this.implementation.timeout = this.getConfig().getInt("responseTimeout", 20) * 1000;
230
+ this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
227
231
  boolean resetWhenUpdate = this.getConfig().getBoolean("resetWhenUpdate", true);
228
232
 
229
233
  this.implementation.autoReset();
@@ -438,7 +442,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
438
442
  CapacitorUpdaterPlugin.this.implementation.unsetChannel(res -> {
439
443
  JSObject jsRes = mapToJSObject(res);
440
444
  if (jsRes.has("error")) {
441
- call.reject(jsRes.getString("error"));
445
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
446
+ String errorCode = jsRes.getString("error");
447
+
448
+ JSObject errorObj = new JSObject();
449
+ errorObj.put("message", errorMessage);
450
+ errorObj.put("error", errorCode);
451
+
452
+ call.reject(errorMessage, "UNSETCHANNEL_FAILED", null, errorObj);
442
453
  } else {
443
454
  if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
444
455
  logger.info("Calling autoupdater after channel change!");
@@ -461,7 +472,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
461
472
 
462
473
  if (channel == null) {
463
474
  logger.error("setChannel called without channel");
464
- call.reject("setChannel called without channel");
475
+ JSObject errorObj = new JSObject();
476
+ errorObj.put("message", "setChannel called without channel");
477
+ errorObj.put("error", "missing_parameter");
478
+ call.reject("setChannel called without channel", "SETCHANNEL_INVALID_PARAMS", null, errorObj);
465
479
  return;
466
480
  }
467
481
  try {
@@ -470,7 +484,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
470
484
  CapacitorUpdaterPlugin.this.implementation.setChannel(channel, res -> {
471
485
  JSObject jsRes = mapToJSObject(res);
472
486
  if (jsRes.has("error")) {
473
- call.reject(jsRes.getString("error"));
487
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
488
+ String errorCode = jsRes.getString("error");
489
+
490
+ JSObject errorObj = new JSObject();
491
+ errorObj.put("message", errorMessage);
492
+ errorObj.put("error", errorCode);
493
+
494
+ call.reject(errorMessage, "SETCHANNEL_FAILED", null, errorObj);
474
495
  } else {
475
496
  if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
476
497
  logger.info("Calling autoupdater after channel change!");
@@ -494,7 +515,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
494
515
  CapacitorUpdaterPlugin.this.implementation.getChannel(res -> {
495
516
  JSObject jsRes = mapToJSObject(res);
496
517
  if (jsRes.has("error")) {
497
- call.reject(jsRes.getString("error"));
518
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
519
+ String errorCode = jsRes.getString("error");
520
+
521
+ JSObject errorObj = new JSObject();
522
+ errorObj.put("message", errorMessage);
523
+ errorObj.put("error", errorCode);
524
+
525
+ call.reject(errorMessage, "GETCHANNEL_FAILED", null, errorObj);
498
526
  } else {
499
527
  call.resolve(jsRes);
500
528
  }
@@ -506,6 +534,33 @@ public class CapacitorUpdaterPlugin extends Plugin {
506
534
  }
507
535
  }
508
536
 
537
+ @PluginMethod
538
+ public void listChannels(final PluginCall call) {
539
+ try {
540
+ logger.info("listChannels");
541
+ startNewThread(() ->
542
+ CapacitorUpdaterPlugin.this.implementation.listChannels(res -> {
543
+ JSObject jsRes = mapToJSObject(res);
544
+ if (jsRes.has("error")) {
545
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
546
+ String errorCode = jsRes.getString("error");
547
+
548
+ JSObject errorObj = new JSObject();
549
+ errorObj.put("message", errorMessage);
550
+ errorObj.put("error", errorCode);
551
+
552
+ call.reject(errorMessage, "LISTCHANNELS_FAILED", null, errorObj);
553
+ } else {
554
+ call.resolve(jsRes);
555
+ }
556
+ })
557
+ );
558
+ } catch (final Exception e) {
559
+ logger.error("Failed to listChannels: " + e.getMessage());
560
+ call.reject("Failed to listChannels", e);
561
+ }
562
+ }
563
+
509
564
  @PluginMethod
510
565
  public void download(final PluginCall call) {
511
566
  final String url = call.getString("url");
@@ -1328,6 +1383,16 @@ public class CapacitorUpdaterPlugin extends Plugin {
1328
1383
  }
1329
1384
  logger.info("onActivityStarted " + getActivity().getClass().getName());
1330
1385
  isPreviousMainActivity = true;
1386
+
1387
+ // Initialize shake menu if enabled and activity is BridgeActivity
1388
+ if (shakeMenuEnabled && getActivity() instanceof com.getcapacitor.BridgeActivity && shakeMenu == null) {
1389
+ try {
1390
+ shakeMenu = new ShakeMenu(this, (com.getcapacitor.BridgeActivity) getActivity(), logger);
1391
+ logger.info("Shake menu initialized");
1392
+ } catch (Exception e) {
1393
+ logger.error("Failed to initialize shake menu: " + e.getMessage());
1394
+ }
1395
+ }
1331
1396
  }
1332
1397
 
1333
1398
  @Override
@@ -1359,5 +1424,61 @@ public class CapacitorUpdaterPlugin extends Plugin {
1359
1424
  if (counterActivityCreate == 0) {
1360
1425
  this.appKilled();
1361
1426
  }
1427
+
1428
+ // Clean up shake menu
1429
+ if (shakeMenu != null) {
1430
+ try {
1431
+ shakeMenu.stop();
1432
+ shakeMenu = null;
1433
+ logger.info("Shake menu cleaned up");
1434
+ } catch (Exception e) {
1435
+ logger.error("Failed to clean up shake menu: " + e.getMessage());
1436
+ }
1437
+ }
1438
+ }
1439
+
1440
+ @PluginMethod
1441
+ public void setShakeMenu(final PluginCall call) {
1442
+ final Boolean enabled = call.getBoolean("enabled");
1443
+ if (enabled == null) {
1444
+ logger.error("setShakeMenu called without enabled parameter");
1445
+ call.reject("setShakeMenu called without enabled parameter");
1446
+ return;
1447
+ }
1448
+
1449
+ this.shakeMenuEnabled = enabled;
1450
+ logger.info("Shake menu " + (enabled ? "enabled" : "disabled"));
1451
+
1452
+ // Manage shake menu instance based on enabled state
1453
+ if (enabled && getActivity() instanceof com.getcapacitor.BridgeActivity && shakeMenu == null) {
1454
+ try {
1455
+ shakeMenu = new ShakeMenu(this, (com.getcapacitor.BridgeActivity) getActivity(), logger);
1456
+ logger.info("Shake menu initialized");
1457
+ } catch (Exception e) {
1458
+ logger.error("Failed to initialize shake menu: " + e.getMessage());
1459
+ }
1460
+ } else if (!enabled && shakeMenu != null) {
1461
+ try {
1462
+ shakeMenu.stop();
1463
+ shakeMenu = null;
1464
+ logger.info("Shake menu stopped");
1465
+ } catch (Exception e) {
1466
+ logger.error("Failed to stop shake menu: " + e.getMessage());
1467
+ }
1468
+ }
1469
+
1470
+ call.resolve();
1471
+ }
1472
+
1473
+ @PluginMethod
1474
+ public void isShakeMenuEnabled(final PluginCall call) {
1475
+ try {
1476
+ final JSObject ret = new JSObject();
1477
+ ret.put("enabled", this.shakeMenuEnabled);
1478
+ call.resolve(ret);
1479
+ } catch (final Exception e) {
1480
+ logger.error("Could not get shake menu status " + e.getMessage());
1481
+ call.reject("Could not get shake menu status", e);
1482
+ }
1362
1483
  }
1363
1484
  }
@@ -35,6 +35,7 @@ import java.util.Objects;
35
35
  import java.util.zip.ZipEntry;
36
36
  import java.util.zip.ZipInputStream;
37
37
  import okhttp3.*;
38
+ import okhttp3.HttpUrl;
38
39
  import org.json.JSONArray;
39
40
  import org.json.JSONException;
40
41
  import org.json.JSONObject;
@@ -699,6 +700,16 @@ public class CapgoUpdater {
699
700
  assert responseBody != null;
700
701
  String responseData = responseBody.string();
701
702
  JSONObject jsonResponse = new JSONObject(responseData);
703
+
704
+ // Check for server-side errors first
705
+ if (jsonResponse.has("error")) {
706
+ Map<String, Object> retError = new HashMap<>();
707
+ retError.put("message", jsonResponse.getString("error"));
708
+ retError.put("error", "server_error");
709
+ callback.callback(retError);
710
+ return;
711
+ }
712
+
702
713
  Map<String, Object> ret = new HashMap<>();
703
714
 
704
715
  Iterator<String> keys = jsonResponse.keys();
@@ -798,6 +809,16 @@ public class CapgoUpdater {
798
809
  assert responseBody != null;
799
810
  String responseData = responseBody.string();
800
811
  JSONObject jsonResponse = new JSONObject(responseData);
812
+
813
+ // Check for server-side errors first
814
+ if (jsonResponse.has("error")) {
815
+ Map<String, Object> retError = new HashMap<>();
816
+ retError.put("message", jsonResponse.getString("error"));
817
+ retError.put("error", "server_error");
818
+ callback.callback(retError);
819
+ return;
820
+ }
821
+
801
822
  Map<String, Object> ret = new HashMap<>();
802
823
 
803
824
  Iterator<String> keys = jsonResponse.keys();
@@ -912,6 +933,16 @@ public class CapgoUpdater {
912
933
  assert responseBody != null;
913
934
  String responseData = responseBody.string();
914
935
  JSONObject jsonResponse = new JSONObject(responseData);
936
+
937
+ // Check for server-side errors first
938
+ if (jsonResponse.has("error")) {
939
+ Map<String, Object> retError = new HashMap<>();
940
+ retError.put("message", jsonResponse.getString("error"));
941
+ retError.put("error", "server_error");
942
+ callback.callback(retError);
943
+ return;
944
+ }
945
+
915
946
  Map<String, Object> ret = new HashMap<>();
916
947
 
917
948
  Iterator<String> keys = jsonResponse.keys();
@@ -934,6 +965,109 @@ public class CapgoUpdater {
934
965
  );
935
966
  }
936
967
 
968
+ public void listChannels(final Callback callback) {
969
+ String channelUrl = this.channelUrl;
970
+ if (channelUrl == null || channelUrl.isEmpty()) {
971
+ logger.error("Channel URL is not set");
972
+ final Map<String, Object> retError = new HashMap<>();
973
+ retError.put("message", "Channel URL is not set");
974
+ retError.put("error", "missing_config");
975
+ callback.callback(retError);
976
+ return;
977
+ }
978
+
979
+ // Auto-detect values
980
+ String appId = this.appId;
981
+ String platform = "android";
982
+ boolean isEmulator = this.isEmulator();
983
+ boolean isProd = this.isProd();
984
+
985
+ // Build URL with query parameters
986
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(channelUrl).newBuilder();
987
+ urlBuilder.addQueryParameter("app_id", appId);
988
+ urlBuilder.addQueryParameter("platform", platform);
989
+ urlBuilder.addQueryParameter("is_emulator", String.valueOf(isEmulator));
990
+ urlBuilder.addQueryParameter("is_prod", String.valueOf(isProd));
991
+
992
+ Request request = new Request.Builder().url(urlBuilder.build()).get().build();
993
+
994
+ client
995
+ .newCall(request)
996
+ .enqueue(
997
+ new okhttp3.Callback() {
998
+ @Override
999
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
1000
+ Map<String, Object> retError = new HashMap<>();
1001
+ retError.put("message", "Request failed: " + e.getMessage());
1002
+ retError.put("error", "network_error");
1003
+ callback.callback(retError);
1004
+ }
1005
+
1006
+ @Override
1007
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
1008
+ try (ResponseBody responseBody = response.body()) {
1009
+ if (!response.isSuccessful()) {
1010
+ Map<String, Object> retError = new HashMap<>();
1011
+ retError.put("message", "Server error: " + response.code());
1012
+ retError.put("error", "response_error");
1013
+ callback.callback(retError);
1014
+ return;
1015
+ }
1016
+
1017
+ assert responseBody != null;
1018
+ String data = responseBody.string();
1019
+
1020
+ try {
1021
+ Map<String, Object> ret = new HashMap<>();
1022
+
1023
+ try {
1024
+ // Try to parse as direct array first
1025
+ JSONArray channelsJson = new JSONArray(data);
1026
+ List<Map<String, Object>> channelsList = new ArrayList<>();
1027
+
1028
+ for (int i = 0; i < channelsJson.length(); i++) {
1029
+ JSONObject channelJson = channelsJson.getJSONObject(i);
1030
+ Map<String, Object> channel = new HashMap<>();
1031
+ channel.put("id", channelJson.optString("id", ""));
1032
+ channel.put("name", channelJson.optString("name", ""));
1033
+ channel.put("public", channelJson.optBoolean("public", false));
1034
+ channel.put("allow_self_set", channelJson.optBoolean("allow_self_set", false));
1035
+ channelsList.add(channel);
1036
+ }
1037
+
1038
+ // Wrap in channels object for JS API
1039
+ ret.put("channels", channelsList);
1040
+
1041
+ logger.info("Channels listed successfully");
1042
+ callback.callback(ret);
1043
+ } catch (JSONException arrayException) {
1044
+ // If not an array, try to parse as error object
1045
+ try {
1046
+ JSONObject json = new JSONObject(data);
1047
+ if (json.has("error")) {
1048
+ Map<String, Object> retError = new HashMap<>();
1049
+ retError.put("message", json.getString("error"));
1050
+ retError.put("error", "server_error");
1051
+ callback.callback(retError);
1052
+ return;
1053
+ }
1054
+ } catch (JSONException objException) {
1055
+ // If neither array nor object, throw parse error
1056
+ throw arrayException;
1057
+ }
1058
+ }
1059
+ } catch (JSONException e) {
1060
+ Map<String, Object> retError = new HashMap<>();
1061
+ retError.put("message", "JSON parse error: " + e.getMessage());
1062
+ retError.put("error", "parse_error");
1063
+ callback.callback(retError);
1064
+ }
1065
+ }
1066
+ }
1067
+ }
1068
+ );
1069
+ }
1070
+
937
1071
  public void sendStats(final String action) {
938
1072
  this.sendStats(action, this.getCurrentBundle().getVersionName());
939
1073
  }
@@ -0,0 +1,72 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package ee.forgr.capacitor_updater;
8
+
9
+ import android.hardware.Sensor;
10
+ import android.hardware.SensorEvent;
11
+ import android.hardware.SensorEventListener;
12
+ import android.hardware.SensorManager;
13
+
14
+ public class ShakeDetector implements SensorEventListener {
15
+
16
+ public interface Listener {
17
+ void onShakeDetected();
18
+ }
19
+
20
+ private static final float SHAKE_THRESHOLD = 12.0f; // Acceleration threshold for shake detection
21
+ private static final int SHAKE_TIMEOUT = 500; // Minimum time between shake events (ms)
22
+
23
+ private Listener listener;
24
+ private SensorManager sensorManager;
25
+ private Sensor accelerometer;
26
+ private long lastShakeTime = 0;
27
+
28
+ public ShakeDetector(Listener listener) {
29
+ this.listener = listener;
30
+ }
31
+
32
+ public void start(SensorManager sensorManager) {
33
+ this.sensorManager = sensorManager;
34
+ this.accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
35
+
36
+ if (accelerometer != null) {
37
+ sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME);
38
+ }
39
+ }
40
+
41
+ public void stop() {
42
+ if (sensorManager != null) {
43
+ sensorManager.unregisterListener(this);
44
+ }
45
+ }
46
+
47
+ @Override
48
+ public void onSensorChanged(SensorEvent event) {
49
+ if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
50
+ float x = event.values[0];
51
+ float y = event.values[1];
52
+ float z = event.values[2];
53
+
54
+ // Calculate the acceleration magnitude (excluding gravity)
55
+ float acceleration = (float) Math.sqrt(x * x + y * y + z * z) - SensorManager.GRAVITY_EARTH;
56
+
57
+ // Check if acceleration exceeds threshold and enough time has passed
58
+ long currentTime = System.currentTimeMillis();
59
+ if (Math.abs(acceleration) > SHAKE_THRESHOLD && currentTime - lastShakeTime > SHAKE_TIMEOUT) {
60
+ lastShakeTime = currentTime;
61
+ if (listener != null) {
62
+ listener.onShakeDetected();
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ @Override
69
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
70
+ // Not needed for shake detection
71
+ }
72
+ }