cordova-plugin-mserviceinterface-2.0 1.0.102 → 1.0.103
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/package.json +1 -1
- package/plugin.xml +1 -1
- package/src/android/AttachmentFileIO.java +59 -0
- package/src/android/CameraActivity.java +111 -10
- package/src/android/GeofenceBroadcastReceiver.java +100 -20
- package/src/android/mInterface.java +360 -576
- package/src/android/mInterfaceReceiver.java +11 -53
- package/src/android/mInterfaceService.java +539 -315
- package/src/android/mInterfaceUtil.java +41 -71
package/package.json
CHANGED
package/plugin.xml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
|
3
|
-
xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-mserviceinterface-2.0" version="1.0.
|
|
3
|
+
xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-mserviceinterface-2.0" version="1.0.103">
|
|
4
4
|
<name>mserviceinterface-2.0</name>
|
|
5
5
|
<description>This plugin establishes the connection to the native interface and performs the requested task.</description>
|
|
6
6
|
<author>selfservit@gmail.com</author>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package com.selfservit.util;
|
|
2
|
+
|
|
3
|
+
import java.io.File;
|
|
4
|
+
import java.io.FileOutputStream;
|
|
5
|
+
import java.io.IOException;
|
|
6
|
+
import java.io.InputStream;
|
|
7
|
+
import java.io.OutputStream;
|
|
8
|
+
|
|
9
|
+
/** Copies a provider stream into an app-owned file without assuming a filesystem URI. */
|
|
10
|
+
final class AttachmentFileIO {
|
|
11
|
+
private AttachmentFileIO() { }
|
|
12
|
+
|
|
13
|
+
static File copy(InputStream input, File directory, String displayName) throws IOException {
|
|
14
|
+
File outputFile = null;
|
|
15
|
+
boolean complete = false;
|
|
16
|
+
try {
|
|
17
|
+
try (InputStream source = input) {
|
|
18
|
+
if (source == null) {
|
|
19
|
+
throw new IOException("The selected file is no longer available");
|
|
20
|
+
}
|
|
21
|
+
if (!directory.isDirectory() && !directory.mkdirs()) {
|
|
22
|
+
throw new IOException("Cannot create attachment directory");
|
|
23
|
+
}
|
|
24
|
+
String name = displayName == null ? "attachment" : displayName;
|
|
25
|
+
// Provider display names are labels, never paths.
|
|
26
|
+
name = name.replaceAll("[\\\\/\\p{Cntrl}:*?\"<>|]", "_").trim();
|
|
27
|
+
if (name.isEmpty() || name.equals(".") || name.equals("..")) {
|
|
28
|
+
name = "attachment";
|
|
29
|
+
}
|
|
30
|
+
int dot = name.lastIndexOf('.');
|
|
31
|
+
String stem = dot > 0 ? name.substring(0, dot) : name;
|
|
32
|
+
String suffix = dot > 0 ? name.substring(dot) : "";
|
|
33
|
+
// Bound provider-supplied names, retaining normal filename extensions.
|
|
34
|
+
stem = stem.substring(0, Math.min(stem.length(), 80));
|
|
35
|
+
suffix = suffix.substring(0, Math.min(suffix.length(), 20));
|
|
36
|
+
outputFile = File.createTempFile(stem + "_attachment_", suffix, directory);
|
|
37
|
+
try (OutputStream output = new FileOutputStream(outputFile)) {
|
|
38
|
+
byte[] buffer = new byte[8192];
|
|
39
|
+
int count;
|
|
40
|
+
while ((count = source.read(buffer)) != -1) {
|
|
41
|
+
if (count == 0) {
|
|
42
|
+
int next = source.read();
|
|
43
|
+
if (next == -1) break;
|
|
44
|
+
output.write(next);
|
|
45
|
+
} else {
|
|
46
|
+
output.write(buffer, 0, count);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
complete = true;
|
|
52
|
+
} finally {
|
|
53
|
+
if (!complete && outputFile != null && outputFile.exists()) {
|
|
54
|
+
outputFile.delete();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return outputFile;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -3,14 +3,20 @@ package com.selfservit.util;
|
|
|
3
3
|
import android.Manifest;
|
|
4
4
|
import android.annotation.SuppressLint;
|
|
5
5
|
import android.app.Activity;
|
|
6
|
+
import android.content.Context;
|
|
6
7
|
import android.content.Intent;
|
|
7
8
|
import android.content.pm.PackageManager;
|
|
9
|
+
import android.content.res.Configuration;
|
|
10
|
+
import android.hardware.display.DisplayManager;
|
|
8
11
|
import android.os.Bundle;
|
|
12
|
+
import android.os.Handler;
|
|
13
|
+
import android.os.Looper;
|
|
9
14
|
import android.util.Log;
|
|
15
|
+
import android.view.Display;
|
|
10
16
|
import android.view.MotionEvent;
|
|
11
17
|
import android.view.ScaleGestureDetector;
|
|
18
|
+
import android.view.Surface;
|
|
12
19
|
import android.view.View;
|
|
13
|
-
import android.view.ViewGroup;
|
|
14
20
|
import android.widget.ImageButton;
|
|
15
21
|
import android.widget.Toast;
|
|
16
22
|
|
|
@@ -27,6 +33,10 @@ import androidx.camera.lifecycle.ProcessCameraProvider;
|
|
|
27
33
|
import androidx.camera.view.PreviewView;
|
|
28
34
|
import androidx.core.app.ActivityCompat;
|
|
29
35
|
import androidx.core.content.ContextCompat;
|
|
36
|
+
import androidx.core.graphics.Insets;
|
|
37
|
+
import androidx.core.view.ViewCompat;
|
|
38
|
+
import androidx.core.view.WindowCompat;
|
|
39
|
+
import androidx.core.view.WindowInsetsCompat;
|
|
30
40
|
|
|
31
41
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
32
42
|
|
|
@@ -46,6 +56,26 @@ public class CameraActivity extends AppCompatActivity {
|
|
|
46
56
|
private ImageButton torchButton;
|
|
47
57
|
private ProcessCameraProvider cameraProvider;
|
|
48
58
|
private Camera camera;
|
|
59
|
+
private View cameraControls;
|
|
60
|
+
private DisplayManager displayManager;
|
|
61
|
+
private int torchOnDrawable;
|
|
62
|
+
private int torchOffDrawable;
|
|
63
|
+
|
|
64
|
+
private final DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener() {
|
|
65
|
+
@Override
|
|
66
|
+
public void onDisplayAdded(int displayId) {}
|
|
67
|
+
|
|
68
|
+
@Override
|
|
69
|
+
public void onDisplayRemoved(int displayId) {}
|
|
70
|
+
|
|
71
|
+
@Override
|
|
72
|
+
public void onDisplayChanged(int displayId) {
|
|
73
|
+
Display display = previewView == null ? null : previewView.getDisplay();
|
|
74
|
+
if (display != null && display.getDisplayId() == displayId) {
|
|
75
|
+
updateCaptureRotation();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
49
79
|
|
|
50
80
|
private int lensFacing = CameraSelector.LENS_FACING_BACK;
|
|
51
81
|
private boolean isTorchOn = false;
|
|
@@ -57,6 +87,17 @@ public class CameraActivity extends AppCompatActivity {
|
|
|
57
87
|
protected void onCreate(Bundle savedInstanceState) {
|
|
58
88
|
super.onCreate(savedInstanceState);
|
|
59
89
|
|
|
90
|
+
// Let the system handle predictive Back while preserving the plugin's cancellation result.
|
|
91
|
+
Intent cancelled = new Intent();
|
|
92
|
+
cancelled.putExtra("result", "cancelled");
|
|
93
|
+
setResult(Activity.RESULT_CANCELED, cancelled);
|
|
94
|
+
if (savedInstanceState != null) {
|
|
95
|
+
lensFacing = savedInstanceState.getInt("lensFacing", CameraSelector.LENS_FACING_BACK);
|
|
96
|
+
isTorchOn = savedInstanceState.getBoolean("isTorchOn", false);
|
|
97
|
+
}
|
|
98
|
+
displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
|
|
99
|
+
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
|
100
|
+
|
|
60
101
|
// Dynamically inflate layout
|
|
61
102
|
String pkg = getPackageName();
|
|
62
103
|
int layoutId = getResources().getIdentifier("activity_camera", "layout", pkg);
|
|
@@ -68,10 +109,11 @@ public class CameraActivity extends AppCompatActivity {
|
|
|
68
109
|
int switchId = getResources().getIdentifier("switchCamera", "id", pkg);
|
|
69
110
|
int torchId = getResources().getIdentifier("torchButton", "id", pkg);
|
|
70
111
|
int focusViewId = getResources().getIdentifier("focusView", "id", pkg);
|
|
112
|
+
int controlsId = getResources().getIdentifier("cameraControls", "id", pkg);
|
|
71
113
|
|
|
72
114
|
// Resolve drawable IDs
|
|
73
|
-
|
|
74
|
-
|
|
115
|
+
torchOnDrawable = getResources().getIdentifier("ic_flashlight_on", "drawable", pkg);
|
|
116
|
+
torchOffDrawable = getResources().getIdentifier("ic_flashlight_off", "drawable", pkg);
|
|
75
117
|
|
|
76
118
|
// Bind views to fields
|
|
77
119
|
previewView = findViewById(previewId);
|
|
@@ -79,6 +121,9 @@ public class CameraActivity extends AppCompatActivity {
|
|
|
79
121
|
ImageButton switchButton = findViewById(switchId);
|
|
80
122
|
torchButton = findViewById(torchId);
|
|
81
123
|
focusView = findViewById(focusViewId);
|
|
124
|
+
cameraControls = findViewById(controlsId);
|
|
125
|
+
applyControlInsets();
|
|
126
|
+
previewView.post(this::updateCaptureRotation);
|
|
82
127
|
|
|
83
128
|
// Utility to add press animation to any view
|
|
84
129
|
View.OnTouchListener pressAnim = (v, e) -> {
|
|
@@ -185,6 +230,7 @@ public class CameraActivity extends AppCompatActivity {
|
|
|
185
230
|
lensFacing = (lensFacing == CameraSelector.LENS_FACING_BACK)
|
|
186
231
|
? CameraSelector.LENS_FACING_FRONT
|
|
187
232
|
: CameraSelector.LENS_FACING_BACK;
|
|
233
|
+
isTorchOn = false;
|
|
188
234
|
bindUseCases();
|
|
189
235
|
});
|
|
190
236
|
|
|
@@ -198,11 +244,69 @@ public class CameraActivity extends AppCompatActivity {
|
|
|
198
244
|
});
|
|
199
245
|
}
|
|
200
246
|
|
|
247
|
+
private void applyControlInsets() {
|
|
248
|
+
final int left = cameraControls.getPaddingLeft();
|
|
249
|
+
final int top = cameraControls.getPaddingTop();
|
|
250
|
+
final int right = cameraControls.getPaddingRight();
|
|
251
|
+
final int bottom = cameraControls.getPaddingBottom();
|
|
252
|
+
ViewCompat.setOnApplyWindowInsetsListener(cameraControls, (view, windowInsets) -> {
|
|
253
|
+
Insets safeInsets = windowInsets.getInsets(
|
|
254
|
+
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
|
|
255
|
+
view.setPadding(left + safeInsets.left, top + safeInsets.top,
|
|
256
|
+
right + safeInsets.right, bottom + safeInsets.bottom);
|
|
257
|
+
return windowInsets;
|
|
258
|
+
});
|
|
259
|
+
cameraControls.post(() -> ViewCompat.requestApplyInsets(cameraControls));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@Override
|
|
263
|
+
protected void onStart() {
|
|
264
|
+
super.onStart();
|
|
265
|
+
if (displayManager != null) {
|
|
266
|
+
displayManager.registerDisplayListener(displayListener, new Handler(Looper.getMainLooper()));
|
|
267
|
+
}
|
|
268
|
+
previewView.post(this::updateCaptureRotation);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@Override
|
|
272
|
+
protected void onStop() {
|
|
273
|
+
if (displayManager != null) {
|
|
274
|
+
displayManager.unregisterDisplayListener(displayListener);
|
|
275
|
+
}
|
|
276
|
+
super.onStop();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@Override
|
|
280
|
+
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
|
281
|
+
super.onConfigurationChanged(newConfig);
|
|
282
|
+
previewView.post(this::updateCaptureRotation);
|
|
283
|
+
ViewCompat.requestApplyInsets(cameraControls);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@Override
|
|
287
|
+
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
|
288
|
+
outState.putInt("lensFacing", lensFacing);
|
|
289
|
+
outState.putBoolean("isTorchOn", isTorchOn);
|
|
290
|
+
super.onSaveInstanceState(outState);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private int getCaptureRotation() {
|
|
294
|
+
Display display = previewView == null ? null : previewView.getDisplay();
|
|
295
|
+
return display == null ? Surface.ROTATION_0 : display.getRotation();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private void updateCaptureRotation() {
|
|
299
|
+
if (imageCapture != null && previewView.getDisplay() != null) {
|
|
300
|
+
imageCapture.setTargetRotation(getCaptureRotation());
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
201
304
|
private void startCamera() {
|
|
202
305
|
ListenableFuture<ProcessCameraProvider> future =
|
|
203
306
|
ProcessCameraProvider.getInstance(this);
|
|
204
307
|
future.addListener(() -> {
|
|
205
308
|
try {
|
|
309
|
+
if (isFinishing() || isDestroyed()) return;
|
|
206
310
|
cameraProvider = future.get();
|
|
207
311
|
bindUseCases();
|
|
208
312
|
} catch (ExecutionException | InterruptedException e) {
|
|
@@ -224,6 +328,7 @@ public class CameraActivity extends AppCompatActivity {
|
|
|
224
328
|
// Image capture use-case
|
|
225
329
|
imageCapture = new ImageCapture.Builder()
|
|
226
330
|
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
|
331
|
+
.setTargetRotation(getCaptureRotation())
|
|
227
332
|
.build();
|
|
228
333
|
|
|
229
334
|
// Select camera
|
|
@@ -238,14 +343,17 @@ public class CameraActivity extends AppCompatActivity {
|
|
|
238
343
|
boolean hasFlash = camera.getCameraInfo().hasFlashUnit();
|
|
239
344
|
if (hasFlash) {
|
|
240
345
|
torchButton.setVisibility(View.VISIBLE);
|
|
346
|
+
camera.getCameraControl().enableTorch(isTorchOn);
|
|
241
347
|
} else {
|
|
242
348
|
torchButton.setVisibility(View.GONE);
|
|
243
349
|
// reset state in case you were mid-toggle
|
|
244
350
|
isTorchOn = false;
|
|
245
351
|
}
|
|
352
|
+
torchButton.setImageResource(isTorchOn ? torchOnDrawable : torchOffDrawable);
|
|
246
353
|
}
|
|
247
354
|
private void takePhoto() {
|
|
248
355
|
if (imageCapture == null) return;
|
|
356
|
+
updateCaptureRotation();
|
|
249
357
|
|
|
250
358
|
// Prepare output file
|
|
251
359
|
File photoFile = new File(getCacheDir(),
|
|
@@ -316,13 +424,6 @@ public class CameraActivity extends AppCompatActivity {
|
|
|
316
424
|
return true;
|
|
317
425
|
}
|
|
318
426
|
@Override
|
|
319
|
-
public void onBackPressed() {
|
|
320
|
-
Intent intent = new Intent();
|
|
321
|
-
intent.putExtra("result", "cancelled");
|
|
322
|
-
setResult(Activity.RESULT_CANCELED, intent);
|
|
323
|
-
finish();
|
|
324
|
-
}
|
|
325
|
-
@Override
|
|
326
427
|
public void onRequestPermissionsResult(int rc, @NonNull String[] perms,
|
|
327
428
|
@NonNull int[] results) {
|
|
328
429
|
super.onRequestPermissionsResult(rc, perms, results);
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
package com.selfservit.util;
|
|
2
2
|
|
|
3
|
+
import android.Manifest;
|
|
3
4
|
import android.app.NotificationChannel;
|
|
4
5
|
import android.app.NotificationManager;
|
|
5
6
|
import android.app.PendingIntent;
|
|
6
7
|
import android.content.BroadcastReceiver;
|
|
7
8
|
import android.content.Context;
|
|
8
9
|
import android.content.Intent;
|
|
10
|
+
import android.content.pm.PackageManager;
|
|
9
11
|
import android.os.Build;
|
|
10
12
|
import android.util.Log;
|
|
11
13
|
import android.widget.Toast;
|
|
12
14
|
import androidx.core.app.NotificationCompat;
|
|
13
15
|
import androidx.core.app.NotificationManagerCompat;
|
|
16
|
+
import androidx.core.content.ContextCompat;
|
|
14
17
|
|
|
15
18
|
import com.google.android.gms.location.Geofence;
|
|
16
19
|
import com.google.android.gms.location.GeofencingEvent;
|
|
@@ -22,6 +25,7 @@ import java.io.FileOutputStream;
|
|
|
22
25
|
import java.io.FileWriter;
|
|
23
26
|
import java.io.IOException;
|
|
24
27
|
import java.io.OutputStreamWriter;
|
|
28
|
+
import java.util.List;
|
|
25
29
|
import java.util.UUID;
|
|
26
30
|
|
|
27
31
|
public class GeofenceBroadcastReceiver extends BroadcastReceiver {
|
|
@@ -31,29 +35,76 @@ public class GeofenceBroadcastReceiver extends BroadcastReceiver {
|
|
|
31
35
|
|
|
32
36
|
@Override
|
|
33
37
|
public void onReceive(Context context, Intent intent) {
|
|
34
|
-
|
|
38
|
+
if (intent == null) {
|
|
39
|
+
Log.w(TAG, "Ignoring null geofence intent");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
GeofencingEvent geofencingEvent;
|
|
43
|
+
String enterMessage;
|
|
44
|
+
String exitMessage;
|
|
45
|
+
try {
|
|
46
|
+
geofencingEvent = GeofencingEvent.fromIntent(intent);
|
|
47
|
+
enterMessage = intent.getStringExtra("enterMessage");
|
|
48
|
+
exitMessage = intent.getStringExtra("exitMessage");
|
|
49
|
+
} catch (RuntimeException e) {
|
|
50
|
+
Log.e(TAG, "Ignoring malformed geofence intent", e);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
35
53
|
|
|
54
|
+
if (geofencingEvent == null) {
|
|
55
|
+
Log.w(TAG, "Ignoring intent without a geofencing event");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
36
58
|
if (geofencingEvent.hasError()) {
|
|
37
59
|
Log.e(TAG, "GeofencingEvent error: " + geofencingEvent.getErrorCode());
|
|
38
60
|
return;
|
|
39
61
|
}
|
|
40
|
-
String enterMessage = intent.getStringExtra("enterMessage");
|
|
41
|
-
String exitMessage = intent.getStringExtra("exitMessage");
|
|
42
62
|
int geofenceTransition = geofencingEvent.getGeofenceTransition();
|
|
43
63
|
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
|
|
44
64
|
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
|
|
45
65
|
String transitionType = geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ? "enter" : "exit";
|
|
66
|
+
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
|
|
67
|
+
if (triggeringGeofences == null || triggeringGeofences.isEmpty()) {
|
|
68
|
+
Log.w(TAG, "Ignoring event without triggering geofences");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
46
71
|
|
|
47
|
-
for (Geofence geofence :
|
|
72
|
+
for (Geofence geofence : triggeringGeofences) {
|
|
73
|
+
if (geofence == null) {
|
|
74
|
+
Log.w(TAG, "Ignoring null triggering geofence");
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
48
77
|
String geofenceId = geofence.getRequestId();
|
|
78
|
+
if (!isValidGeofenceId(geofenceId)) {
|
|
79
|
+
Log.w(TAG, "Ignoring geofence with an invalid request ID");
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
49
82
|
writeEventToJsonFile(context, geofenceId, transitionType);
|
|
50
|
-
createNotificationChannel(context);
|
|
51
83
|
String message = (transitionType.equals("enter") ? enterMessage : exitMessage);
|
|
52
84
|
sendNotification(context, "GeoFence", message);
|
|
53
85
|
// showToast(context, "Geofence " + transitionType + ": " + geofenceId);
|
|
54
86
|
}
|
|
55
87
|
}
|
|
56
88
|
}
|
|
89
|
+
|
|
90
|
+
private static boolean isValidGeofenceId(String geofenceId) {
|
|
91
|
+
if (geofenceId == null) return false;
|
|
92
|
+
String[] parameters = geofenceId.split("--", -1);
|
|
93
|
+
if (parameters.length < 4) return false;
|
|
94
|
+
for (String parameter : parameters) {
|
|
95
|
+
if (parameter.isEmpty() || parameter.equals(".") || parameter.equals("..")) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
for (int i = 0; i < parameter.length(); i++) {
|
|
99
|
+
char character = parameter.charAt(i);
|
|
100
|
+
if (character == '/' || character == '\\' || Character.isISOControl(character)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
57
108
|
private void showToast(Context context, String message) {
|
|
58
109
|
//Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_LONG).show();
|
|
59
110
|
}
|
|
@@ -63,18 +114,23 @@ public class GeofenceBroadcastReceiver extends BroadcastReceiver {
|
|
|
63
114
|
JSONObject jsonObject = new JSONObject();
|
|
64
115
|
jsonObject.put("eventName", eventType);
|
|
65
116
|
jsonObject.put("geofenceID", geofenceId);
|
|
66
|
-
String[] parameters = geofenceId.split("--");
|
|
67
|
-
fileDirectory =
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
117
|
+
String[] parameters = geofenceId.split("--", -1);
|
|
118
|
+
fileDirectory = context.getExternalFilesDir("");
|
|
119
|
+
if (fileDirectory == null) {
|
|
120
|
+
Log.w(TAG, "External app storage is unavailable for geofence event");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
for (int i = 0; i < 4; i++) {
|
|
124
|
+
fileDirectory = new File(fileDirectory, parameters[i]);
|
|
125
|
+
}
|
|
126
|
+
fileDirectory = new File(fileDirectory, "geofence");
|
|
127
|
+
if (!fileDirectory.isDirectory() && !fileDirectory.mkdirs()) {
|
|
128
|
+
throw new IOException("Unable to create geofence event directory");
|
|
129
|
+
}
|
|
74
130
|
notificationsFile = new File(fileDirectory, geofenceId + "--" + eventType + ".json");
|
|
75
|
-
FileWriter fw = new FileWriter(notificationsFile)
|
|
76
|
-
|
|
77
|
-
|
|
131
|
+
try (FileWriter fw = new FileWriter(notificationsFile)) {
|
|
132
|
+
fw.write(mInterfaceUtil.encrypt(jsonObject.toString()));
|
|
133
|
+
}
|
|
78
134
|
Log.i(TAG, "Event written to file: " + geofenceId + ".json");
|
|
79
135
|
} catch (Exception e) {
|
|
80
136
|
Log.e(TAG, "Error writing event to JSON file", e);
|
|
@@ -83,6 +139,26 @@ public class GeofenceBroadcastReceiver extends BroadcastReceiver {
|
|
|
83
139
|
|
|
84
140
|
private void sendNotification(Context context, String title, String message) {
|
|
85
141
|
try {
|
|
142
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
|
143
|
+
&& ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
|
|
144
|
+
!= PackageManager.PERMISSION_GRANTED) {
|
|
145
|
+
Log.i(TAG, "Skipping geofence notification; notification permission is not granted");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!NotificationManagerCompat.from(context).areNotificationsEnabled()) {
|
|
149
|
+
Log.i(TAG, "Skipping geofence notification; notifications are disabled");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
153
|
+
if (notificationManager == null) return;
|
|
154
|
+
createNotificationChannel(context);
|
|
155
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
156
|
+
NotificationChannel channel = notificationManager.getNotificationChannel(CHANNEL_ID);
|
|
157
|
+
if (channel != null && channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
|
|
158
|
+
Log.i(TAG, "Skipping geofence notification; notification channel is disabled");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
86
162
|
String notification_icon, notification_color, app_name;
|
|
87
163
|
int imageResource,notification_color_id;
|
|
88
164
|
String packageName = context.getPackageName();
|
|
@@ -104,15 +180,17 @@ public class GeofenceBroadcastReceiver extends BroadcastReceiver {
|
|
|
104
180
|
.setAutoCancel(true)
|
|
105
181
|
.setColor(context.getResources().getColor(notification_color_id))
|
|
106
182
|
.setContentIntent(pendingIntent);
|
|
107
|
-
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
108
183
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
109
184
|
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Channel human readable title", NotificationManager.IMPORTANCE_DEFAULT);
|
|
110
185
|
notificationManager.createNotificationChannel(channel);
|
|
111
|
-
}
|
|
186
|
+
}
|
|
112
187
|
int notificationId = UUID.randomUUID().hashCode();
|
|
113
188
|
notificationManager.notify(notificationId, notificationBuilder.build());
|
|
114
189
|
} catch (ClassNotFoundException e) {
|
|
115
|
-
|
|
190
|
+
Log.e(TAG, "Unable to open the app from geofence notification", e);
|
|
191
|
+
} catch (RuntimeException e) {
|
|
192
|
+
// Permission or notification settings can change after the checks above.
|
|
193
|
+
Log.e(TAG, "Unable to show geofence notification", e);
|
|
116
194
|
}
|
|
117
195
|
}
|
|
118
196
|
|
|
@@ -124,7 +202,9 @@ public class GeofenceBroadcastReceiver extends BroadcastReceiver {
|
|
|
124
202
|
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
|
|
125
203
|
channel.setDescription(description);
|
|
126
204
|
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
|
127
|
-
notificationManager
|
|
205
|
+
if (notificationManager != null) {
|
|
206
|
+
notificationManager.createNotificationChannel(channel);
|
|
207
|
+
}
|
|
128
208
|
}
|
|
129
209
|
}
|
|
130
210
|
}
|