cordova-plugin-mserviceinterface-2.0 1.0.94 → 1.0.96

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cordova-plugin-mserviceinterface-2.0",
3
- "version": "1.0.94",
3
+ "version": "1.0.96",
4
4
  "description": "This plugin establishes the connection to the native interface and performs the requested task.",
5
5
  "main": "index.js",
6
6
  "scripts": {
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.94">
3
+ xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-mserviceinterface-2.0" version="1.0.96">
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>
@@ -47,14 +47,11 @@
47
47
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
48
48
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
49
49
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
50
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
51
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
52
50
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
53
51
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
54
52
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
55
53
  <uses-permission android:name="android.permission.READ_PHONE_STATE" />
56
54
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
57
- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
58
55
  <queries>
59
56
  <intent>
60
57
  <action android:name="android.media.action.IMAGE_CAPTURE" />
@@ -486,11 +486,12 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
486
486
  } else if (action.equals("CheckLocationPermission")) {
487
487
  checkLocationPermission(args, callbackContext);
488
488
  return true;
489
- } else if (action.equals("GetMediaImagePermission")) {
490
- getMediaImagePermission(args, callbackContext);
491
- return true;
492
- } else if (action.equals("CheckMediaImagePermission")) {
493
- checkMediaImagePermission(args, callbackContext);
489
+ } else if (action.equals("CheckMediaImagePermission") || action.equals("GetMediaImagePermission")) {
490
+ try {
491
+ callbackContext.success("not_required");
492
+ } catch (Exception e) {
493
+ callbackContext.error(e.getMessage());
494
+ }
494
495
  return true;
495
496
  } else if (action.equals("AppVersion")) {
496
497
  appVersion(callbackContext);
@@ -1606,40 +1607,12 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
1606
1607
  }
1607
1608
  }
1608
1609
 
1609
- private void checkMediaImagePermission(JSONArray args, CallbackContext callbackContext) {
1610
- boolean permissionGranted = true, checkPermission = true;
1611
- List<String> list;
1612
- try {
1613
- callback = callbackContext;
1614
- Context context = cordova.getActivity().getApplicationContext();
1615
- String[] permissions = args.optJSONObject(0).optString("permissions").split(",");
1616
- if (permissions[0].equalsIgnoreCase("android.permission.READ_MEDIA_IMAGES")) {
1617
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
1618
- checkPermission = false;
1619
- }
1620
- }
1621
- if (checkPermission) {
1622
- list = new ArrayList<String>(Arrays.asList(permissions));
1623
- permissions = list.toArray(new String[0]);
1624
-
1625
- for (int i = 0; i < permissions.length; i++) {
1626
- if (ActivityCompat.checkSelfPermission(context, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
1627
- permissionGranted = false;
1628
- }
1629
- }
1630
- if (!permissionGranted) {
1631
- callback.error("failure");
1632
- } else {
1633
- callback.success("success");
1634
- }
1635
- } else {
1636
- callback.success("success");
1637
- }
1638
- } catch (Exception e) {
1639
- e.printStackTrace();
1640
- }
1610
+ private void checkMediaImagePermission(JSONArray args, CallbackContext cb) {
1611
+ try { cb.success("not_required"); } catch (Exception e) { cb.error(e.getMessage()); }
1612
+ }
1613
+ private void getMediaImagePermission(JSONArray args, CallbackContext cb) {
1614
+ try { cb.success("not_required"); } catch (Exception e) { cb.error(e.getMessage()); }
1641
1615
  }
1642
-
1643
1616
 
1644
1617
  private void getLocationPermission(JSONArray args, CallbackContext callbackContext) {
1645
1618
  boolean permissionGranted = true;
@@ -1665,30 +1638,7 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
1665
1638
  e.printStackTrace();
1666
1639
  }
1667
1640
  }
