// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.moodle.moodlemobile; /* * Imports */ import static android.content.Context.BATTERY_SERVICE; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Random; import org.apache.cordova.CordovaWebView; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaInterface; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.net.ConnectivityManager; import android.net.Uri; import android.os.BatteryManager; import android.os.Build; import android.util.Log; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.provider.Settings; import androidx.core.app.ActivityCompat; /** * Diagnostic plugin implementation for Android */ public class Diagnostic extends CordovaPlugin{ /************* * Constants * *************/ /** * Tag for debug log messages */ public static final String TAG = "Diagnostic"; /** * Map of "dangerous" permissions that need to be requested at run-time (Android 6.0/API 23 and above) * See http://developer.android.com/guide/topics/security/permissions.html#perm-groups */ protected static final Map permissionsMap; static { Map _permissionsMap = new HashMap (); // API 1-22+ Diagnostic.addBiDirMapEntry(_permissionsMap, "ACCESS_COARSE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION"); Diagnostic.addBiDirMapEntry(_permissionsMap, "ACCESS_FINE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"); Diagnostic.addBiDirMapEntry(_permissionsMap, "ADD_VOICEMAIL", "android.permission.ADD_VOICEMAIL"); Diagnostic.addBiDirMapEntry(_permissionsMap, "BODY_SENSORS", "android.permission.BODY_SENSORS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "CALL_PHONE", "android.permission.CALL_PHONE"); Diagnostic.addBiDirMapEntry(_permissionsMap, "CAMERA", "android.permission.CAMERA"); Diagnostic.addBiDirMapEntry(_permissionsMap, "GET_ACCOUNTS", "android.permission.GET_ACCOUNTS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "PROCESS_OUTGOING_CALLS", "android.permission.PROCESS_OUTGOING_CALLS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_CALENDAR", "android.permission.READ_CALENDAR"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_CALL_LOG", "android.permission.READ_CALL_LOG"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_CONTACTS", "android.permission.READ_CONTACTS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_PHONE_STATE", "android.permission.READ_PHONE_STATE"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_SMS", "android.permission.READ_SMS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "RECEIVE_MMS", "android.permission.RECEIVE_MMS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "RECEIVE_SMS", "android.permission.RECEIVE_SMS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "RECEIVE_WAP_PUSH", "android.permission.RECEIVE_WAP_PUSH"); Diagnostic.addBiDirMapEntry(_permissionsMap, "RECORD_AUDIO", "android.permission.RECORD_AUDIO"); Diagnostic.addBiDirMapEntry(_permissionsMap, "SEND_SMS", "android.permission.SEND_SMS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "USE_SIP", "android.permission.USE_SIP"); Diagnostic.addBiDirMapEntry(_permissionsMap, "WRITE_CALENDAR", "android.permission.WRITE_CALENDAR"); Diagnostic.addBiDirMapEntry(_permissionsMap, "WRITE_CALL_LOG", "android.permission.WRITE_CALL_LOG"); Diagnostic.addBiDirMapEntry(_permissionsMap, "WRITE_CONTACTS", "android.permission.WRITE_CONTACTS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "WRITE_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE"); // API 26+ Diagnostic.addBiDirMapEntry(_permissionsMap, "ANSWER_PHONE_CALLS", "android.permission.ANSWER_PHONE_CALLS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_PHONE_NUMBERS", "android.permission.READ_PHONE_NUMBERS"); // API 28+ Diagnostic.addBiDirMapEntry(_permissionsMap, "ACCEPT_HANDOVER", "android.permission.ACCEPT_HANDOVER"); // API 29+ Diagnostic.addBiDirMapEntry(_permissionsMap, "ACCESS_BACKGROUND_LOCATION", "android.permission.ACCESS_BACKGROUND_LOCATION"); Diagnostic.addBiDirMapEntry(_permissionsMap, "ACCESS_MEDIA_LOCATION", "android.permission.ACCESS_MEDIA_LOCATION"); Diagnostic.addBiDirMapEntry(_permissionsMap, "ACTIVITY_RECOGNITION", "android.permission.ACTIVITY_RECOGNITION"); // API 31+ Diagnostic.addBiDirMapEntry(_permissionsMap, "BLUETOOTH_ADVERTISE", "android.permission.BLUETOOTH_ADVERTISE"); Diagnostic.addBiDirMapEntry(_permissionsMap, "BLUETOOTH_CONNECT", "android.permission.BLUETOOTH_CONNECT"); Diagnostic.addBiDirMapEntry(_permissionsMap, "BLUETOOTH_SCAN", "android.permission.BLUETOOTH_SCAN"); Diagnostic.addBiDirMapEntry(_permissionsMap, "UWB_RANGING", "android.permission.UWB_RANGING"); // API 33+ Diagnostic.addBiDirMapEntry(_permissionsMap, "BODY_SENSORS_BACKGROUND", "android.permission.BODY_SENSORS_BACKGROUND"); Diagnostic.addBiDirMapEntry(_permissionsMap, "NEARBY_WIFI_DEVICES", "android.permission.NEARBY_WIFI_DEVICES"); Diagnostic.addBiDirMapEntry(_permissionsMap, "POST_NOTIFICATIONS", "android.permission.POST_NOTIFICATIONS"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_MEDIA_AUDIO", "android.permission.READ_MEDIA_AUDIO"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_MEDIA_IMAGES", "android.permission.READ_MEDIA_IMAGES"); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_MEDIA_VIDEO", "android.permission.READ_MEDIA_VIDEO"); permissionsMap = Collections.unmodifiableMap(_permissionsMap); } /** * Map of minimum build SDK version supported by defined permissions */ protected static final Map minSdkPermissionMap; static { Map _permissionsMap = new HashMap (); // API 26+ Diagnostic.addBiDirMapEntry(_permissionsMap, "ANSWER_PHONE_CALLS", 26); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_PHONE_NUMBERS", 26); // API 28+ Diagnostic.addBiDirMapEntry(_permissionsMap, "ACCEPT_HANDOVER", 28); // API 29+ Diagnostic.addBiDirMapEntry(_permissionsMap, "ACCESS_BACKGROUND_LOCATION", 29); Diagnostic.addBiDirMapEntry(_permissionsMap, "ACCESS_MEDIA_LOCATION", 29); Diagnostic.addBiDirMapEntry(_permissionsMap, "ACTIVITY_RECOGNITION", 29); // API 31+ Diagnostic.addBiDirMapEntry(_permissionsMap, "BLUETOOTH_ADVERTISE", 31); Diagnostic.addBiDirMapEntry(_permissionsMap, "BLUETOOTH_CONNECT", 31); Diagnostic.addBiDirMapEntry(_permissionsMap, "BLUETOOTH_SCAN", 31); Diagnostic.addBiDirMapEntry(_permissionsMap, "UWB_RANGING", 31); // API 33+ Diagnostic.addBiDirMapEntry(_permissionsMap, "BODY_SENSORS_BACKGROUND", 33); Diagnostic.addBiDirMapEntry(_permissionsMap, "NEARBY_WIFI_DEVICES", 33); Diagnostic.addBiDirMapEntry(_permissionsMap, "POST_NOTIFICATIONS", 33); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_MEDIA_AUDIO", 33); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_MEDIA_IMAGES", 33); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_MEDIA_VIDEO", 33); minSdkPermissionMap = Collections.unmodifiableMap(_permissionsMap); } /** * Map of maximum build SDK version supported by defined permissions */ protected static final Map maxSdkPermissionMap; static { Map _permissionsMap = new HashMap (); Diagnostic.addBiDirMapEntry(_permissionsMap, "READ_EXTERNAL_STORAGE", 32); Diagnostic.addBiDirMapEntry(_permissionsMap, "WRITE_EXTERNAL_STORAGE", 29); maxSdkPermissionMap = Collections.unmodifiableMap(_permissionsMap); } /* * Map of permission request code to callback context */ protected HashMap callbackContexts = new HashMap(); /* * Map of permission request code to permission statuses */ protected HashMap permissionStatuses = new HashMap(); /** * User authorised permission */ protected static final String STATUS_GRANTED = "GRANTED"; /** * User denied permission (without checking "never ask again") */ protected static final String STATUS_DENIED_ONCE = "DENIED_ONCE"; /** * User denied permission and checked "never ask again" */ protected static final String STATUS_DENIED_ALWAYS = "DENIED_ALWAYS"; /** * Authorisation has not yet been requested for permission */ protected static final String STATUS_NOT_REQUESTED = "NOT_REQUESTED"; public static final String CPU_ARCH_UNKNOWN = "unknown"; public static final String CPU_ARCH_ARMv6 = "ARMv6"; public static final String CPU_ARCH_ARMv7 = "ARMv7"; public static final String CPU_ARCH_ARMv8 = "ARMv8"; public static final String CPU_ARCH_X86 = "X86"; public static final String CPU_ARCH_X86_64 = "X86_64"; public static final String CPU_ARCH_MIPS = "MIPS"; public static final String CPU_ARCH_MIPS_64 = "MIPS_64"; protected static final String externalStorageClassName = "cordova.plugins.Diagnostic_External_Storage"; protected static final Integer GET_EXTERNAL_SD_CARD_DETAILS_PERMISSION_REQUEST = 1000; /************* * Variables * *************/ /** * Singleton class instance */ public static Diagnostic instance = null; boolean debugEnabled = false; /** * Current Cordova callback context (on this thread) */ protected CallbackContext currentContext; protected Context applicationContext; protected SharedPreferences sharedPref; protected SharedPreferences.Editor editor; /************* * Public API ************/ /** * Constructor. */ public Diagnostic() {} public static Diagnostic getInstance(){ return instance; } /** * Sets the context of the Command. This can then be used to do things like * get file paths associated with the Activity. * * @param cordova The context of the main Activity. * @param webView The CordovaWebView Cordova is running in. */ public void initialize(CordovaInterface cordova, CordovaWebView webView) { Log.d(TAG, "initialize()"); instance = this; applicationContext = this.cordova.getActivity().getApplicationContext(); sharedPref = cordova.getActivity().getSharedPreferences(TAG, Activity.MODE_PRIVATE); editor = sharedPref.edit(); super.initialize(cordova, webView); } /** * Executes the request and returns PluginResult. * * @param action The action to execute. * @param args JSONArry of arguments for the plugin. * @param callbackContext The callback id used when calling back into JavaScript. * @return True if the action was valid, false if not. */ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { currentContext = callbackContext; try { if (action.equals("enableDebug")){ debugEnabled = true; logDebug("Debug enabled"); callbackContext.success(); } else if (action.equals("switchToSettings")){ switchToAppSettings(); callbackContext.success(); } else if (action.equals("switchToMobileDataSettings")){ switchToMobileDataSettings(); callbackContext.success(); } else if (action.equals("switchToWirelessSettings")){ switchToWirelessSettings(); callbackContext.success(); } else if(action.equals("isDataRoamingEnabled")) { if(Build.VERSION.SDK_INT <= 32) { // Android 12L callbackContext.success(isDataRoamingEnabled() ? 1 : 0); } else { callbackContext.error("Data roaming setting not available on Android 12L / API32+"); } callbackContext.success(isDataRoamingEnabled() ? 1 : 0); } else if(action.equals("getPermissionAuthorizationStatus")) { this.getPermissionAuthorizationStatus(args); } else if(action.equals("getPermissionsAuthorizationStatus")) { this.getPermissionsAuthorizationStatus(args); } else if(action.equals("requestRuntimePermission")) { this.requestRuntimePermission(args); } else if(action.equals("requestRuntimePermissions")) { this.requestRuntimePermissions(args); } else if(action.equals("requestMicrophoneAuthorization")) { this.requestRuntimePermission("RECORD_AUDIO"); } else if(action.equals("isADBModeEnabled")) { callbackContext.success(isADBModeEnabled() ? 1 : 0); } else if(action.equals("isDeviceRooted")) { callbackContext.success(isDeviceRooted() ? 1 : 0); } else if(action.equals("isMobileDataEnabled")) { callbackContext.success(isMobileDataEnabled() ? 1 : 0); } else if(action.equals("restart")) { this.restart(args); } else if(action.equals("getArchitecture")) { callbackContext.success(getCPUArchitecture()); } else if(action.equals("getCurrentBatteryLevel")) { callbackContext.success(getCurrentBatteryLevel()); } else if(action.equals("isAirplaneModeEnabled")) { callbackContext.success(isAirplaneModeEnabled() ? 1 : 0); } else if(action.equals("getDeviceOSVersion")) { callbackContext.success(getDeviceOSVersion()); } else if(action.equals("getBuildOSVersion")) { callbackContext.success(getBuildOSVersion()); } else { handleError("Invalid action"); return false; } }catch(Exception e ) { handleError("Exception occurred: ".concat(e.getMessage())); return false; } return true; } public void restart(JSONArray args) throws Exception{ boolean cold = args.getBoolean(0); if(cold){ doColdRestart(); }else{ doWarmRestart(); } } public boolean isDataRoamingEnabled() throws Exception { return Settings.Global.getInt(this.cordova.getActivity().getContentResolver(), Settings.Global.DATA_ROAMING, 0) == 1; } public void switchToAppSettings() { logDebug("Switch to App Settings"); Intent appIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", cordova.getActivity().getPackageName(), null); appIntent.setData(uri); cordova.getActivity().startActivity(appIntent); } public void switchToMobileDataSettings() { logDebug("Switch to Mobile Data Settings"); Intent settingsIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS); cordova.getActivity().startActivity(settingsIntent); } public void switchToWirelessSettings() { logDebug("Switch to wireless Settings"); Intent settingsIntent = new Intent(Settings.ACTION_WIRELESS_SETTINGS); cordova.getActivity().startActivity(settingsIntent); } public void getPermissionsAuthorizationStatus(JSONArray args) throws Exception{ JSONArray permissions = args.getJSONArray(0); JSONObject statuses = _getPermissionsAuthorizationStatus(jsonArrayToStringArray(permissions)); currentContext.success(statuses); } public void getPermissionAuthorizationStatus(JSONArray args) throws Exception{ String permission = args.getString(0); JSONArray permissions = new JSONArray(); permissions.put(permission); JSONObject statuses = _getPermissionsAuthorizationStatus(jsonArrayToStringArray(permissions)); currentContext.success(statuses.getString(permission)); } public void requestRuntimePermissions(JSONArray args) throws Exception{ JSONArray permissions = args.getJSONArray(0); int requestId = storeCurrentContextByRequestId(); _requestRuntimePermissions(permissions, requestId); } public void requestRuntimePermission(JSONArray args) throws Exception{ requestRuntimePermission(args.getString(0)); } public void requestRuntimePermission(String permission) throws Exception{ requestRuntimePermission(permission, storeCurrentContextByRequestId()); } public void requestRuntimePermission(String permission, int requestId) throws Exception{ JSONArray permissions = new JSONArray(); permissions.put(permission); _requestRuntimePermissions(permissions, requestId); } /** * get device ADB mode info */ public int getADBMode(){ int mode; if (Build.VERSION.SDK_INT >= 17){ // Jelly_Bean_MR1 and above mode = Settings.Global.getInt(applicationContext.getContentResolver(), Settings.Global.ADB_ENABLED, 0); } else { // Pre-Jelly_Bean_MR1 mode = Settings.Secure.getInt(applicationContext.getContentResolver(), Settings.Secure.ADB_ENABLED, 0); } return mode; } /** * checks if ADB mode is on * especially for debug mode check */ public boolean isADBModeEnabled(){ boolean result = false; try { result = getADBMode() == 1; } catch (Exception e) { logError(e.getMessage()); } logDebug("ADB mode enabled: " + result); return result; } /** * checks if device is rooted * refer to: https://stackoverflow.com/questions/1101380 */ public boolean isDeviceRooted(){ // from build info String buildTags = android.os.Build.TAGS; if (buildTags != null && buildTags.contains("test-keys")) { return true; } // from binary exists try { String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su" }; for (String path : paths) { if (new File(path).exists()) { return true; } } } catch (Exception e) { logDebug(e.getMessage()); } // from command authority Process process = null; try { process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" }); BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); if (in.readLine() != null) { return true; } } catch (Exception e) { logDebug(e.getMessage()); } finally { if (process != null) process.destroy(); } return false; } // https://stackoverflow.com/a/12864897/777265 public boolean isMobileDataEnabled(){ boolean mobileDataEnabled = false; // Assume disabled ConnectivityManager cm = (ConnectivityManager) cordova.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); try { Class cmClass = Class.forName(cm.getClass().getName()); Method method = cmClass.getDeclaredMethod("getMobileDataEnabled"); method.setAccessible(true); mobileDataEnabled = (Boolean)method.invoke(cm); } catch (Exception e) { logDebug(e.getMessage()); } return mobileDataEnabled; } /************ * Internals ***********/ public void logDebug(String msg) { if(msg == null) return; if(debugEnabled){ Log.d(TAG, msg); executeGlobalJavascript("console.log(\""+TAG+"[native]: "+escapeDoubleQuotes(msg)+"\")"); } } public void logInfo(String msg){ if(msg == null) return; Log.i(TAG, msg); if(debugEnabled){ executeGlobalJavascript("console.info(\""+TAG+"[native]: "+escapeDoubleQuotes(msg)+"\")"); } } public void logWarning(String msg){ if(msg == null) return; Log.w(TAG, msg); if(debugEnabled){ executeGlobalJavascript("console.warn(\""+TAG+"[native]: "+escapeDoubleQuotes(msg)+"\")"); } } public void logError(String msg){ if(msg == null) return; Log.e(TAG, msg); if(debugEnabled){ executeGlobalJavascript("console.error(\""+TAG+"[native]: "+escapeDoubleQuotes(msg)+"\")"); } } public String escapeDoubleQuotes(String string){ String escapedString = string.replace("\"", "\\\""); escapedString = escapedString.replace("%22", "\\%22"); return escapedString; } /** * Handles an error while executing a plugin API method in the specified context. * Calls the registered Javascript plugin error handler callback. * @param errorMsg Error message to pass to the JS error handler */ public void handleError(String errorMsg, CallbackContext context){ try { logError(errorMsg); context.error(errorMsg); } catch (Exception e) { logError(e.toString()); } } /** * Handles an error while executing a plugin API method in the current context. * Calls the registered Javascript plugin error handler callback. * @param errorMsg Error message to pass to the JS error handler */ public void handleError(String errorMsg) { handleError(errorMsg, currentContext); } /** * Handles error during a runtime permissions request. * Calls the registered Javascript plugin error handler callback * then removes entries associated with the request ID. * @param errorMsg Error message to pass to the JS error handler * @param requestId The ID of the runtime request */ public void handleError(String errorMsg, int requestId){ CallbackContext context; String sRequestId = String.valueOf(requestId); if (callbackContexts.containsKey(sRequestId)) { context = callbackContexts.get(sRequestId); }else{ context = currentContext; } handleError(errorMsg, context); clearRequest(requestId); } protected JSONObject _getPermissionsAuthorizationStatus(String[] permissions) throws Exception{ JSONObject statuses = new JSONObject(); for(int i=0; i maxSdkPermissionMap.get(permission)){ throw new Exception("Permission "+permission+" not supported for build SDK version "+getDeviceRuntimeSdkVersion()); } String androidPermission = permissionsMap.get(permission); Log.d(TAG, "Requesting permission for "+androidPermission); permissionsToRequest.put(androidPermission); } } if(permissionsToRequest.length() > 0){ Log.v(TAG, "Requesting permissions"); requestPermissions(this, requestId, jsonArrayToStringArray(permissionsToRequest)); }else{ Log.d(TAG, "No permissions to request: returning result"); sendRuntimeRequestResult(requestId); } } protected boolean isPermissionImplicitlyGranted(String permission) throws Exception{ boolean isImplicitlyGranted = false; int buildTargetSdkVersion = getBuildTargetSdkVersion(); int deviceRuntimeSdkVersion = getDeviceRuntimeSdkVersion(); if(minSdkPermissionMap.containsKey(permission)){ int minSDKForPermission = minSdkPermissionMap.get(permission); if(buildTargetSdkVersion >= minSDKForPermission && deviceRuntimeSdkVersion < minSDKForPermission) { isImplicitlyGranted = true; Log.v(TAG, "Permission "+permission+" is implicitly granted because while it's defined in build SDK version "+buildTargetSdkVersion+", the device runtime SDK version "+deviceRuntimeSdkVersion+" does not support it."); } } return isImplicitlyGranted; } protected void sendRuntimeRequestResult(int requestId){ String sRequestId = String.valueOf(requestId); CallbackContext context = callbackContexts.get(sRequestId); JSONObject statuses = permissionStatuses.get(sRequestId); Log.v(TAG, "Sending runtime request result for id="+sRequestId); context.success(statuses); } protected int storeCurrentContextByRequestId(){ return storeContextByRequestId(currentContext); } protected int storeContextByRequestId(CallbackContext callbackContext){ String requestId = generateRandomRequestId(); callbackContexts.put(requestId, callbackContext); permissionStatuses.put(requestId, new JSONObject()); return Integer.valueOf(requestId); } protected String generateRandomRequestId(){ String requestId = null; while(requestId == null){ requestId = generateRandom(); if(callbackContexts.containsKey(requestId)){ requestId = null; } } return requestId; } protected String generateRandom(){ Random rn = new Random(); int random = rn.nextInt(1000000) + 1; return Integer.toString(random); } protected String[] jsonArrayToStringArray(JSONArray array) throws JSONException{ if(array==null) return null; String[] arr=new String[array.length()]; for(int i=0; i= 24){ minVersion = applicationInfo.minSdkVersion; } } return minVersion; } // https://stackoverflow.com/a/55946200/777265 protected String getNameForApiLevel(int apiLevel) throws Exception{ Field[] fields = Build.VERSION_CODES.class.getFields(); String codeName = "UNKNOWN"; for (Field field : fields) { if (field.getInt(Build.VERSION_CODES.class) == apiLevel) { codeName = field.getName(); } } return codeName; } protected String[] concatStrings(String[] A, String[] B) { int aLen = A.length; int bLen = B.length; String[] C= new String[aLen+bLen]; System.arraycopy(A, 0, C, 0, aLen); System.arraycopy(B, 0, C, aLen, bLen); return C; } /************ * Overrides ***********/ /** * Callback received when a runtime permissions request has been completed. * Retrieves the stateful Cordova context and permission statuses associated with the requestId, * then updates the list of status based on the grantResults before passing the result back via the context. * * @param requestCode - ID that was used when requesting permissions * @param permissions - list of permissions that were requested * @param grantResults - list of flags indicating if above permissions were granted or denied */ public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { String sRequestId = String.valueOf(requestCode); Log.v(TAG, "Received result for permissions request id=" + sRequestId); try { CallbackContext context = getContextById(sRequestId); JSONObject statuses = permissionStatuses.get(sRequestId); for (int i = 0, len = permissions.length; i < len; i++) { String androidPermission = permissions[i]; String permission = permissionsMap.get(androidPermission); if(Build.VERSION.SDK_INT < 29 && permission.equals("ACCESS_BACKGROUND_LOCATION")){ // This version of Android doesn't support background location permission so use standard coarse location permission permission = "ACCESS_COARSE_LOCATION"; } if(Build.VERSION.SDK_INT < 29 && permission.equals("ACTIVITY_RECOGNITION")){ // This version of Android doesn't support activity recognition permission so check for body sensors permission permission = "BODY_SENSORS"; } String status; if (grantResults[i] == PackageManager.PERMISSION_DENIED) { boolean showRationale = shouldShowRequestPermissionRationale(this.cordova.getActivity(), androidPermission); if (!showRationale) { if(isPermissionRequested(permission)){ // user denied WITH "never ask again" status = Diagnostic.STATUS_DENIED_ALWAYS; }else{ // The app doesn't have permission and the user has not been asked for the permission before status = Diagnostic.STATUS_NOT_REQUESTED; } } else { // user denied WITHOUT "never ask again" status = Diagnostic.STATUS_DENIED_ONCE; } } else { // Permission granted status = Diagnostic.STATUS_GRANTED; } statuses.put(permission, status); Log.v(TAG, "Authorisation for " + permission + " is " + statuses.get(permission)); clearRequest(requestCode); } Class externalStorageClass = null; try { externalStorageClass = Class.forName(externalStorageClassName); } catch( ClassNotFoundException e ){} if(requestCode == GET_EXTERNAL_SD_CARD_DETAILS_PERMISSION_REQUEST && externalStorageClass != null){ Method method = externalStorageClass.getMethod("onReceivePermissionResult"); method.invoke(null); }else{ context.success(statuses); } }catch(Exception e ) { handleError("Exception occurred onRequestPermissionsResult: ".concat(e.getMessage()), requestCode); } } }