cordova-plugin-mserviceinterface-2.0 1.0.103 → 1.0.104
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 +6 -8
- package/scripts/mservice-deeplink-setup.js +67 -16
- package/src/android/GeofenceBroadcastReceiver.java +0 -4
- package/src/android/doubleogradle.gradle +0 -4
- package/src/android/mInterface.java +145 -26
- package/src/android/mInterfaceUtil.java +143 -112
- package/src/android/res/layout/activity_camera.xml +12 -0
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.104">
|
|
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>
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
<config-file target="AndroidManifest.xml" parent="/manifest">
|
|
47
47
|
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
|
48
48
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
49
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
|
49
50
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
50
51
|
<uses-permission android:name="android.permission.CAMERA" />
|
|
51
52
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
@@ -63,23 +64,20 @@
|
|
|
63
64
|
</queries>
|
|
64
65
|
</config-file>
|
|
65
66
|
<config-file target="AndroidManifest.xml" parent="/manifest/application">
|
|
66
|
-
<activity android:name="com.selfservit.util.CameraActivity" android:configChanges="orientation|screenSize" android:screenOrientation="portrait" android:exported="
|
|
67
|
-
<service android:name="com.selfservit.util.mInterfaceService" android:foregroundServiceType="location" android:enabled="true" android:exported="
|
|
67
|
+
<activity android:name="com.selfservit.util.CameraActivity" android:configChanges="orientation|screenSize" android:screenOrientation="portrait" android:exported="false" android:theme="@style/CameraTheme" />
|
|
68
|
+
<service android:name="com.selfservit.util.mInterfaceService" android:foregroundServiceType="location" android:enabled="true" android:exported="false"/>
|
|
68
69
|
<receiver android:name="com.selfservit.util.mInterfaceReceiver" android:enabled="true" android:exported="true">
|
|
69
70
|
<intent-filter>
|
|
70
71
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
|
71
|
-
<action android:name="com.example.checkservice" />
|
|
72
72
|
</intent-filter>
|
|
73
73
|
</receiver>
|
|
74
|
-
<receiver android:enabled="true" android:exported="
|
|
75
|
-
<intent-filter>
|
|
76
|
-
<action android:name="com.google.android.gms.location.GeofenceEvent" />
|
|
77
|
-
</intent-filter>
|
|
74
|
+
<receiver android:enabled="true" android:exported="false" android:name="com.selfservit.util.GeofenceBroadcastReceiver">
|
|
78
75
|
</receiver>
|
|
79
76
|
<provider android:name="com.selfservit.util.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true">
|
|
80
77
|
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/opener_paths" />
|
|
81
78
|
</provider>
|
|
82
79
|
</config-file>
|
|
80
|
+
<source-file src="src/android/AttachmentFileIO.java" target-dir="src/com/selfservit/util/" />
|
|
83
81
|
<source-file src="src/android/mInterfaceService.java" target-dir="src/com/selfservit/util/" />
|
|
84
82
|
<source-file src="src/android/mInterfaceReceiver.java" target-dir="src/com/selfservit/util/" />
|
|
85
83
|
<source-file src="src/android/mInterface.java" target-dir="src/com/selfservit/util/" />
|
|
@@ -166,6 +166,58 @@ function setActivityAttribute(openingTag, attributeName, attributeValue) {
|
|
|
166
166
|
);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function isMatchingAndroidAppLink(filter, configuration) {
|
|
170
|
+
function attributes(tag) {
|
|
171
|
+
var values = {};
|
|
172
|
+
var pattern = /([\w:.-]+)\s*=\s*(["'])([\s\S]*?)\2/g;
|
|
173
|
+
var match;
|
|
174
|
+
while ((match = pattern.exec(tag)) !== null) {
|
|
175
|
+
values[match[1]] = match[3].replace(
|
|
176
|
+
/&(#x[0-9a-f]+|#\d+|amp|quot|apos|lt|gt);/gi,
|
|
177
|
+
function (entity, value) {
|
|
178
|
+
if (value.charAt(0) === "#") {
|
|
179
|
+
return String.fromCodePoint(value.charAt(1).toLowerCase() === "x"
|
|
180
|
+
? parseInt(value.slice(2), 16) : parseInt(value.slice(1), 10));
|
|
181
|
+
}
|
|
182
|
+
return {amp: "&", quot: '"', apos: "'", lt: "<", gt: ">"}[value];
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return values;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
var content = filter.replace(/<!--[\s\S]*?-->/g, "");
|
|
190
|
+
var opening = content.match(/<intent-filter\b[^>]*>/i);
|
|
191
|
+
var filterAttributes = opening ? attributes(opening[0]) : {};
|
|
192
|
+
// Preserve filters with extra behavior, such as priority or additional data.
|
|
193
|
+
if (Object.keys(filterAttributes).some(function (key) { return key !== "android:autoVerify"; })) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
var body = content.replace(/^\s*<intent-filter\b[^>]*>/i, "").replace(/<\/intent-filter>\s*$/i, "");
|
|
197
|
+
var tags = body.match(/<(?:action|category|data)\b[^>]*\/\s*>/gi) || [];
|
|
198
|
+
if (tags.length !== 4 || body.replace(/<(?:action|category|data)\b[^>]*\/\s*>/gi, "").trim() !== "") {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
var actions = [], categories = [], data = [];
|
|
202
|
+
tags.forEach(function (tag) {
|
|
203
|
+
var values = attributes(tag);
|
|
204
|
+
if (/^<data\b/i.test(tag)) {
|
|
205
|
+
data.push(values);
|
|
206
|
+
} else if (Object.keys(values).length === 1 && values["android:name"]) {
|
|
207
|
+
if (/^<action\b/i.test(tag)) actions.push(values["android:name"]);
|
|
208
|
+
else categories.push(values["android:name"]);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
return actions.length === 1 && actions[0] === "android.intent.action.VIEW"
|
|
212
|
+
&& categories.length === 2
|
|
213
|
+
&& categories.indexOf("android.intent.category.DEFAULT") !== -1
|
|
214
|
+
&& categories.indexOf("android.intent.category.BROWSABLE") !== -1
|
|
215
|
+
&& data.length === 1 && Object.keys(data[0]).length === 3
|
|
216
|
+
&& data[0]["android:scheme"] === configuration.scheme
|
|
217
|
+
&& data[0]["android:host"] === configuration.host
|
|
218
|
+
&& data[0]["android:path"] === configuration.path;
|
|
219
|
+
}
|
|
220
|
+
|
|
169
221
|
function configureAndroidAppLink(projectRoot, configuration) {
|
|
170
222
|
var manifestPath;
|
|
171
223
|
var manifestContent;
|
|
@@ -243,22 +295,21 @@ function configureAndroidAppLink(projectRoot, configuration) {
|
|
|
243
295
|
" <!-- MService App Link - END -->\n";
|
|
244
296
|
|
|
245
297
|
markerPattern =
|
|
246
|
-
/\s*<!-- MService App Link - START -->[\s\S]*?<!-- MService App Link - END -->\s*/
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
298
|
+
/\s*<!-- MService App Link - START -->[\s\S]*?<!-- MService App Link - END -->\s*/gi;
|
|
299
|
+
|
|
300
|
+
// Cordova can discard comments when rewriting XML. Remove both marked
|
|
301
|
+
// blocks and equivalent unmarked filters before inserting one definition.
|
|
302
|
+
updatedActivityBlock = updatedActivityBlock.replace(markerPattern, "\n");
|
|
303
|
+
updatedActivityBlock = updatedActivityBlock.replace(
|
|
304
|
+
/\s*<intent-filter\b[^>]*>[\s\S]*?<\/intent-filter>/gi,
|
|
305
|
+
function (filter) {
|
|
306
|
+
return isMatchingAndroidAppLink(filter, configuration) ? "" : filter;
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
updatedActivityBlock = updatedActivityBlock.replace(
|
|
310
|
+
/\s*<\/activity>\s*$/i,
|
|
311
|
+
intentFilter + " </activity>"
|
|
312
|
+
);
|
|
262
313
|
|
|
263
314
|
manifestContent = manifestContent.replace(
|
|
264
315
|
activityPattern,
|
|
@@ -180,10 +180,6 @@ public class GeofenceBroadcastReceiver extends BroadcastReceiver {
|
|
|
180
180
|
.setAutoCancel(true)
|
|
181
181
|
.setColor(context.getResources().getColor(notification_color_id))
|
|
182
182
|
.setContentIntent(pendingIntent);
|
|
183
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
184
|
-
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Channel human readable title", NotificationManager.IMPORTANCE_DEFAULT);
|
|
185
|
-
notificationManager.createNotificationChannel(channel);
|
|
186
|
-
}
|
|
187
183
|
int notificationId = UUID.randomUUID().hashCode();
|
|
188
184
|
notificationManager.notify(notificationId, notificationBuilder.build());
|
|
189
185
|
} catch (ClassNotFoundException e) {
|
|
@@ -26,6 +26,7 @@ import android.graphics.Color;
|
|
|
26
26
|
import android.graphics.Matrix;
|
|
27
27
|
import android.graphics.Paint;
|
|
28
28
|
import android.location.Location;
|
|
29
|
+
import android.location.LocationManager;
|
|
29
30
|
import android.media.MediaMetadataRetriever;
|
|
30
31
|
import android.media.ThumbnailUtils;
|
|
31
32
|
import android.net.ConnectivityManager;
|
|
@@ -35,6 +36,9 @@ import android.os.AsyncTask;
|
|
|
35
36
|
import android.os.Build;
|
|
36
37
|
import android.os.Bundle;
|
|
37
38
|
import android.os.Environment;
|
|
39
|
+
import android.os.Handler;
|
|
40
|
+
import android.os.Looper;
|
|
41
|
+
import android.os.SystemClock;
|
|
38
42
|
import android.provider.MediaStore;
|
|
39
43
|
import android.provider.OpenableColumns;
|
|
40
44
|
import android.provider.Settings;
|
|
@@ -50,9 +54,13 @@ import androidx.annotation.NonNull;
|
|
|
50
54
|
import androidx.annotation.Nullable;
|
|
51
55
|
import androidx.annotation.RequiresApi;
|
|
52
56
|
import androidx.core.app.ActivityCompat;
|
|
57
|
+
import androidx.core.location.LocationManagerCompat;
|
|
53
58
|
import androidx.exifinterface.media.ExifInterface;
|
|
54
59
|
|
|
55
60
|
import com.google.android.gms.common.ConnectionResult;
|
|
61
|
+
import com.google.android.gms.common.GoogleApiAvailability;
|
|
62
|
+
import com.google.android.gms.location.CurrentLocationRequest;
|
|
63
|
+
import com.google.android.gms.tasks.CancellationTokenSource;
|
|
56
64
|
import com.google.android.gms.common.api.GoogleApiClient;
|
|
57
65
|
import com.google.android.gms.common.api.PendingResult;
|
|
58
66
|
import com.google.android.gms.common.api.ResultCallback;
|
|
@@ -140,7 +148,6 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
|
|
|
140
148
|
private static final String PORTRAIT = "portrait";
|
|
141
149
|
private static final String LANDSCAPE = "landscape";
|
|
142
150
|
private static final String ANDROID_PLATFORM = "Android";
|
|
143
|
-
String currentLocationInd = "";
|
|
144
151
|
Location mCurrentLocation1;
|
|
145
152
|
LocationRequest mLocationRequest1;
|
|
146
153
|
GoogleApiClient mGoogleApiClient1;
|
|
@@ -174,6 +181,8 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
|
|
|
174
181
|
|
|
175
182
|
@Override
|
|
176
183
|
public void onDestroy() {
|
|
184
|
+
locationPluginDestroyed = true;
|
|
185
|
+
cancelCurrentLocationRequest();
|
|
177
186
|
mInterfaceService.stopIfNotTracking(cordova.getActivity());
|
|
178
187
|
if (mGoogleApiClient1 != null) {
|
|
179
188
|
if (mGoogleApiClient1.isConnected()) {
|
|
@@ -184,6 +193,12 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
|
|
|
184
193
|
super.onDestroy();
|
|
185
194
|
}
|
|
186
195
|
|
|
196
|
+
@Override
|
|
197
|
+
public void onReset() {
|
|
198
|
+
cancelCurrentLocationRequest();
|
|
199
|
+
super.onReset();
|
|
200
|
+
}
|
|
201
|
+
|
|
187
202
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
188
203
|
@Override
|
|
189
204
|
public boolean execute(final String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
|
|
@@ -489,6 +504,10 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
|
|
|
489
504
|
String str = "false";
|
|
490
505
|
if (isServiceRunning(mInterfaceService.class)) {
|
|
491
506
|
str = "true";
|
|
507
|
+
// Legacy JS skips StartService when this returns true.
|
|
508
|
+
// Recheck an existing queue-only/tracking service here,
|
|
509
|
+
// preserving the callback contract and eligibility guards.
|
|
510
|
+
mInterfaceService.start(cordova.getActivity());
|
|
492
511
|
System.out.println("service check is: " + str + " " + isServiceRunning(mInterfaceService.class));
|
|
493
512
|
}
|
|
494
513
|
callbackContext.success(str);
|
|
@@ -622,19 +641,8 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
|
|
|
622
641
|
callbackContext.error(e.getMessage());
|
|
623
642
|
}
|
|
624
643
|
} else if ("GetCurrentLocation".equals(action)) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
callback = callbackContext;
|
|
628
|
-
createLocationRequest1();
|
|
629
|
-
this.mGoogleApiClient1 = new GoogleApiClient.Builder(cordova.getActivity())
|
|
630
|
-
.addConnectionCallbacks(this)
|
|
631
|
-
.addOnConnectionFailedListener(this)
|
|
632
|
-
.addApi(LocationServices.API)
|
|
633
|
-
.build();
|
|
634
|
-
mGoogleApiClient1.connect();
|
|
635
|
-
} catch (Exception e) {
|
|
636
|
-
callbackContext.error(e.getMessage());
|
|
637
|
-
}
|
|
644
|
+
getCurrentLocation(callbackContext);
|
|
645
|
+
return true;
|
|
638
646
|
} else if (action.equals("GetSyncIndicator")) {
|
|
639
647
|
try {
|
|
640
648
|
File newQueueFolder, oldQueueFolder;
|
|
@@ -1494,6 +1502,122 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
|
|
|
1494
1502
|
return false;
|
|
1495
1503
|
}
|
|
1496
1504
|
|
|
1505
|
+
private static final long CURRENT_LOCATION_TIMEOUT_MS = 10_000L;
|
|
1506
|
+
private static final String CURRENT_LOCATION_TIMEOUT_ERROR = "Unable to obtain current location within 10 seconds";
|
|
1507
|
+
private final Handler currentLocationHandler = new Handler(Looper.getMainLooper());
|
|
1508
|
+
private CurrentLocationOperation currentLocationOperation;
|
|
1509
|
+
private volatile boolean locationPluginDestroyed;
|
|
1510
|
+
|
|
1511
|
+
private void getCurrentLocation(final CallbackContext resultCallback) {
|
|
1512
|
+
final long deadline = SystemClock.elapsedRealtime() + CURRENT_LOCATION_TIMEOUT_MS;
|
|
1513
|
+
currentLocationHandler.post(() -> {
|
|
1514
|
+
if (locationPluginDestroyed) {
|
|
1515
|
+
resultCallback.error("Current location request cancelled: plugin destroyed");
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
if (currentLocationOperation != null) {
|
|
1519
|
+
resultCallback.error("A current location request is already in progress");
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
CurrentLocationOperation operation = new CurrentLocationOperation(resultCallback, deadline);
|
|
1523
|
+
currentLocationOperation = operation;
|
|
1524
|
+
operation.start();
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
private void cancelCurrentLocationRequest() {
|
|
1529
|
+
currentLocationHandler.post(() -> {
|
|
1530
|
+
if (currentLocationOperation != null) {
|
|
1531
|
+
currentLocationOperation.finish(null, "Current location request cancelled");
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// All request state and completion listeners run on the main looper. This
|
|
1537
|
+
// callback is separate from the shared camera/scanner/GPS-settings callback.
|
|
1538
|
+
private final class CurrentLocationOperation {
|
|
1539
|
+
private final CallbackContext resultCallback;
|
|
1540
|
+
private final long deadline;
|
|
1541
|
+
private final CancellationTokenSource cancellation = new CancellationTokenSource();
|
|
1542
|
+
private final Runnable timeout = () -> finish(null, CURRENT_LOCATION_TIMEOUT_ERROR);
|
|
1543
|
+
private boolean completed;
|
|
1544
|
+
|
|
1545
|
+
CurrentLocationOperation(CallbackContext resultCallback, long deadline) {
|
|
1546
|
+
this.resultCallback = resultCallback;
|
|
1547
|
+
this.deadline = deadline;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
void start() {
|
|
1551
|
+
long remaining = deadline - SystemClock.elapsedRealtime();
|
|
1552
|
+
if (remaining <= 0) {
|
|
1553
|
+
finish(null, CURRENT_LOCATION_TIMEOUT_ERROR);
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
currentLocationHandler.postDelayed(timeout, remaining);
|
|
1557
|
+
try {
|
|
1558
|
+
Context context = cordova.getActivity().getApplicationContext();
|
|
1559
|
+
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
|
|
1560
|
+
&& ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
|
1561
|
+
finish(null, "Location permission not granted");
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
LocationManager manager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
|
1565
|
+
if (manager == null || !LocationManagerCompat.isLocationEnabled(manager)) {
|
|
1566
|
+
finish(null, "Device location is disabled");
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) != ConnectionResult.SUCCESS) {
|
|
1570
|
+
finish(null, "Google Play services is unavailable");
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
remaining = deadline - SystemClock.elapsedRealtime();
|
|
1574
|
+
if (remaining <= 0) {
|
|
1575
|
+
finish(null, CURRENT_LOCATION_TIMEOUT_ERROR);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
CurrentLocationRequest request = new CurrentLocationRequest.Builder()
|
|
1579
|
+
.setPriority(Priority.PRIORITY_HIGH_ACCURACY)
|
|
1580
|
+
.setMaxUpdateAgeMillis(0)
|
|
1581
|
+
.setDurationMillis(remaining)
|
|
1582
|
+
.build();
|
|
1583
|
+
// No network precondition: a satellite fix can be available offline.
|
|
1584
|
+
LocationServices.getFusedLocationProviderClient(context)
|
|
1585
|
+
.getCurrentLocation(request, cancellation.getToken())
|
|
1586
|
+
.addOnSuccessListener(location -> finish(location,
|
|
1587
|
+
location == null ? "Current location is unavailable" : null))
|
|
1588
|
+
.addOnFailureListener(error -> finish(null,
|
|
1589
|
+
"Unable to obtain current location: " + error.getMessage()))
|
|
1590
|
+
.addOnCanceledListener(() -> finish(null, "Current location request cancelled"));
|
|
1591
|
+
} catch (Exception error) {
|
|
1592
|
+
finish(null, "Unable to obtain current location: " + error.getMessage());
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
void finish(Location location, String error) {
|
|
1597
|
+
if (completed) return;
|
|
1598
|
+
completed = true;
|
|
1599
|
+
currentLocationHandler.removeCallbacks(timeout);
|
|
1600
|
+
if (currentLocationOperation == this) currentLocationOperation = null;
|
|
1601
|
+
// Cancels outstanding acquisition on timeout/reset and releases the
|
|
1602
|
+
// one-shot request on every terminal path. Late results are ignored.
|
|
1603
|
+
try {
|
|
1604
|
+
cancellation.cancel();
|
|
1605
|
+
} catch (RuntimeException cancellationError) {
|
|
1606
|
+
Log.w(TAG, "Unable to cancel current location acquisition", cancellationError);
|
|
1607
|
+
}
|
|
1608
|
+
if (SystemClock.elapsedRealtime() >= deadline) {
|
|
1609
|
+
location = null;
|
|
1610
|
+
error = CURRENT_LOCATION_TIMEOUT_ERROR;
|
|
1611
|
+
}
|
|
1612
|
+
if (location != null) {
|
|
1613
|
+
resultCallback.success(location.getLatitude() + "," + location.getLongitude());
|
|
1614
|
+
} else {
|
|
1615
|
+
resultCallback.error(error);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
|
|
1497
1621
|
public void createLocationRequest1() {
|
|
1498
1622
|
try {
|
|
1499
1623
|
mLocationRequest1 = new LocationRequest();
|
|
@@ -2585,36 +2709,31 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
|
|
|
2585
2709
|
final Status status = locationSettingsResult.getStatus();
|
|
2586
2710
|
switch (status.getStatusCode()) {
|
|
2587
2711
|
case LocationSettingsStatusCodes.SUCCESS:
|
|
2712
|
+
callback.success();
|
|
2588
2713
|
break;
|
|
2589
2714
|
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
|
|
2590
2715
|
try {
|
|
2591
2716
|
this.cordova.setActivityResultCallback(this);
|
|
2592
2717
|
status.startResolutionForResult(this.cordova.getActivity(), LOCATION_ACCURACY_REQUEST_NEW);
|
|
2593
2718
|
} catch (IntentSender.SendIntentException e) {
|
|
2594
|
-
|
|
2719
|
+
callback.error("Unable to open location settings: " + e.getMessage());
|
|
2595
2720
|
}
|
|
2596
2721
|
break;
|
|
2597
2722
|
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
|
|
2598
|
-
|
|
2723
|
+
callback.error("Location settings cannot be changed on this device");
|
|
2724
|
+
break;
|
|
2725
|
+
default:
|
|
2726
|
+
callback.error("Unable to check location settings: " + status.getStatusCode());
|
|
2599
2727
|
break;
|
|
2600
2728
|
}
|
|
2601
2729
|
} catch (Exception e) {
|
|
2602
|
-
|
|
2730
|
+
callback.error("LocationSettingsResult failed, exception is : " + e.getMessage());
|
|
2603
2731
|
}
|
|
2604
2732
|
}
|
|
2605
2733
|
|
|
2606
2734
|
@Override
|
|
2607
2735
|
public void onLocationChanged(@NonNull Location location) {
|
|
2608
2736
|
mCurrentLocation1 = location;
|
|
2609
|
-
try {
|
|
2610
|
-
System.out.println(mCurrentLocation1.getLatitude() + "," + mCurrentLocation1.getLongitude());
|
|
2611
|
-
if (currentLocationInd.equals("true")) {
|
|
2612
|
-
currentLocationInd = "";
|
|
2613
|
-
callback.success(mCurrentLocation1.getLatitude() + "," + mCurrentLocation1.getLongitude());
|
|
2614
|
-
}
|
|
2615
|
-
} catch (Exception e) {
|
|
2616
|
-
callback.error("onLocationChanged failed, exception is : " + e.getMessage());
|
|
2617
|
-
}
|
|
2618
2737
|
}
|
|
2619
2738
|
|
|
2620
2739
|
private class DownloadTask extends AsyncTask<String, Integer, String> {
|
|
@@ -838,133 +838,164 @@ public class mInterfaceUtil {
|
|
|
838
838
|
return imgString;
|
|
839
839
|
}
|
|
840
840
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
@Override
|
|
845
|
-
protected void onPreExecute() {
|
|
846
|
-
}
|
|
841
|
+
private static final int CHAT_UPLOAD_CONNECT_TIMEOUT_MS = 30_000;
|
|
842
|
+
private static final int CHAT_UPLOAD_READ_TIMEOUT_MS = 60_000;
|
|
843
|
+
private static final long CHAT_UPLOAD_TIMEOUT_MS = 5 * 60_000L;
|
|
847
844
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
HttpURLConnection urlConObj;
|
|
852
|
-
URL requestPath;
|
|
853
|
-
int bytesAvailable,
|
|
854
|
-
bufferSize;
|
|
855
|
-
int maxBufferSize = 1 * 1024 * 1024;
|
|
856
|
-
DataOutputStream dos;
|
|
857
|
-
String lineEnd = "\r\n";
|
|
858
|
-
String twoHyphens = "--";
|
|
859
|
-
String boundary = "*****";
|
|
860
|
-
StringBuilder serverResponseObj;
|
|
861
|
-
BufferedReader readerObj;
|
|
862
|
-
String currentLine,
|
|
863
|
-
receiveData = "", statusIndication = null;
|
|
864
|
-
int progressCheck = 10;
|
|
865
|
-
try {
|
|
866
|
-
if (new File(filePath).exists()) {
|
|
867
|
-
if (new File(filePath).exists()) {
|
|
868
|
-
fileInputStream = new FileInputStream(new File(filePath));
|
|
869
|
-
|
|
870
|
-
requestPath = new URL((requestURL).replaceAll(" ", "%20"));
|
|
871
|
-
urlConObj = (HttpURLConnection) requestPath.openConnection();
|
|
872
|
-
urlConObj.setDoInput(true); // Allow Inputs
|
|
873
|
-
urlConObj.setDoOutput(true); // Allow Outputs
|
|
874
|
-
urlConObj.setUseCaches(false); // Don't use a Cached Copy
|
|
875
|
-
urlConObj.setRequestMethod("POST");
|
|
876
|
-
urlConObj.setRequestProperty("Connection", "Keep-Alive");
|
|
877
|
-
urlConObj.setRequestProperty("ENCTYPE", "multipart/form-data");
|
|
878
|
-
urlConObj.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
|
|
879
|
-
urlConObj.setRequestProperty("uploaded_file", fileName);
|
|
880
|
-
|
|
881
|
-
dos = new DataOutputStream(urlConObj.getOutputStream());
|
|
882
|
-
dos.writeBytes(twoHyphens + boundary + lineEnd);
|
|
883
|
-
dos.writeBytes("Content-Disposition: form-data; name=\"uploaded_file\";filename=\""
|
|
884
|
-
+ fileName + "\"" + lineEnd);
|
|
885
|
-
dos.writeBytes(lineEnd);
|
|
886
|
-
// create a buffer of maximum size
|
|
887
|
-
bytesAvailable = fileInputStream.available();
|
|
888
|
-
|
|
889
|
-
bufferSize = Math.min(bytesAvailable, maxBufferSize);
|
|
890
|
-
|
|
891
|
-
int progress = 0;
|
|
892
|
-
int progressToSend = 0;
|
|
893
|
-
int fileLength = (int) new File(filePath).length();
|
|
894
|
-
int bytesReadValue = 0;
|
|
895
|
-
byte[] buf = new byte[1024];
|
|
896
|
-
BufferedInputStream bufInput = new BufferedInputStream(new FileInputStream(new File(filePath)));
|
|
897
|
-
while ((bytesReadValue = bufInput.read(buf)) != -1) {
|
|
898
|
-
// write output
|
|
899
|
-
dos.write(buf, 0, bytesReadValue);
|
|
900
|
-
progress += bytesReadValue;
|
|
901
|
-
progressToSend = (int) ((progress * 100) / fileLength);
|
|
902
|
-
|
|
903
|
-
if (progressToSend >= progressCheck) {
|
|
904
|
-
JSONObject progressObj = new JSONObject();
|
|
905
|
-
progressObj.put("progress", progressCheck);
|
|
906
|
-
progressObj.put("fileLength", fileLength);
|
|
907
|
-
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, progressObj);
|
|
908
|
-
pluginResult.setKeepCallback(true);
|
|
909
|
-
callbackContext.sendPluginResult(pluginResult);
|
|
910
|
-
progressCheck = progressCheck + 10;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
845
|
+
public void sendChatAttachment(String filePath, String fileName, String requestURL, Context context, CallbackContext callbackContext) {
|
|
846
|
+
new ChatAttachmentUpload(filePath, fileName, requestURL, callbackContext).start();
|
|
847
|
+
}
|
|
913
848
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
statusIndication = "false";
|
|
932
|
-
}
|
|
933
|
-
urlConObj.disconnect();
|
|
934
|
-
}
|
|
935
|
-
}
|
|
849
|
+
// Own the callback for the whole request, including progress, timeout and cleanup.
|
|
850
|
+
private static final class ChatAttachmentUpload implements Runnable {
|
|
851
|
+
private final String filePath;
|
|
852
|
+
private final String fileName;
|
|
853
|
+
private final String requestURL;
|
|
854
|
+
private final CallbackContext callback;
|
|
855
|
+
private volatile boolean finished;
|
|
856
|
+
private volatile HttpURLConnection connection;
|
|
857
|
+
private java.util.Timer timeout;
|
|
858
|
+
private long startedAt;
|
|
859
|
+
|
|
860
|
+
ChatAttachmentUpload(String filePath, String fileName, String requestURL, CallbackContext callback) {
|
|
861
|
+
this.filePath = filePath;
|
|
862
|
+
this.fileName = fileName;
|
|
863
|
+
this.requestURL = requestURL;
|
|
864
|
+
this.callback = callback;
|
|
865
|
+
}
|
|
936
866
|
|
|
937
|
-
|
|
938
|
-
|
|
867
|
+
void start() {
|
|
868
|
+
try {
|
|
869
|
+
startedAt = android.os.SystemClock.elapsedRealtime();
|
|
870
|
+
timeout = new java.util.Timer("ChatAttachmentTimeout", true);
|
|
871
|
+
timeout.schedule(new java.util.TimerTask() {
|
|
872
|
+
@Override public void run() {
|
|
873
|
+
finish(false, "request timed out");
|
|
874
|
+
// Timer runs off the UI thread; disconnect can block on some devices.
|
|
875
|
+
disconnect();
|
|
939
876
|
}
|
|
940
|
-
|
|
877
|
+
}, CHAT_UPLOAD_TIMEOUT_MS);
|
|
878
|
+
AsyncTask.THREAD_POOL_EXECUTOR.execute(this);
|
|
879
|
+
} catch (Exception e) {
|
|
880
|
+
finish(false, "could not start upload");
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
private synchronized void finish(boolean success, String reason) {
|
|
885
|
+
if (finished) return;
|
|
886
|
+
if (android.os.SystemClock.elapsedRealtime() - startedAt >= CHAT_UPLOAD_TIMEOUT_MS) {
|
|
887
|
+
success = false;
|
|
888
|
+
reason = "request timed out";
|
|
889
|
+
}
|
|
890
|
+
finished = true;
|
|
891
|
+
if (timeout != null) timeout.cancel();
|
|
892
|
+
// Do not log the URL, local path, file contents or server response.
|
|
893
|
+
if (!success) Log.w("mInterfaceChatUpload", reason);
|
|
894
|
+
if (success) callback.success("success");
|
|
895
|
+
else callback.error("failure");
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private synchronized void progress(int percent, long length) throws JSONException {
|
|
899
|
+
if (finished) return;
|
|
900
|
+
JSONObject value = new JSONObject();
|
|
901
|
+
value.put("progress", percent);
|
|
902
|
+
value.put("fileLength", length);
|
|
903
|
+
PluginResult result = new PluginResult(PluginResult.Status.OK, value);
|
|
904
|
+
result.setKeepCallback(true);
|
|
905
|
+
callback.sendPluginResult(result);
|
|
906
|
+
}
|
|
941
907
|
|
|
908
|
+
private void disconnect() {
|
|
909
|
+
HttpURLConnection active = connection;
|
|
910
|
+
if (active != null) {
|
|
911
|
+
try {
|
|
912
|
+
active.disconnect();
|
|
913
|
+
} catch (Exception ignored) {
|
|
914
|
+
// Cleanup must never prevent delivery of the terminal callback.
|
|
942
915
|
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
943
918
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
919
|
+
@Override public void run() {
|
|
920
|
+
try {
|
|
921
|
+
if (finished) return;
|
|
922
|
+
File file = new File(filePath);
|
|
923
|
+
if (!file.isFile() || !file.canRead()) {
|
|
924
|
+
finish(false, "attachment is not readable");
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
String boundary = "*****";
|
|
928
|
+
byte[] header = ("--" + boundary + "\r\n"
|
|
929
|
+
+ "Content-Disposition: form-data; name=\"uploaded_file\";filename=\""
|
|
930
|
+
+ fileName.replace("\r", "").replace("\n", "").replace("\"", "_") + "\"\r\n\r\n")
|
|
931
|
+
.getBytes(StandardCharsets.UTF_8);
|
|
932
|
+
byte[] footer = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8);
|
|
933
|
+
long length = file.length();
|
|
934
|
+
HttpURLConnection active = (HttpURLConnection) new URL(requestURL.replace(" ", "%20")).openConnection();
|
|
935
|
+
connection = active;
|
|
936
|
+
if (finished) return;
|
|
937
|
+
active.setConnectTimeout(CHAT_UPLOAD_CONNECT_TIMEOUT_MS);
|
|
938
|
+
active.setReadTimeout(CHAT_UPLOAD_READ_TIMEOUT_MS);
|
|
939
|
+
active.setDoInput(true);
|
|
940
|
+
active.setDoOutput(true);
|
|
941
|
+
active.setUseCaches(false);
|
|
942
|
+
active.setRequestMethod("POST");
|
|
943
|
+
active.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
|
|
944
|
+
active.setRequestProperty("uploaded_file", fileName);
|
|
945
|
+
|
|
946
|
+
// Finish and close the request body BEFORE asking for the HTTP response.
|
|
947
|
+
try (InputStream input = new BufferedInputStream(new FileInputStream(file));
|
|
948
|
+
OutputStream output = active.getOutputStream()) {
|
|
949
|
+
output.write(header);
|
|
950
|
+
byte[] buffer = new byte[16 * 1024];
|
|
951
|
+
long sent = 0;
|
|
952
|
+
int count;
|
|
953
|
+
int nextProgress = 10;
|
|
954
|
+
while ((count = input.read(buffer)) != -1) {
|
|
955
|
+
if (finished) return;
|
|
956
|
+
output.write(buffer, 0, count);
|
|
957
|
+
sent += count;
|
|
958
|
+
int percent = length == 0 ? 100 : (int) (sent * 100.0 / length);
|
|
959
|
+
while (nextProgress <= 100 && percent >= nextProgress) {
|
|
960
|
+
progress(nextProgress, length);
|
|
961
|
+
nextProgress += 10;
|
|
954
962
|
}
|
|
955
|
-
} catch (Exception e) {
|
|
956
|
-
e.printStackTrace();
|
|
957
963
|
}
|
|
964
|
+
if (finished) return;
|
|
965
|
+
output.write(footer);
|
|
966
|
+
output.flush();
|
|
967
|
+
}
|
|
958
968
|
|
|
969
|
+
if (finished) return;
|
|
970
|
+
int status = active.getResponseCode();
|
|
971
|
+
if (status < 200 || status >= 300) {
|
|
972
|
+
finish(false, "HTTP " + status);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
StringBuilder response = new StringBuilder();
|
|
976
|
+
try (InputStreamReader reader = new InputStreamReader(active.getInputStream(), StandardCharsets.UTF_8)) {
|
|
977
|
+
char[] buffer = new char[4096];
|
|
978
|
+
int count;
|
|
979
|
+
while ((count = reader.read(buffer)) != -1) {
|
|
980
|
+
if (finished) return;
|
|
981
|
+
if (response.length() + count > 1024 * 1024) {
|
|
982
|
+
throw new IOException("Upload response too large");
|
|
983
|
+
}
|
|
984
|
+
response.append(buffer, 0, count);
|
|
985
|
+
}
|
|
959
986
|
}
|
|
987
|
+
String body = response.toString().trim();
|
|
988
|
+
if (body.startsWith("\uFEFF")) body = body.substring(1).trim();
|
|
989
|
+
boolean success = "success".equalsIgnoreCase(new JSONObject(body).optString("status"));
|
|
990
|
+
finish(success, "server did not confirm success");
|
|
991
|
+
} catch (Exception e) {
|
|
992
|
+
finish(false, "upload failed: " + e.getClass().getSimpleName());
|
|
993
|
+
} finally {
|
|
994
|
+
disconnect();
|
|
960
995
|
}
|
|
961
|
-
new SendFile().execute();
|
|
962
|
-
} catch (Exception e) {
|
|
963
|
-
e.printStackTrace();
|
|
964
996
|
}
|
|
965
997
|
}
|
|
966
998
|
|
|
967
|
-
|
|
968
999
|
public String sendAttachmentToServer(String filePath, String fileName, String requestURL) {
|
|
969
1000
|
String statusIndication = "false";
|
|
970
1001
|
try {
|
|
@@ -22,6 +22,16 @@
|
|
|
22
22
|
app:layout_constraintTop_toTopOf="parent"
|
|
23
23
|
app:layout_constraintStart_toStartOf="parent"/>
|
|
24
24
|
|
|
25
|
+
<!-- Only the controls receive system-bar/cutout padding; the preview fills the window. -->
|
|
26
|
+
<androidx.constraintlayout.widget.ConstraintLayout
|
|
27
|
+
android:id="@+id/cameraControls"
|
|
28
|
+
android:layout_width="0dp"
|
|
29
|
+
android:layout_height="0dp"
|
|
30
|
+
app:layout_constraintTop_toTopOf="parent"
|
|
31
|
+
app:layout_constraintBottom_toBottomOf="parent"
|
|
32
|
+
app:layout_constraintStart_toStartOf="parent"
|
|
33
|
+
app:layout_constraintEnd_toEndOf="parent">
|
|
34
|
+
|
|
25
35
|
<ImageButton
|
|
26
36
|
android:id="@+id/torchButton"
|
|
27
37
|
android:layout_width="56dp"
|
|
@@ -61,4 +71,6 @@
|
|
|
61
71
|
app:layout_constraintEnd_toEndOf="parent"
|
|
62
72
|
app:layout_constraintStart_toEndOf="@+id/captureButton" />
|
|
63
73
|
|
|
74
|
+
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
75
|
+
|
|
64
76
|
</androidx.constraintlayout.widget.ConstraintLayout>
|