1668
- private void getMediaImagePermission(JSONArray args, CallbackContext callbackContext) {
1669
- boolean permissionGranted = true;
1670
- List<String> list;
1671
- try {
1672
- callback = callbackContext;
1673
- Context context = cordova.getActivity().getApplicationContext();
1674
- String[] permissions = args.optJSONObject(0).optString("permissions").split(",");
1675
- list = new ArrayList<String>(Arrays.asList(permissions));
1676
- permissions = list.toArray(new String[0]);
1677
-
1678
- for (int i = 0; i < permissions.length; i++) {
1679
- if (ActivityCompat.checkSelfPermission(context, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
1680
- permissionGranted = false;
1681
- }
1682
- }
1683
- if (!permissionGranted) {
1684
- PermissionHelper.requestPermissions(this, 111, permissions);
1685
- } else {
1686
- callback.success("success");
1687
- }
1688
- } catch (Exception e) {
1689
- e.printStackTrace();
1690
- }
1691
- }
1641
+
1692
1642
  private void getPermissions(JSONArray args, CallbackContext callbackContext) {
1693
1643
  boolean permissionGranted = true;
1694
1644
  List<String> list;
@@ -1760,29 +1710,33 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
1760
1710
  pluginResult.setKeepCallback(true);
1761
1711
  callbackContext.sendPluginResult(pluginResult);
1762
1712
  }
1763
- public void pickImageFromGallery(CallbackContext callbackContext) {
1713
+
1714
+ void pickImageFromGallery(CallbackContext callbackContext) {
1764
1715
  try {
1765
- Intent openFile,
1766
- selectFile;
1767
- PluginResult pluginResult;
1768
- callback = callbackContext;
1769
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
1770
- // Use the ACTION_OPEN_DOCUMENT intent for Android 13 and higher
1771
- openFile = new Intent(MediaStore.ACTION_PICK_IMAGES);
1772
- openFile.setType("image/*");
1716
+ Intent i;
1717
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
1718
+ i = new Intent(MediaStore.ACTION_PICK_IMAGES);
1719
+ i.setType("image/*");
1773
1720
  } else {
1774
- openFile = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
1775
- openFile.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
1721
+ i = new Intent(Intent.ACTION_OPEN_DOCUMENT);
1722
+ i.addCategory(Intent.CATEGORY_OPENABLE);
1723
+ i.setType("image/*");
1724
+ i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
1776
1725
  }
1777
- selectFile = Intent.createChooser(openFile, "Select File");
1778
- cordova.startActivityForResult(this, selectFile, PICK_IMAGE_FROM_GALLERY);
1779
- pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
1780
- pluginResult.setKeepCallback(true);
1781
- callbackContext.sendPluginResult(pluginResult);
1726
+ i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1727
+ this.callback = callbackContext;
1728
+ PluginResult pr = new PluginResult(PluginResult.Status.NO_RESULT);
1729
+ pr.setKeepCallback(true);
1730
+ callbackContext.sendPluginResult(pr);
1731
+ cordova.startActivityForResult(this, i, PICK_IMAGE_FROM_GALLERY);
1732
+
1733
+
1782
1734
  } catch (Exception e) {
1783
1735
  callbackContext.error("pickImageFromGallery failed, exception is : " + e.getMessage());
1784
1736
  }
1785
1737
  }
1738
+
1739
+
1786
1740
 
1787
1741
  private String getMimeType(String path, Uri uri) {
1788
1742
  String mimeType = null;
@@ -2117,6 +2071,198 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
2117
2071
  return "";
2118
2072
  }
2119
2073
 
2074
+
2075
+ private String safeGetDisplayName(@NonNull ContentResolver cr, @NonNull Uri uri, @NonNull String defaultExt) {
2076
+ String name = null;
2077
+ try (Cursor c = cr.query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
2078
+ if (c != null && c.moveToFirst()) {
2079
+ int idx = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
2080
+ if (idx != -1) name = c.getString(idx);
2081
+ }
2082
+ } catch (Throwable t) {
2083
+ android.util.Log.w("mInterface", "safeGetDisplayName query failed: " + t.getMessage());
2084
+ }
2085
+ if (name == null || name.trim().isEmpty()) {
2086
+ String ts = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new java.util.Date());
2087
+ name = "FILE_" + ts + (defaultExt.startsWith(".") ? defaultExt : "." + defaultExt);
2088
+ }
2089
+ return name;
2090
+ }
2091
+ private void postSuccess(final CallbackContext cb, final org.json.JSONObject out) {
2092
+ if (cb == null) return;
2093
+ final PluginResult pr = new PluginResult(PluginResult.Status.OK, out);
2094
+ pr.setKeepCallback(false);
2095
+ cordova.getActivity().runOnUiThread(new Runnable() {
2096
+ @Override public void run() { cb.sendPluginResult(pr); }
2097
+ });
2098
+ }
2099
+
2100
+ private void postSuccess(final CallbackContext cb, final String s) {
2101
+ if (cb == null) return;
2102
+ final PluginResult pr = new PluginResult(PluginResult.Status.OK, s);
2103
+ pr.setKeepCallback(false);
2104
+ cordova.getActivity().runOnUiThread(new Runnable() {
2105
+ @Override public void run() { cb.sendPluginResult(pr); }
2106
+ });
2107
+ }
2108
+
2109
+ private void postError(final CallbackContext cb, final String msg) {
2110
+ if (cb == null) return;
2111
+ final PluginResult pr = new PluginResult(PluginResult.Status.ERROR, msg);
2112
+ pr.setKeepCallback(false);
2113
+ cordova.getActivity().runOnUiThread(new Runnable() {
2114
+ @Override public void run() { cb.sendPluginResult(pr); }
2115
+ });
2116
+ }
2117
+
2118
+ private int calcInSampleSize(BitmapFactory.Options o, int reqW, int reqH) {
2119
+ int inSample = 1, h = o.outHeight, w = o.outWidth;
2120
+ while ((h / inSample) > reqH || (w / inSample) > reqW) inSample <<= 1;
2121
+ return Math.max(1, inSample);
2122
+ }
2123
+
2124
+ /**
2125
+ * Async version that mirrors the sync behavior:
2126
+ * - "camera"/"gallery": returns base64 string
2127
+ * - "compress": returns JSON { filePath, fileName, fileSize, fileExtension, thumbnail }
2128
+ */
2129
+ private void convertImageTo64BaseStringAsync(final CallbackContext cb,
2130
+ final String imageSourcePath,
2131
+ final String imgSrcType,
2132
+ final String destinationPath) {
2133
+ cordova.getThreadPool().execute(new Runnable() {
2134
+ @Override public void run() {
2135
+ InputStream srcFileReader = null;
2136
+ OutputStream desFileWriter = null;
2137
+ ByteArrayOutputStream baos = null;
2138
+ Bitmap bitmap = null;
2139
+ try {
2140
+ // Common bits
2141
+ final String[] parts = imageSourcePath.split("/");
2142
+ String baseName = parts.length > 0 ? parts[parts.length - 1] : "IMG";
2143
+ if (baseName.indexOf(".") > 0) {
2144
+ baseName = baseName.substring(0, baseName.lastIndexOf("."));
2145
+ }
2146
+ final String extFromSource = "." + imageSourcePath.substring(imageSourcePath.lastIndexOf(".") + 1).toLowerCase();
2147
+ final String thumbnail = getFileThumbnail(imageSourcePath, extFromSource);
2148
+
2149
+ if ("compress".equalsIgnoreCase(imgSrcType)) {
2150
+ // === COMPRESS: write JPEG to destination and return file properties ===
2151
+ File fileToCompress = new File(imageSourcePath);
2152
+ File compressedImageFile = new Compressor(cordova.getActivity())
2153
+ .setQuality(15)
2154
+ .setCompressFormat(Bitmap.CompressFormat.JPEG)
2155
+ .compressToFile(fileToCompress);
2156
+
2157
+ String timeStamp = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new java.util.Date());
2158
+ final String baseDir = (destinationPath != null && !destinationPath.isEmpty())
2159
+ ? (mInterfaceUtil.rootDirectory + "/" + destinationPath)
2160
+ : (mInterfaceUtil.rootDirectory + "/" + mInterfaceUtil.packageName + "/images");
2161
+ final String outName = baseName + "_" + timeStamp + ".jpg";
2162
+ final String outFullPath = baseDir + "/" + outName;
2163
+
2164
+ File outDir = new File(baseDir);
2165
+ if (!outDir.exists()) outDir.mkdirs();
2166
+
2167
+ // Copy compressed temp → final path
2168
+ if (compressedImageFile.exists()) {
2169
+ srcFileReader = new FileInputStream(compressedImageFile);
2170
+ desFileWriter = new FileOutputStream(new File(outFullPath));
2171
+ byte[] buf = new byte[64 * 1024];
2172
+ int n;
2173
+ while ((n = srcFileReader.read(buf)) > 0) {
2174
+ desFileWriter.write(buf, 0, n);
2175
+ }
2176
+ desFileWriter.flush();
2177
+ }
2178
+
2179
+ // (Optional) load to normalize EXIF rotation like the sync version did
2180
+ try { bitmap = handleImageRotation(outFullPath); } catch (Throwable ignore) {}
2181
+
2182
+ // Build properties
2183
+ File outFile = new File(outFullPath);
2184
+ org.json.JSONObject fileProps = new org.json.JSONObject();
2185
+ fileProps.put("filePath", outFile.getParent());
2186
+ fileProps.put("fileName", outFile.getName());
2187
+ fileProps.put("fileSize", outFile.length());
2188
+ fileProps.put("fileExtension", ".jpg");
2189
+ fileProps.put("thumbnail", thumbnail);
2190
+
2191
+ // Match original behavior: clear attachmentDestPath if you rely on that global
2192
+ try { attachmentDestPath = ""; } catch (Throwable ignore) {}
2193
+
2194
+ postSuccess(cb, fileProps);
2195
+ return;
2196
+ }
2197
+
2198
+ // === CAMERA / GALLERY → return BASE64 string ===
2199
+ // Decode with downsampling and fix rotation
2200
+ BitmapFactory.Options o = new BitmapFactory.Options();
2201
+ o.inJustDecodeBounds = true; BitmapFactory.decodeFile(imageSourcePath, o);
2202
+ o.inSampleSize = calcInSampleSize(o, 1920, 1920);
2203
+ o.inJustDecodeBounds = false;
2204
+
2205
+ // Prefer your rotation-aware helper (handles EXIF)
2206
+ try {
2207
+ bitmap = handleImageRotation(imageSourcePath);
2208
+ if (bitmap == null) bitmap = BitmapFactory.decodeFile(imageSourcePath, o);
2209
+ } catch (Throwable t) {
2210
+ bitmap = BitmapFactory.decodeFile(imageSourcePath, o);
2211
+ }
2212
+ if (bitmap == null) {
2213
+ postError(cb, "decode_failed");
2214
+ return;
2215
+ }
2216
+
2217
+ baos = new ByteArrayOutputStream(1024 * 1024);
2218
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 15, baos);
2219
+ String encodedImage = android.util.Base64.encodeToString(baos.toByteArray(), android.util.Base64.NO_WRAP);
2220
+
2221
+ // Match original behavior: delete camera temp + clear dest flag
2222
+ if ("camera".equalsIgnoreCase(imgSrcType)) {
2223
+ try {
2224
+ File f = new File(imageSourcePath);
2225
+ if (f.exists()) f.delete();
2226
+ } catch (Throwable ignore) {}
2227
+ try { attachmentDestPath = ""; } catch (Throwable ignore) {}
2228
+ } else if ("gallery".equalsIgnoreCase(imgSrcType)) {
2229
+ try { attachmentDestPath = ""; } catch (Throwable ignore) {}
2230
+ }
2231
+
2232
+ postSuccess(cb, encodedImage);
2233
+ } catch (Exception e) {
2234
+ try { attachmentDestPath = ""; } catch (Throwable ignore) {}
2235
+ postError(cb, "convertImageTo64BaseStringAsync failed: " + e.getMessage());
2236
+ } finally {
2237
+ try { if (srcFileReader != null) srcFileReader.close(); } catch (Exception ignore) {}
2238
+ try { if (desFileWriter != null) desFileWriter.close(); } catch (Exception ignore) {}
2239
+ try { if (baos != null) baos.close(); } catch (Exception ignore) {}
2240
+ try { if (bitmap != null) bitmap.recycle(); } catch (Exception ignore) {}
2241
+ }
2242
+ }
2243
+ });
2244
+ }
2245
+
2246
+
2247
+ private String copyFileFromUriFast(Context ctx, Uri uri, String destName, String destDir) {
2248
+ java.io.File dir = (destDir!=null && !destDir.isEmpty())
2249
+ ? new java.io.File(ctx.getFilesDir(), destDir)
2250
+ : ctx.getFilesDir();
2251
+ if (!dir.exists() && !dir.mkdirs()) return null;
2252
+ java.io.File out = new java.io.File(dir, destName);
2253
+ try (java.io.InputStream in = ctx.getContentResolver().openInputStream(uri);
2254
+ java.io.OutputStream os = new java.io.FileOutputStream(out)) {
2255
+ if (in == null) return null;
2256
+ byte[] buf = new byte[64 * 1024];
2257
+ int n; while ((n = in.read(buf)) != -1) os.write(buf, 0, n);
2258
+ os.flush();
2259
+ return out.getAbsolutePath();
2260
+ } catch (Exception e) {
2261
+ android.util.Log.e("mInterface", "copyFileFromUriFast", e);
2262
+ return null;
2263
+ }
2264
+ }
2265
+
2120
2266
  @Override
2121
2267
  public void onActivityResult(int requestCode, int resultCode, Intent data) {
2122
2268
  try {
@@ -2158,136 +2304,89 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
2158
2304
  SCANCODEINT = 0;
2159
2305
  callback.error(resultCode);
2160
2306
  }
2161
- } else if (requestCode == PICK_IMAGE_FROM_GALLERY && callback != null) {
2162
- if (resultCode == RESULT_OK) {
2163
- String fileName = null, getfilePath = null, filePath, fileType, storageDefinition, fileExtension;
2164
- final String docId, type, id;
2165
- final String[] split;
2166
- final String tag, path;
2167
- final int splitIndex;
2168
- Cursor cursor;
2169
- Uri uri = data.getData(); //get uri for selected file
2170
- fileType = this.cordova.getActivity().getContentResolver().getType(uri); // get mimeType for selected file
2171
- File getFileName, file;
2172
- try {
2173
- //API LEVEL 19 AND ABOVE (TO GET FILEPATH)
2174
- if (Build.VERSION.SDK_INT >= 19 && !Build.MANUFACTURER.equals("samsung") && DocumentsContract.isDocumentUri(cordova.getActivity(), uri)) {
2175
- if ("com.android.externalstorage.documents".equals(uri.getAuthority())) { // ExternalStorageProvider
2176
- docId = DocumentsContract.getDocumentId(uri);
2177
- split = docId.split(":");
2178
- type = split[0];
2179
- if ("primary".equalsIgnoreCase(type)) {
2180
- getfilePath = mInterfaceUtil.rootDirectory + "/" + split[1];
2181
- } else {
2182
- if (Environment.isExternalStorageRemovable()) {
2183
- storageDefinition = System.getenv("EXTERNAL_STORAGE");
2184
- getfilePath = storageDefinition;
2185
- } else {
2186
- splitIndex = docId.indexOf(':', 1);
2187
- tag = docId.substring(0, splitIndex);
2188
- path = docId.substring(splitIndex + 1);
2189
- if (new mInterfaceUtil().getPathToNonPrimaryVolume(cordova.getActivity(), tag) != null) {
2190
- storageDefinition = new mInterfaceUtil().getPathToNonPrimaryVolume(cordova.getActivity(), tag) + "/" + path;
2191
- file = new File(storageDefinition);
2192
- if (file.exists() && file.canRead()) {
2193
- getfilePath = storageDefinition;
2194
- }
2195
- }
2196
- }
2197
- }
2198
- } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) { // DownloadsProvider
2199
- id = DocumentsContract.getDocumentId(uri);
2200
- final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
2201
- getfilePath = new mInterfaceUtil().getDataColumn(cordova.getActivity(), contentUri, null, null);
2202
- } else if ("com.android.providers.media.documents".equals(uri.getAuthority())) { // MediaProvider
2203
- docId = DocumentsContract.getDocumentId(uri);
2204
- split = docId.split(":");
2205
- type = split[0];
2206
- Uri contentUri = null;
2207
- if ("image".equals(type)) {
2208
- contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
2209
- } else if ("video".equals(type)) {
2210
- contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
2211
- } else if ("audio".equals(type)) {
2212
- contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
2213
- }
2214
- final String selection = "_id=?";
2215
- final String[] selectionArgs = new String[]{
2216
- split[1]
2217
- };
2218
- getfilePath = new mInterfaceUtil().getDataColumn(cordova.getActivity(), contentUri, selection, selectionArgs);
2219
- }
2220
- // *** API Level 19 and Above (To Get FileName,FileExtension and FileSize)**** //
2221
- cursor = cordova.getActivity().getContentResolver().query(uri, null, null, null, null, null);
2307
+ }else if (requestCode == PICK_IMAGE_FROM_GALLERY) {
2308
+ final CallbackContext cb = this.callback;
2309
+ this.callback = null;
2310
+ if (cb == null) return;
2311
+
2312
+ try {
2313
+ if (resultCode != RESULT_OK || data == null) {
2314
+ cb.error("canceled");
2315
+ return;
2316
+ }
2317
+
2318
+ // Photo Picker may return via getData() or ClipData
2319
+ Uri uri = data.getData();
2320
+ if (uri == null && data.getClipData() != null && data.getClipData().getItemCount() > 0) {
2321
+ uri = data.getClipData().getItemAt(0).getUri();
2322
+ }
2323
+ if (uri == null) {
2324
+ cb.error("no_file_selected");
2325
+ return;
2326
+ }
2327
+
2328
+ final Uri selectedUri = uri;
2329
+ final String destPath = attachmentDestPath; // capture current dest
2330
+ final String galleryInd = String.valueOf(galleryAttachmentInd);
2331
+
2332
+ // Offload ALL heavy work (resolver, copy, bitmap/encode) to Cordova thread pool
2333
+ cordova.getThreadPool().execute(new Runnable() {
2334
+ @Override public void run() {
2222
2335
  try {
2223
- if (cursor != null && cursor.moveToFirst()) {
2224
- int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
2225
- if (displayNameIndex != -1) {
2226
- fileName = cursor.getString(displayNameIndex);
2227
- } else {
2228
- fileName = "";
2229
- }
2336
+ ContentResolver cr = cordova.getActivity().getContentResolver();
2337
+
2338
+ String mime = null;
2339
+ try { mime = cr.getType(selectedUri); } catch (Throwable ignore) {}
2340
+
2341
+ String defExt = (mime != null && mime.contains("/"))
2342
+ ? mime.substring(mime.indexOf('/') + 1).toLowerCase()
2343
+ : "jpg";
2344
+
2345
+ // Get a display name if provider allows; otherwise build a sane fallback
2346
+ String fileName = safeGetDisplayName(cr, selectedUri, defExt);
2347
+ String ext = fileName.contains(".")
2348
+ ? fileName.substring(fileName.lastIndexOf('.')).toLowerCase()
2349
+ : ("." + defExt);
2350
+
2351
+ // Copy the user-selected URI into app-private storage (no READ_MEDIA_* needed)
2352
+ String localPath = copyFileFromUriFast(
2353
+ cordova.getActivity().getApplicationContext(),
2354
+ selectedUri, fileName, destPath);
2355
+
2356
+ if (localPath == null) {
2357
+ cb.error("copy_failed");
2358
+ return;
2230
2359
  }
2231
- } finally {
2232
- cursor.close();
2233
- }
2234
- // *** API Level 19 below and SAMSUNG (To Get FileProperties of MediaFiles)**** //
2235
- } else {
2236
- if (fileType != null) {
2237
- String[] projection = {
2238
- MediaStore.Images.Media.DATA,
2239
- MediaStore.Images.Media.SIZE,
2240
- MediaStore.Images.Media.DISPLAY_NAME
2241
- };
2242
- cursor = cordova.getActivity().getContentResolver().query(uri, projection, null, null, null);
2243
- if (cursor.moveToFirst()) {
2244
- getfilePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
2245
- fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME));
2360
+
2361
+ // Accept only image-like picks (by MIME or extension)
2362
+ boolean imageLike =
2363
+ (mime != null && mime.startsWith("image/")) ||
2364
+ ext.equals(".jpg") || ext.equals(".jpeg") ||
2365
+ ext.equals(".png") || ext.equals(".heic");
2366
+
2367
+ if (!imageLike) {
2368
+ cb.error("Only JPEG/PNG/HEIC images are allowed.");
2369
+ return;
2246
2370
  }
2247
- cursor.close();
2248
- // API LEVEL 19 BELOW AND SAMSUNG (TO GET FILE PROPERTIES OF NORMAL FILES(EX:txt,.pdf,etc..,))
2249
- } else {
2250
- getfilePath = data.getData().getPath();
2251
- filePath = getfilePath.substring(0, getfilePath.lastIndexOf(File.separator));
2252
- getFileName = new File(getfilePath);
2253
- fileName = getFileName.getName();
2254
- }
2255
- }
2256
- filePath = getfilePath.substring(0, getfilePath.lastIndexOf(File.separator));
2257
- fileExtension = "." + getfilePath.substring(getfilePath.lastIndexOf(".") + 1).toLowerCase();
2258
- if (filePath != null) {
2259
- try {
2260
- if (fileExtension.equals(".heic")) {
2261
- if (galleryAttachmentInd.equals("true")) {
2262
- convertImageTo64BaseString(callback, filePath + "/" + fileName, "compress", attachmentDestPath);
2263
- } else {
2264
- convertImageTo64BaseString(callback, filePath + "/" + fileName, "gallery", "");
2265
- }
2266
2371
 
2267
- } else if (fileExtension.equals(".jpeg") || fileExtension.equals(".jpg") || fileExtension.equals(".png")) {
2268
- if (galleryAttachmentInd.equals("true")) {
2269
- convertImageTo64BaseString(callback, filePath + "/" + fileName, "compress", attachmentDestPath);
2270
- } else {
2271
- convertImageTo64BaseString(callback, filePath + "/" + fileName, "gallery", "");
2272
- }
2372
+ // Run your existing conversion on a worker too (non-blocking)
2373
+ if ("true".equals(galleryInd)) {
2374
+ convertImageTo64BaseStringAsync(cb, localPath, "compress", destPath);
2273
2375
  } else {
2274
- callback.error("Only JPEG or PNG file extensions are allowed to attach.");
2376
+ convertImageTo64BaseStringAsync(cb, localPath, "gallery", "");
2275
2377
  }
2276
2378
  } catch (Exception e) {
2277
- callback.error("Only JPEG or PNG file extensions are allowed to attach.");
2379
+ cb.error("PICK_IMAGE_FROM_GALLERY: " + e.getMessage());
2278
2380
  }
2279
- } else {
2280
- callback.error("File path was null");
2281
2381
  }
2282
- } catch (Exception e) {
2283
- e.printStackTrace();
2284
- }
2285
- } else if (resultCode == Activity.RESULT_CANCELED) {
2286
- callback.error("failure");
2287
- } else {
2288
- callback.error(resultCode);
2382
+ });
2383
+ return; // IMPORTANT: stop here so no other branch falls through
2384
+ } catch (Exception e) {
2385
+ cb.error("PICK_IMAGE_FROM_GALLERY dispatch failed: " + e.getMessage());
2386
+ return;
2289
2387
  }
2290
- } else if (requestCode == NEW_FILE_PICK_REQUEST && callback != null) {
2388
+ }
2389
+ else if (requestCode == NEW_FILE_PICK_REQUEST && callback != null) {
2291
2390
  try {
2292
2391
  Uri uri = data.getData();
2293
2392
  String fileName, extension, filePath;
@@ -2367,7 +2367,7 @@
2367
2367
  double locationInterval = 0.0;
2368
2368
  NSString * directory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
2369
2369
  NSFileManager *fileManager = [NSFileManager defaultManager];
2370
- NSString *locConfgFilePath = [NSString stringWithFormat:@"%@/%@/app_controller.json.txt", directory, [self GetAppName]];
2370
+ NSString *locConfgFilePath = [NSString stringWithFormat:@"%@/%@/app_controller.json", directory, [self GetAppName]];
2371
2371
  if([fileManager fileExistsAtPath:locConfgFilePath]){
2372
2372
  NSData *user_data = [NSData dataWithContentsOfFile:locConfgFilePath];
2373
2373
  NSError *jsonError = nil;
@@ -2381,25 +2381,32 @@
2381
2381
  NSLog(@"No location update should not be happnened..");
2382
2382
  } else {
2383
2383
  self.locationManager = [[CLLocationManager alloc] init];
2384
- [self.locationManager setDelegate:self];
2385
- [self.locationManager setDistanceFilter:kCLDistanceFilterNone];
2386
- [self.locationManager setHeadingFilter:kCLHeadingFilterNone];
2387
- [self.locationManager requestAlwaysAuthorization];
2388
- [self.locationManager requestWhenInUseAuthorization];
2384
+ self.locationManager.delegate = self;
2385
+ self.locationManager.distanceFilter = kCLDistanceFilterNone;
2386
+ self.locationManager.headingFilter = kCLHeadingFilterNone;
2387
+
2388
+ // Foreground-only: When In Use authorization
2389
2389
  CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
2390
- if (status == kCLAuthorizationStatusAuthorizedAlways) {
2391
- // Location permission is granted, enable background location updates
2392
- if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9) {
2393
- _locationManager.allowsBackgroundLocationUpdates = YES;
2394
- }
2390
+
2391
+ if (status == kCLAuthorizationStatusNotDetermined) {
2392
+ // First time: ask for When In Use
2393
+ [self.locationManager requestWhenInUseAuthorization];
2394
+ } else if (status == kCLAuthorizationStatusAuthorizedWhenInUse ||
2395
+ status == kCLAuthorizationStatusAuthorizedAlways) {
2396
+ // Permission already granted
2397
+ [self.locationManager startUpdatingLocation];
2395
2398
  }
2396
- [self.locationManager startUpdatingLocation];
2399
+
2400
+ // No allowsBackgroundLocationUpdates here
2401
+
2397
2402
  [self KillTimers];
2398
2403
  self.LocationTimer = [NSTimer scheduledTimerWithTimeInterval:locationInterval
2399
- target:self
2404
+ target:self
2400
2405
  selector:@selector(SendLocation)
2401
2406
  userInfo:nil
2402
- repeats:YES];
2407
+ repeats:YES];
2408
+ [self.LocationTimer fire];
2409
+
2403
2410
  }
2404
2411
  } else {
2405
2412
  NSLog(@"'location_configuration' key not found in JSON.");