From df0ae16d544a91fc1f9cea20aef0d7b202a6135d Mon Sep 17 00:00:00 2001 From: Alfonso Salces Date: Fri, 9 Feb 2024 10:07:57 +0100 Subject: [PATCH 1/2] MOBILE-4501 diagnostic: Include diagnostic plugin Note: The code from android & ios folders is extracted from https://github.com/dpa99c/cordova-diagnostic-plugin Co-Authored-By: Dave Alden --- config.xml | 9 - cordova-plugin-moodleapp/plugin.xml | 59 + .../src/android/Diagnostic.java | 1073 +++++++++++++++++ .../src/android/Diagnostic_Location.java | 318 +++++ cordova-plugin-moodleapp/src/ios/Diagnostic.h | 63 + cordova-plugin-moodleapp/src/ios/Diagnostic.m | 379 ++++++ .../src/ios/Diagnostic_Location.h | 31 + .../src/ios/Diagnostic_Location.m | 286 +++++ .../src/ios/Diagnostic_Microphone.h | 21 + .../src/ios/Diagnostic_Microphone.m | 97 ++ 10 files changed, 2327 insertions(+), 9 deletions(-) create mode 100644 cordova-plugin-moodleapp/src/android/Diagnostic.java create mode 100644 cordova-plugin-moodleapp/src/android/Diagnostic_Location.java create mode 100644 cordova-plugin-moodleapp/src/ios/Diagnostic.h create mode 100644 cordova-plugin-moodleapp/src/ios/Diagnostic.m create mode 100644 cordova-plugin-moodleapp/src/ios/Diagnostic_Location.h create mode 100644 cordova-plugin-moodleapp/src/ios/Diagnostic_Location.m create mode 100644 cordova-plugin-moodleapp/src/ios/Diagnostic_Microphone.h create mode 100644 cordova-plugin-moodleapp/src/ios/Diagnostic_Microphone.m diff --git a/config.xml b/config.xml index 07ec852aeb9..8059c0c81c4 100644 --- a/config.xml +++ b/config.xml @@ -80,18 +80,9 @@ - - We need your location so you can attach it as part of your submissions. - - - We need your location so you can attach it as part of your submissions. - We need camera access to take pictures so you can attach them as part of your submissions. - - We need microphone access to record sounds so you can attach them as part of your submissions. - We need photo library access to get pictures from there so you can attach them as part of your submissions. diff --git a/cordova-plugin-moodleapp/plugin.xml b/cordova-plugin-moodleapp/plugin.xml index f25baf04121..4a17c33c47a 100644 --- a/cordova-plugin-moodleapp/plugin.xml +++ b/cordova-plugin-moodleapp/plugin.xml @@ -27,5 +27,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + We need your location so you can attach it as part of your submissions. + + + + We need your location so you can attach it as part of your submissions. + + + + + + + + + + + + + + + + + + + We need microphone access to record sounds so you can attach them as part of your submissions. + + + + + + diff --git a/cordova-plugin-moodleapp/src/android/Diagnostic.java b/cordova-plugin-moodleapp/src/android/Diagnostic.java new file mode 100644 index 00000000000..77b97745c01 --- /dev/null +++ b/cordova-plugin-moodleapp/src/android/Diagnostic.java @@ -0,0 +1,1073 @@ +// (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); + } + } + +} diff --git a/cordova-plugin-moodleapp/src/android/Diagnostic_Location.java b/cordova-plugin-moodleapp/src/android/Diagnostic_Location.java new file mode 100644 index 00000000000..53a9e54b867 --- /dev/null +++ b/cordova-plugin-moodleapp/src/android/Diagnostic_Location.java @@ -0,0 +1,318 @@ +// (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 org.apache.cordova.CordovaWebView; +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; + +import android.content.BroadcastReceiver; +import android.content.IntentFilter; +import android.location.LocationManager; +import android.os.Build; +import android.util.Log; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; + +/** + * Diagnostic plugin implementation for Android + */ +public class Diagnostic_Location extends CordovaPlugin{ + + + /************* + * Constants * + *************/ + + /** + * Tag for debug log messages + */ + public static final String TAG = "Diagnostic_Location"; + + private static String gpsLocationPermission = "ACCESS_FINE_LOCATION"; + private static String networkLocationPermission = "ACCESS_COARSE_LOCATION"; + private static String backgroundLocationPermission = "ACCESS_BACKGROUND_LOCATION"; + + + private static final String LOCATION_MODE_HIGH_ACCURACY = "high_accuracy"; + private static final String LOCATION_MODE_DEVICE_ONLY = "device_only"; + private static final String LOCATION_MODE_BATTERY_SAVING = "battery_saving"; + private static final String LOCATION_MODE_OFF = "location_off"; + private static final String LOCATION_MODE_UNKNOWN = "unknown"; + + /************* + * Variables * + *************/ + + /** + * Singleton class instance + */ + public static Diagnostic_Location instance = null; + + private Diagnostic diagnostic; + + public static LocationManager locationManager; + + /** + * Current Cordova callback context (on this thread) + */ + protected CallbackContext currentContext; + + private String currentLocationMode = null; + + /************* + * Public API + ************/ + + /** + * Constructor. + */ + public Diagnostic_Location() {} + + /** + * 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; + diagnostic = Diagnostic.getInstance(); + + try { + diagnostic.applicationContext.registerReceiver(locationProviderChangedReceiver, new IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)); + locationManager = (LocationManager) this.cordova.getActivity().getSystemService(Context.LOCATION_SERVICE); + }catch(Exception e){ + diagnostic.logWarning("Unable to register Location Provider Change receiver: " + e.getMessage()); + } + + try { + currentLocationMode = getLocationModeName(); + }catch(Exception e){ + diagnostic.logWarning("Unable to get initial location mode: " + e.getMessage()); + } + + super.initialize(cordova, webView); + } + + /** + * Called on destroying activity + */ + public void onDestroy() { + try { + diagnostic.applicationContext.unregisterReceiver(locationProviderChangedReceiver); + }catch(Exception e){ + diagnostic.logWarning("Unable to unregister Location Provider Change receiver: " + e.getMessage()); + } + } + + /** + * 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 { + Diagnostic.instance.currentContext = currentContext = callbackContext; + + try { + if (action.equals("switchToLocationSettings")){ + switchToLocationSettings(); + callbackContext.success(); + } else if(action.equals("isLocationAvailable")) { + callbackContext.success(isGpsLocationAvailable() || isNetworkLocationAvailable() ? 1 : 0); + } else if(action.equals("isLocationEnabled")) { + callbackContext.success(isGpsLocationEnabled() || isNetworkLocationEnabled() ? 1 : 0); + } else if(action.equals("isGpsLocationAvailable")) { + callbackContext.success(isGpsLocationAvailable() ? 1 : 0); + } else if(action.equals("isNetworkLocationAvailable")) { + callbackContext.success(isNetworkLocationAvailable() ? 1 : 0); + } else if(action.equals("isGpsLocationEnabled")) { + callbackContext.success(isGpsLocationEnabled() ? 1 : 0); + } else if(action.equals("isNetworkLocationEnabled")) { + callbackContext.success(isNetworkLocationEnabled() ? 1 : 0); + } else if(action.equals("getLocationMode")) { + callbackContext.success(getLocationModeName()); + } else if(action.equals("requestLocationAuthorization")) { + requestLocationAuthorization(args, callbackContext); + }else { + diagnostic.handleError("Invalid action"); + return false; + } + }catch(Exception e ) { + diagnostic.handleError("Exception occurred: ".concat(e.getMessage())); + return false; + } + return true; + } + + public boolean isGpsLocationAvailable() throws Exception { + boolean result = isGpsLocationEnabled() && isLocationAuthorized(); + diagnostic.logDebug("GPS location available: " + result); + return result; + } + + public boolean isGpsLocationEnabled() throws Exception { + int mode = getLocationMode(); + boolean result = (mode == 3 || mode == 1); + diagnostic.logDebug("GPS location setting enabled: " + result); + return result; + } + + public boolean isNetworkLocationAvailable() throws Exception { + boolean result = isNetworkLocationEnabled() && isLocationAuthorized(); + diagnostic.logDebug("Network location available: " + result); + return result; + } + + public boolean isNetworkLocationEnabled() throws Exception { + int mode = getLocationMode(); + boolean result = (mode == 3 || mode == 2); + diagnostic.logDebug("Network location setting enabled: " + result); + return result; + } + + public String getLocationModeName() throws Exception { + String modeName; + int mode = getLocationMode(); + switch(mode){ + case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY: + modeName = LOCATION_MODE_HIGH_ACCURACY; + break; + case Settings.Secure.LOCATION_MODE_SENSORS_ONLY: + modeName = LOCATION_MODE_DEVICE_ONLY; + break; + case Settings.Secure.LOCATION_MODE_BATTERY_SAVING: + modeName = LOCATION_MODE_BATTERY_SAVING; + break; + case Settings.Secure.LOCATION_MODE_OFF: + modeName = LOCATION_MODE_OFF; + break; + default: + modeName = LOCATION_MODE_UNKNOWN; + } + return modeName; + } + + public void notifyLocationStateChange(){ + try { + String newMode = getLocationModeName(); + if(!newMode.equals(currentLocationMode)){ + diagnostic.logDebug("Location mode change to: " + newMode); + diagnostic.executePluginJavascript("location._onLocationStateChange(\"" + newMode +"\");"); + currentLocationMode = newMode; + } + }catch(Exception e){ + diagnostic.logError("Error retrieving current location mode on location state change: "+e.toString()); + } + } + + public void switchToLocationSettings() { + diagnostic.logDebug("Switch to Location Settings"); + Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + cordova.getActivity().startActivity(settingsIntent); + } + + public void requestLocationAuthorization(JSONArray args, CallbackContext callbackContext) throws Exception{ + JSONArray permissionsToRequest = new JSONArray(); + boolean shouldRequestBackground = args.getBoolean(0); + boolean shouldRequestPrecise = args.getBoolean(1); + + permissionsToRequest.put(networkLocationPermission); + if(shouldRequestPrecise || Build.VERSION.SDK_INT < 31){ + permissionsToRequest.put(gpsLocationPermission); + } + + if(shouldRequestBackground && Build.VERSION.SDK_INT >= 29 ){ + permissionsToRequest.put(backgroundLocationPermission); + } + + int requestId = Diagnostic.instance.storeContextByRequestId(callbackContext); + Diagnostic.instance._requestRuntimePermissions(permissionsToRequest, requestId); + + PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); + result.setKeepCallback(true); + callbackContext.sendPluginResult(result); + } + + + + /************ + * Internals + ***********/ + /** + * Returns current location mode + */ + private int getLocationMode() throws Exception { + int mode; + if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 28){ // Kitkat to Oreo, Settings.Secute.LOCATION_MODE was deprecated in Pie (https://developer.android.com/reference/android/provider/Settings.Secure#LOCATION_MODE) + mode = Settings.Secure.getInt(this.cordova.getActivity().getContentResolver(), Settings.Secure.LOCATION_MODE); + }else{ // Pre-Kitkat and post-Oreo + if(isLocationProviderEnabled(LocationManager.GPS_PROVIDER) && isLocationProviderEnabled(LocationManager.NETWORK_PROVIDER)){ + mode = 3; + } else if(isLocationProviderEnabled(LocationManager.GPS_PROVIDER)){ + mode = 1; + } else if(isLocationProviderEnabled(LocationManager.NETWORK_PROVIDER)){ + mode = 2; + }else{ + mode = 0; + } + } + return mode; + } + + private boolean isLocationAuthorized() throws Exception { + boolean authorized = diagnostic.hasRuntimePermission(diagnostic.permissionsMap.get(gpsLocationPermission)) || diagnostic.hasRuntimePermission(diagnostic.permissionsMap.get(networkLocationPermission)); + Log.v(TAG, "Location permission is "+(authorized ? "authorized" : "unauthorized")); + return authorized; + } + + private boolean isLocationProviderEnabled(String provider) { + return locationManager.isProviderEnabled(provider); + } + + + /************ + * Overrides + ***********/ + + protected final BroadcastReceiver locationProviderChangedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + try { + final String action = intent.getAction(); + if(instance != null && action.equals(LocationManager.PROVIDERS_CHANGED_ACTION)){ + Log.v(TAG, "onReceiveLocationProviderChange"); + instance.notifyLocationStateChange(); + } + } catch (Exception e) { + diagnostic.logError("Error receiving location provider state change: "+e.toString()); + } + } + }; +} diff --git a/cordova-plugin-moodleapp/src/ios/Diagnostic.h b/cordova-plugin-moodleapp/src/ios/Diagnostic.h new file mode 100644 index 00000000000..4a779124753 --- /dev/null +++ b/cordova-plugin-moodleapp/src/ios/Diagnostic.h @@ -0,0 +1,63 @@ +/* + * Diagnostic.h + * Diagnostic Plugin - Core Module + * + * Copyright (c) 2015 Working Edge Ltd. + * Copyright (c) 2012 AVANTIC ESTUDIO DE INGENIEROS + */ + +#import +#import +#import + +#import +#import +#import + +// Public constants +extern NSString*const UNKNOWN; + +extern NSString*const AUTHORIZATION_NOT_DETERMINED; +extern NSString*const AUTHORIZATION_DENIED; +extern NSString*const AUTHORIZATION_GRANTED; +extern NSString*const AUTHORIZATION_PROVISIONAL; +extern NSString*const AUTHORIZATION_EPHEMERAL; +extern NSString*const AUTHORIZATION_LIMITED; + +@interface Diagnostic : CDVPlugin + +@property (nonatomic) float osVersion; +@property (nonatomic) BOOL debugEnabled; + +// Plugin API +- (void) enableDebug: (CDVInvokedUrlCommand*)command; +- (void) switchToSettings: (CDVInvokedUrlCommand*)command; +- (void) getBackgroundRefreshStatus: (CDVInvokedUrlCommand*)command; +- (void) getArchitecture: (CDVInvokedUrlCommand*)command; +- (void) getCurrentBatteryLevel: (CDVInvokedUrlCommand*)command; +- (void) getDeviceOSVersion: (CDVInvokedUrlCommand*)command; +- (void) getBuildOSVersion: (CDVInvokedUrlCommand*)command; +- (void) isMobileDataEnabled: (CDVInvokedUrlCommand*)command; + +// Utilities ++ (id) getInstance; +- (void) sendPluginResult: (CDVPluginResult*)result :(CDVInvokedUrlCommand*)command; +- (void) sendPluginResultSuccess:(CDVInvokedUrlCommand*)command; +- (void) sendPluginNoResultAndKeepCallback:(CDVInvokedUrlCommand*)command; +- (void) sendPluginResultBool: (BOOL)result :(CDVInvokedUrlCommand*)command; +- (void) sendPluginResultString: (NSString*)result :(CDVInvokedUrlCommand*)command; +- (void) sendPluginError: (NSString*) errorMessage :(CDVInvokedUrlCommand*)command; +- (void) handlePluginException: (NSException*) exception :(CDVInvokedUrlCommand*)command; +- (void)executeGlobalJavascript: (NSString*)jsString; +- (NSString*) arrayToJsonString:(NSArray*)inputArray; +- (NSString*) objectToJsonString:(NSDictionary*)inputObject; +- (NSArray*) jsonStringToArray:(NSString*)jsonStr; +- (NSDictionary*) jsonStringToDictionary:(NSString*)jsonStr; +- (bool)isNull: (NSString*)str; +- (void)logDebug: (NSString*)msg; +- (void)logError: (NSString*)msg; +- (NSString*)escapeDoubleQuotes: (NSString*)str; +- (void) setSetting: (NSString*)key forValue:(id)value; +- (id) getSetting: (NSString*) key; + +@end diff --git a/cordova-plugin-moodleapp/src/ios/Diagnostic.m b/cordova-plugin-moodleapp/src/ios/Diagnostic.m new file mode 100644 index 00000000000..329391ece2c --- /dev/null +++ b/cordova-plugin-moodleapp/src/ios/Diagnostic.m @@ -0,0 +1,379 @@ +/* + * Diagnostic.m + * Diagnostic Plugin - Core Module + * + * Copyright (c) 2015 Working Edge Ltd. + * Copyright (c) 2012 AVANTIC ESTUDIO DE INGENIEROS + */ + +#import "Diagnostic.h" +#import + +@implementation Diagnostic + +// Public constants +NSString*const UNKNOWN = @"unknown"; + +NSString*const AUTHORIZATION_NOT_DETERMINED = @"not_determined"; +NSString*const AUTHORIZATION_DENIED = @"denied_always"; +NSString*const AUTHORIZATION_GRANTED = @"authorized"; +NSString*const AUTHORIZATION_PROVISIONAL = @"provisional"; // Remote Notifications +NSString*const AUTHORIZATION_EPHEMERAL = @"ephemeral"; // Remote Notifications +NSString*const AUTHORIZATION_LIMITED = @"limited"; // Photo Library + +// Internal constants +static NSString*const LOG_TAG = @"Diagnostic[native]"; + +static NSString*const CPU_ARCH_ARMv6 = @"ARMv6"; +static NSString*const CPU_ARCH_ARMv7 = @"ARMv7"; +static NSString*const CPU_ARCH_ARMv8 = @"ARMv8"; +static NSString*const CPU_ARCH_X86 = @"X86"; +static NSString*const CPU_ARCH_X86_64 = @"X86_64"; + +// Internal properties +static Diagnostic* diagnostic = nil; +static CTCellularData* cellularData; + +/********************************/ +#pragma mark - Public static functions +/********************************/ ++ (id) getInstance{ + return diagnostic; +} + + +/********************************/ +#pragma mark - Plugin API +/********************************/ + +-(void)enableDebug:(CDVInvokedUrlCommand*)command{ + self.debugEnabled = true; + [self logDebug:@"Debug enabled"]; +} + +#pragma mark - Settings +- (void) switchToSettings: (CDVInvokedUrlCommand*)command +{ + @try { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString: UIApplicationOpenSettingsURLString] options:@{} completionHandler:^(BOOL success) { + if (success) { + [self sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] :command]; + }else{ + [self sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR] :command]; + } + }]; + } + @catch (NSException *exception) { + [self handlePluginException:exception :command]; + } +} + +#pragma mark - Background refresh +- (void) getBackgroundRefreshStatus: (CDVInvokedUrlCommand*)command +{ + UIBackgroundRefreshStatus _status; + @try { + // Must run on UI thread + _status = [[UIApplication sharedApplication] backgroundRefreshStatus]; + }@catch (NSException *exception) { + [self handlePluginException:exception :command]; + return; + } + [self.commandDelegate runInBackground:^{ + @try { + NSString* status; + + if (_status == UIBackgroundRefreshStatusAvailable) { + status = AUTHORIZATION_GRANTED; + [self logDebug:@"Background updates are available for the app."]; + }else if(_status == UIBackgroundRefreshStatusDenied){ + status = AUTHORIZATION_DENIED; + [self logDebug:@"The user explicitly disabled background behavior for this app or for the whole system."]; + }else if(_status == UIBackgroundRefreshStatusRestricted){ + status = @"restricted"; + [self logDebug:@"Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user."]; + } + [self sendPluginResultString:status:command]; + } + @catch (NSException *exception) { + [self handlePluginException:exception :command]; + } + }]; +} + + +/********************************/ +#pragma mark - Internal functions +/********************************/ + +- (void)pluginInitialize { + + [super pluginInitialize]; + + diagnostic = self; + + self.debugEnabled = false; + self.osVersion = [[[UIDevice currentDevice] systemVersion] floatValue]; + cellularData = [[CTCellularData alloc] init]; +} + +// https://stackoverflow.com/a/38441011/777265 +- (void) getArchitecture: (CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* cpuArch = UNKNOWN; + + size_t size; + cpu_type_t type; + cpu_subtype_t subtype; + size = sizeof(type); + sysctlbyname("hw.cputype", &type, &size, NULL, 0); + + size = sizeof(subtype); + sysctlbyname("hw.cpusubtype", &subtype, &size, NULL, 0); + + // values for cputype and cpusubtype defined in mach/machine.h + if (type == CPU_TYPE_X86_64) { + cpuArch = CPU_ARCH_X86_64; + } else if (type == CPU_TYPE_X86) { + cpuArch = CPU_ARCH_X86; + } else if (type == CPU_TYPE_ARM64) { + cpuArch = CPU_ARCH_ARMv8; + } else if (type == CPU_TYPE_ARM) { + switch(subtype){ + case CPU_SUBTYPE_ARM_V6: + cpuArch = CPU_ARCH_ARMv6; + break; + case CPU_SUBTYPE_ARM_V7: + cpuArch = CPU_ARCH_ARMv7; + break; + case CPU_SUBTYPE_ARM_V8: + cpuArch = CPU_ARCH_ARMv8; + break; + } + } + [self logDebug:[NSString stringWithFormat:@"Current CPU architecture: %@", cpuArch]]; + [self sendPluginResultString:cpuArch:command]; + }@catch (NSException *exception) { + [self handlePluginException:exception :command]; + } + }]; +} + +- (void) getCurrentBatteryLevel: (CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + UIDevice* currentDevice = [UIDevice currentDevice]; + [currentDevice setBatteryMonitoringEnabled:true]; + int batteryLevel = (int)([currentDevice batteryLevel]*100); + [self logDebug:[NSString stringWithFormat:@"Battery level: %d", batteryLevel]]; + [self sendPluginResultInt:batteryLevel:command]; + [currentDevice setBatteryMonitoringEnabled:false]; + }@catch (NSException *exception) { + [self handlePluginException:exception :command]; + } + }]; +} + +- (void) getDeviceOSVersion: (CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + NSString* s_version = [UIDevice currentDevice].systemVersion; + float f_version = [s_version floatValue]; + + NSDictionary* details = @{ + @"version": s_version, + @"apiLevel" : [NSNumber numberWithFloat:f_version*10000], + @"apiName": s_version + }; + + [self sendPluginResultObject:details:command]; + }@catch (NSException *exception) { + [self handlePluginException:exception :command]; + } + }]; +} + +- (void) getBuildOSVersion: (CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ + @try { + int i_min_version = __IPHONE_OS_VERSION_MIN_REQUIRED; + NSString* s_min_version = [NSString stringWithFormat:@"%.01f", (float) i_min_version/10000]; + int i_target_version = __IPHONE_OS_VERSION_MAX_ALLOWED; + NSString* s_target_version = [NSString stringWithFormat:@"%.01f", (float) i_target_version/10000]; + + NSDictionary* details = @{ + @"targetApiLevel": [NSNumber numberWithInt:i_target_version], + @"targetApiName": s_target_version, + @"minApiLevel": [NSNumber numberWithInt:i_min_version], + @"minApiName": s_min_version + }; + + [self sendPluginResultObject:details:command]; + }@catch (NSException *exception) { + [self handlePluginException:exception :command]; + } + }]; +} + +- (void) isMobileDataEnabled: (CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + @try { + bool isEnabled = cellularData.restrictedState == kCTCellularDataNotRestricted;; + [diagnostic sendPluginResultBool:isEnabled :command]; + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + }]; +} + + +/********************************/ +#pragma mark - Send results +/********************************/ + +- (void) sendPluginResult: (CDVPluginResult*)result :(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; +} + +- (void) sendPluginResultSuccess:(CDVInvokedUrlCommand*)command{ + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId]; +} + +- (void) sendPluginNoResultAndKeepCallback:(CDVInvokedUrlCommand*)command { + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT]; + [pluginResult setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void) sendPluginResultBool: (BOOL)result :(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult; + if(result) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:1]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:0]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void) sendPluginResultString: (NSString*)result :(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:result]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void) sendPluginResultInt: (int)result :(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:result]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void) sendPluginResultObject: (NSDictionary*)result :(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void) sendPluginError: (NSString*) errorMessage :(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; + [self logError:errorMessage]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void) handlePluginException: (NSException*) exception :(CDVInvokedUrlCommand*)command +{ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:exception.reason]; + [self logError:[NSString stringWithFormat:@"EXCEPTION: %@", exception.reason]]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void)executeGlobalJavascript: (NSString*)jsString +{ + [self.commandDelegate evalJs:jsString]; +} + +- (NSString*) arrayToJsonString:(NSArray*)inputArray +{ + NSError* error; + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:inputArray options:NSJSONWritingPrettyPrinted error:&error]; + NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + jsonString = [[jsonString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:@""]; + return jsonString; +} + +- (NSString*) objectToJsonString:(NSDictionary*)inputObject +{ + NSError* error; + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:inputObject options:NSJSONWritingPrettyPrinted error:&error]; + NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + return jsonString; +} + +- (NSArray*) jsonStringToArray:(NSString*)jsonStr +{ + NSError* error = nil; + NSArray* array = [NSJSONSerialization JSONObjectWithData:[jsonStr dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error]; + if (error != nil){ + array = nil; + } + return array; +} + +- (NSDictionary*) jsonStringToDictionary:(NSString*)jsonStr +{ + return (NSDictionary*) [self jsonStringToArray:jsonStr]; +} + +- (bool)isNull: (NSString*)str +{ + return str == nil || str == (id)[NSNull null] || str.length == 0 || [str isEqual: @""]; +} + + +/********************************/ +#pragma mark - utility functions +/********************************/ + +- (void)logDebug: (NSString*)msg +{ + if(self.debugEnabled){ + NSLog(@"%@: %@", LOG_TAG, msg); + NSString* jsString = [NSString stringWithFormat:@"console.log(\"%@: %@\")", LOG_TAG, [self escapeDoubleQuotes:msg]]; + [self executeGlobalJavascript:jsString]; + } +} + +- (void)logError: (NSString*)msg +{ + NSLog(@"%@ ERROR: %@", LOG_TAG, msg); + if(self.debugEnabled){ + NSString* jsString = [NSString stringWithFormat:@"console.error(\"%@: %@\")", LOG_TAG, [self escapeDoubleQuotes:msg]]; + [self executeGlobalJavascript:jsString]; + } +} + +- (NSString*)escapeDoubleQuotes: (NSString*)str +{ + NSString *result =[str stringByReplacingOccurrencesOfString: @"\"" withString: @"\\\""]; + return result; +} + +- (void) setSetting: (NSString*)key forValue:(id)value +{ + [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (id) getSetting: (NSString*) key +{ + return [[NSUserDefaults standardUserDefaults] objectForKey:key]; +} + +@end + + diff --git a/cordova-plugin-moodleapp/src/ios/Diagnostic_Location.h b/cordova-plugin-moodleapp/src/ios/Diagnostic_Location.h new file mode 100644 index 00000000000..358bcdd4f3c --- /dev/null +++ b/cordova-plugin-moodleapp/src/ios/Diagnostic_Location.h @@ -0,0 +1,31 @@ +/* + * Diagnostic_Location.h + * Diagnostic Plugin - Location Module + * + * Copyright (c) 2018 Working Edge Ltd. + * Copyright (c) 2012 AVANTIC ESTUDIO DE INGENIEROS + */ + +#import +#import +#import "Diagnostic.h" +#import + + + +@interface Diagnostic_Location : CDVPlugin + +@property (strong, nonatomic) CLLocationManager* locationManager; +@property (nonatomic, retain) NSString* locationRequestCallbackId; +@property (nonatomic, retain) NSString* currentLocationAuthorizationStatus; +@property (nonatomic, retain) NSString* currentLocationAccuracyAuthorization; + +- (void) isLocationAvailable: (CDVInvokedUrlCommand*)command; +- (void) isLocationEnabled: (CDVInvokedUrlCommand*)command; +- (void) isLocationAuthorized: (CDVInvokedUrlCommand*)command; +- (void) getLocationAuthorizationStatus: (CDVInvokedUrlCommand*)command; +- (void) getLocationAccuracyAuthorization: (CDVInvokedUrlCommand*)command; +- (void) requestLocationAuthorization: (CDVInvokedUrlCommand*)command; +- (void) requestTemporaryFullAccuracyAuthorization: (CDVInvokedUrlCommand*)command; + +@end diff --git a/cordova-plugin-moodleapp/src/ios/Diagnostic_Location.m b/cordova-plugin-moodleapp/src/ios/Diagnostic_Location.m new file mode 100644 index 00000000000..c1f1b473c0d --- /dev/null +++ b/cordova-plugin-moodleapp/src/ios/Diagnostic_Location.m @@ -0,0 +1,286 @@ +/* + * Diagnostic_Location.m + * Diagnostic Plugin - Location Module + * + * Copyright (c) 2018 Working Edge Ltd. + * Copyright (c) 2012 AVANTIC ESTUDIO DE INGENIEROS + */ + +#import "Diagnostic_Location.h" + +@implementation Diagnostic_Location + +// Internal reference to Diagnostic singleton instance +static Diagnostic* diagnostic; + +// Internal constants +static NSString*const LOG_TAG = @"Diagnostic_Location[native]"; + + +/********************************/ +#pragma mark - Plugin API +/********************************/ + +- (void) isLocationAvailable: (CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + @try { + [diagnostic sendPluginResultBool:[CLLocationManager locationServicesEnabled] && [self isLocationAuthorized] :command]; + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + }]; +} + +- (void) isLocationEnabled: (CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + @try { + [diagnostic sendPluginResultBool:[CLLocationManager locationServicesEnabled] :command]; + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + }]; +} + + +- (void) isLocationAuthorized: (CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + @try { + [diagnostic sendPluginResultBool:[self isLocationAuthorized] :command]; + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + }]; +} + +- (void) getLocationAuthorizationStatus: (CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + @try { + NSString* status = [self getLocationAuthorizationStatusAsString:[self getAuthorizationStatus]]; + [diagnostic logDebug:[NSString stringWithFormat:@"Location authorization status is: %@", status]]; + [diagnostic sendPluginResultString:status:command]; + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + }]; +} + +- (void) requestLocationAuthorization: (CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + @try { + if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)]) + { + BOOL always = [[command argumentAtIndex:0] boolValue]; + if(always){ + NSAssert([[[NSBundle mainBundle] infoDictionary] valueForKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"], @"Your app must have a value for NSLocationAlwaysAndWhenInUseUsageDescription in its Info.plist"); + [self.locationManager requestAlwaysAuthorization]; + [diagnostic logDebug:@"Requesting location authorization: always"]; + }else{ + NSAssert([[[NSBundle mainBundle] infoDictionary] valueForKey:@"NSLocationWhenInUseUsageDescription"], @"Your app must have a value for NSLocationWhenInUseUsageDescription in its Info.plist"); + [self.locationManager requestWhenInUseAuthorization]; + [diagnostic logDebug:@"Requesting location authorization: when in use"]; + } + } + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + self.locationRequestCallbackId = command.callbackId; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT]; + [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + [diagnostic sendPluginResult:pluginResult :command]; + }]; +} + +- (void) getLocationAccuracyAuthorization: (CDVInvokedUrlCommand*)command{ + [self.commandDelegate runInBackground:^{ + @try { + +#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 + if ([CLLocationManager instancesRespondToSelector:@selector(requestTemporaryFullAccuracyAuthorizationWithPurposeKey:completion:)]){ + NSString* locationAccuracyAuthorization = [self getLocationAccuracyAuthorizationAsString:[self.locationManager accuracyAuthorization]]; + [diagnostic logDebug:[NSString stringWithFormat:@"Location accuracy authorization is: %@", locationAccuracyAuthorization]]; + [diagnostic sendPluginResultString:locationAccuracyAuthorization:command]; + }else{ + [diagnostic logDebug:@"Location accuracy authorization is not available on device running iOS <14"]; + [diagnostic sendPluginResultString:@"full":command]; + } +#else + [diagnostic logDebug:@"Location accuracy authorization is not available in builds with iOS SDK <14"]; + [diagnostic sendPluginResultString:@"full":command]; +#endif + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + }]; +} + +- (void) requestTemporaryFullAccuracyAuthorization: (CDVInvokedUrlCommand*)command{ + [self.commandDelegate runInBackground:^{ + @try { + +#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 + if ([CLLocationManager instancesRespondToSelector:@selector(requestTemporaryFullAccuracyAuthorizationWithPurposeKey:completion:)]){ + NSAssert([[[NSBundle mainBundle] infoDictionary] valueForKey:@"NSLocationTemporaryUsageDescriptionDictionary"], @"For iOS 14 and above, your app must have a value for NSLocationTemporaryUsageDescriptionDictionary in its Info.plist"); + NSString* purpose = [command argumentAtIndex:0]; + [self.locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:purpose completion:^(NSError* error){ + if(error != nil){ + [diagnostic sendPluginError:[NSString stringWithFormat:@"Error when requesting temporary full location accuracy authorization: %@", error] :command]; + }else{ + NSString* locationAccuracyAuthorization = [self getLocationAccuracyAuthorizationAsString:[self.locationManager accuracyAuthorization]]; + [diagnostic sendPluginResultString:locationAccuracyAuthorization :command]; + } + }]; + }else{ + [diagnostic sendPluginError:@"requestTemporaryFullAccuracyAuthorization is not available on device running iOS <14":command]; + } +#else + [diagnostic sendPluginError:@"requestTemporaryFullAccuracyAuthorization is not available in builds with iOS SDK <14":command]; +#endif + + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + }]; +} + +/********************************/ +#pragma mark - Internals +/********************************/ + +- (void)pluginInitialize { + + [super pluginInitialize]; + + diagnostic = [Diagnostic getInstance]; + + self.locationRequestCallbackId = nil; + self.currentLocationAuthorizationStatus = nil; + self.locationManager = [[CLLocationManager alloc] init]; + self.locationManager.delegate = self; +} + +- (NSString*) getLocationAuthorizationStatusAsString: (CLAuthorizationStatus)authStatus +{ + NSString* status; + if(authStatus == kCLAuthorizationStatusDenied || authStatus == kCLAuthorizationStatusRestricted){ + status = AUTHORIZATION_DENIED; + }else if(authStatus == kCLAuthorizationStatusNotDetermined){ + status = AUTHORIZATION_NOT_DETERMINED; + }else if(authStatus == kCLAuthorizationStatusAuthorizedAlways){ + status = AUTHORIZATION_GRANTED; + }else if(authStatus == kCLAuthorizationStatusAuthorizedWhenInUse){ + status = @"authorized_when_in_use"; + } + return status; +} + +#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 +- (NSString*) getLocationAccuracyAuthorizationAsString: (CLAccuracyAuthorization)accuracyAuthorization +{ + NSString* accuracy; + if(accuracyAuthorization == CLAccuracyAuthorizationFullAccuracy){ + accuracy = @"full"; + }else{ + accuracy = @"reduced"; + } + return accuracy; +} +#endif + +- (BOOL) isLocationAuthorized +{ + CLAuthorizationStatus authStatus = [self getAuthorizationStatus]; + NSString* status = [self getLocationAuthorizationStatusAsString:authStatus]; + if([status isEqual: AUTHORIZATION_GRANTED] || [status isEqual: @"authorized_when_in_use"]) { + return true; + } else { + return false; + } +} + +-(CLAuthorizationStatus) getAuthorizationStatus{ + CLAuthorizationStatus authStatus; +#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 + if ([CLLocationManager instancesRespondToSelector:@selector(authorizationStatus)]){ + authStatus = [self.locationManager authorizationStatus]; + }else{ + authStatus = [CLLocationManager authorizationStatus]; + } +#else + authStatus = [CLLocationManager authorizationStatus]; +#endif + return authStatus; +} + +#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 +// Note: if built with Xcode >=12 (iOS >=14 SDK) but device is running on iOS <=13, this will not be invoked +-(void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager{ + // Location authorization status + [self reportChangeAuthorizationStatus:[self.locationManager authorizationStatus]]; + + // Location accuracy authorization + NSString* locationAccuracyAuthorization = [self getLocationAccuracyAuthorizationAsString:[self.locationManager accuracyAuthorization]]; + BOOL locationAccuracyAuthorizationChanged = false; + if(self.currentLocationAccuracyAuthorization != nil && ![locationAccuracyAuthorization isEqual: self.currentLocationAccuracyAuthorization]){ + locationAccuracyAuthorizationChanged = true; + } + self.currentLocationAccuracyAuthorization = locationAccuracyAuthorization; + + if(locationAccuracyAuthorizationChanged){ + [diagnostic logDebug:[NSString stringWithFormat:@"Location accuracy authorization changed to: %@", locationAccuracyAuthorization]]; + + [diagnostic executeGlobalJavascript:[NSString stringWithFormat:@"cordova.plugins.diagnostic.location._onLocationAccuracyAuthorizationChange(\"%@\");", locationAccuracyAuthorization]]; + } +} +#endif + +- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)authStatus { +#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 + if ([CLLocationManager instancesRespondToSelector:@selector(authorizationStatus)]){ + // Build SDK & device using iOS >=14 so locationManagerDidChangeAuthorization will be invoked + }else{ + // Build SDK using iOS >=14 but device running iOS <=13 + [self reportChangeAuthorizationStatus:authStatus]; + } +#else + // Device may be running iOS >=14 but build SDK is iOS <=13 + [self reportChangeAuthorizationStatus:authStatus]; +#endif +} + + +- (void)reportChangeAuthorizationStatus:(CLAuthorizationStatus)authStatus{ + + NSString* locationAuthorizationStatus = [self getLocationAuthorizationStatusAsString:authStatus]; + BOOL locationAuthorizationStatusChanged = false; + if(self.currentLocationAuthorizationStatus != nil && ![locationAuthorizationStatus isEqual: self.currentLocationAuthorizationStatus]){ + locationAuthorizationStatusChanged = true; + } + self.currentLocationAuthorizationStatus = locationAuthorizationStatus; + + if(locationAuthorizationStatusChanged){ + [diagnostic logDebug:[NSString stringWithFormat:@"Location authorization status changed to: %@", locationAuthorizationStatus]]; + + if(self.locationRequestCallbackId != nil){ + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:locationAuthorizationStatus]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.locationRequestCallbackId]; + self.locationRequestCallbackId = nil; + } + + [diagnostic executeGlobalJavascript:[NSString stringWithFormat:@"cordova.plugins.diagnostic.location._onLocationStateChange(\"%@\");", locationAuthorizationStatus]]; + } +} + +@end + diff --git a/cordova-plugin-moodleapp/src/ios/Diagnostic_Microphone.h b/cordova-plugin-moodleapp/src/ios/Diagnostic_Microphone.h new file mode 100644 index 00000000000..876709bfe90 --- /dev/null +++ b/cordova-plugin-moodleapp/src/ios/Diagnostic_Microphone.h @@ -0,0 +1,21 @@ +/* + * Diagnostic_Microphone.h + * Diagnostic Plugin - Microphone Module + * + * Copyright (c) 2018 Working Edge Ltd. + * Copyright (c) 2012 AVANTIC ESTUDIO DE INGENIEROS + */ + +#import +#import +#import "Diagnostic.h" + +#import + +@interface Diagnostic_Microphone : CDVPlugin + +- (void) isMicrophoneAuthorized: (CDVInvokedUrlCommand*)command; +- (void) getMicrophoneAuthorizationStatus: (CDVInvokedUrlCommand*)command; +- (void) requestMicrophoneAuthorization: (CDVInvokedUrlCommand*)command; + +@end diff --git a/cordova-plugin-moodleapp/src/ios/Diagnostic_Microphone.m b/cordova-plugin-moodleapp/src/ios/Diagnostic_Microphone.m new file mode 100644 index 00000000000..3d5e2f1164c --- /dev/null +++ b/cordova-plugin-moodleapp/src/ios/Diagnostic_Microphone.m @@ -0,0 +1,97 @@ +/* + * Diagnostic_Microphone.m + * Diagnostic Plugin - Microphone Module + * + * Copyright (c) 2018 Working Edge Ltd. + * Copyright (c) 2012 AVANTIC ESTUDIO DE INGENIEROS + */ + +#import "Diagnostic_Microphone.h" + +@implementation Diagnostic_Microphone + +// Internal reference to Diagnostic singleton instance +static Diagnostic* diagnostic; + +// Internal constants +static NSString*const LOG_TAG = @"Diagnostic_Microphone[native]"; + +- (void)pluginInitialize { + + [super pluginInitialize]; + + diagnostic = [Diagnostic getInstance]; +} + +/********************************/ +#pragma mark - Plugin API +/********************************/ + +- (void) isMicrophoneAuthorized: (CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + CDVPluginResult* pluginResult; + @try { + AVAudioSessionRecordPermission recordPermission = [AVAudioSession sharedInstance].recordPermission; + + if(recordPermission == AVAudioSessionRecordPermissionGranted) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:1]; + } + else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:0]; + } + [diagnostic sendPluginResultBool:recordPermission == AVAudioSessionRecordPermissionGranted :command]; + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + }; + }]; +} + +- (void) getMicrophoneAuthorizationStatus: (CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + @try { + NSString* status; + AVAudioSessionRecordPermission recordPermission = [AVAudioSession sharedInstance].recordPermission; + switch(recordPermission){ + case AVAudioSessionRecordPermissionDenied: + status = AUTHORIZATION_DENIED; + break; + case AVAudioSessionRecordPermissionGranted: + status = AUTHORIZATION_GRANTED; + break; + case AVAudioSessionRecordPermissionUndetermined: + status = AUTHORIZATION_NOT_DETERMINED; + break; + } + + [diagnostic logDebug:[NSString stringWithFormat:@"Microphone authorization status is: %@", status]]; + [diagnostic sendPluginResultString:status:command]; + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + }]; +} + +- (void) requestMicrophoneAuthorization: (CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + @try { + [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) { + [diagnostic logDebug:[NSString stringWithFormat:@"Has access to microphone: %d", granted]]; + [diagnostic sendPluginResultBool:granted :command]; + }]; + } + @catch (NSException *exception) { + [diagnostic handlePluginException:exception :command]; + } + }]; +} + +/********************************/ +#pragma mark - Internals +/********************************/ + +@end From abf2867fc6ec020749341b0bc780b6396ecbb366 Mon Sep 17 00:00:00 2001 From: Alfonso Salces Date: Fri, 9 Feb 2024 10:11:51 +0100 Subject: [PATCH 2/2] MOBILE-4501 cordova-plugin-moodleapp: Add diagnostic plugin --- cordova-plugin-moodleapp/plugin.xml | 21 +++ cordova-plugin-moodleapp/src/ts/index.ts | 2 + .../src/ts/plugins/Diagnostic.ts | 131 ++++++++++++++++++ cordova-plugin-moodleapp/types/index.d.ts | 2 + package-lock.json | 39 ------ package.json | 7 - .../audio-recorder.component.ts | 15 +- src/core/features/native/native.module.ts | 3 - .../settings/pages/general/general.ts | 5 +- src/core/services/geolocation.ts | 50 +++++-- src/core/services/utils/iframe.ts | 5 +- src/core/singletons/index.ts | 2 - 12 files changed, 207 insertions(+), 75 deletions(-) create mode 100644 cordova-plugin-moodleapp/src/ts/plugins/Diagnostic.ts diff --git a/cordova-plugin-moodleapp/plugin.xml b/cordova-plugin-moodleapp/plugin.xml index 4a17c33c47a..d8e5e6abceb 100644 --- a/cordova-plugin-moodleapp/plugin.xml +++ b/cordova-plugin-moodleapp/plugin.xml @@ -15,9 +15,30 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/cordova-plugin-moodleapp/src/ts/index.ts b/cordova-plugin-moodleapp/src/ts/index.ts index 79542caa026..1b2ff1e8c44 100644 --- a/cordova-plugin-moodleapp/src/ts/index.ts +++ b/cordova-plugin-moodleapp/src/ts/index.ts @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { Diagnostic } from './plugins/Diagnostic'; import { InstallReferrer } from './plugins/InstallReferrer'; import { SecureStorage } from './plugins/SecureStorage'; const api: MoodleAppPlugins = { secureStorage: new SecureStorage(), installReferrer: new InstallReferrer(), + diagnostic: new Diagnostic(), }; // This is necessary to work around the default transpilation behavior, diff --git a/cordova-plugin-moodleapp/src/ts/plugins/Diagnostic.ts b/cordova-plugin-moodleapp/src/ts/plugins/Diagnostic.ts new file mode 100644 index 00000000000..34c17739d89 --- /dev/null +++ b/cordova-plugin-moodleapp/src/ts/plugins/Diagnostic.ts @@ -0,0 +1,131 @@ +// (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. +// (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. +// (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. + +/** + * Checks whether device hardware features are enabled or available to the app, e.g. camera, GPS, wifi + */ +export class Diagnostic { + + /** + * Constants for requesting and reporting the various permission states. + */ + declare permissionStatus: typeof permissionStatus; + + /** + * ANDROID ONLY + * "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 + * + */ + declare permission: typeof permission; + + constructor() { + this.permissionStatus = permissionStatus; + this.permission = permission; + } + + isLocationEnabled(): Promise { + return new Promise((resolve, reject) => cordova.exec(resolve, reject, 'Diagnostic', 'isLocationEnabled')); + } + + switchToLocationSettings(): Promise { + return new Promise((resolve, reject) => cordova.exec(resolve, reject, 'Diagnostic', 'switchToLocationSettings')); + } + + switchToSettings(): Promise { + return new Promise((resolve, reject) => cordova.exec(resolve, reject, 'Diagnostic', 'switchToSettings')); + } + + getLocationAuthorizationStatus(): Promise { + return new Promise((resolve, reject) => + cordova.exec(resolve, reject, 'Diagnostic', 'getLocationAuthorizationStatus')); + } + + requestLocationAuthorization(): Promise { + return new Promise((resolve, reject) => cordova.exec(resolve, reject, 'Diagnostic', 'requestLocationAuthorization')); + } + + requestMicrophoneAuthorization(): Promise { + return new Promise((resolve, reject) => + cordova.exec(resolve, reject, 'Diagnostic', 'requestMicrophoneAuthorization')); + } + +} + +const permission = { + acceptHandover: 'ACCEPT_HANDOVER', + accessBackgroundLocation: 'ACCESS_BACKGROUND_LOCATION', + accessCoarseLocation: 'ACCESS_COARSE_LOCATION', + accessFineLocation: 'ACCESS_FINE_LOCATION', + accessMediaLocation: 'ACCESS_MEDIA_LOCATION', + bodySensors: 'BODY_SENSORS', + bodySensorsBackground: 'BODY_SENSORS_BACKGROUND', + getAccounts: 'GET_ACCOUNTS', + readExternalStorage: 'READ_EXTERNAL_STORAGE', + readMediaAudio: 'READ_MEDIA_AUDIO', + readMediaImages: 'READ_MEDIA_IMAGES', + readMediaVideo: 'READ_MEDIA_VIDEO', + readPhoneState: 'READ_PHONE_STATE', + readSms: 'READ_SMS', + receiveMms: 'RECEIVE_MMS', + receiveSms: 'RECEIVE_SMS', + receiveWapPush: 'RECEIVE_WAP_PUSH', + recordAudio: 'RECORD_AUDIO', + sendSms: 'SEND_SMS', + useSip: 'USE_SIP', + uwbRanging: 'UWB_RANGING', + writeExternalStorage: 'WRITE_EXTERNAL_STORAGE', +} as const; + +const permissionStatus = { + // Android only + deniedOnce: 'DENIED_ONCE', + + // iOS only + restricted: 'restricted', + ephimeral: 'ephemeral', + provisional: 'provisional', + + // Both iOS and Android + granted: 'authorized' || 'GRANTED', + grantedWhenInUse: 'authorized_when_in_use', + notRequested: 'not_determined' || 'NOT_REQUESTED', + deniedAlways: 'denied_always' || 'DENIED_ALWAYS', +} as const; diff --git a/cordova-plugin-moodleapp/types/index.d.ts b/cordova-plugin-moodleapp/types/index.d.ts index 9e7c3c6f23c..18482e2d590 100644 --- a/cordova-plugin-moodleapp/types/index.d.ts +++ b/cordova-plugin-moodleapp/types/index.d.ts @@ -14,12 +14,14 @@ import { InstallReferrer } from '../src/ts/plugins/InstallReferrer'; import { SecureStorage as SecureStorageImpl } from '../src/ts/plugins/SecureStorage'; +import { Diagnostic } from '../src/ts/plugins/Diagnostic'; declare global { interface MoodleAppPlugins { secureStorage: SecureStorageImpl; installReferrer: InstallReferrer; + diagnostic: Diagnostic; } interface Cordova { diff --git a/package-lock.json b/package-lock.json index 41ee2d71951..5566d6bc715 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "@awesome-cordova-plugins/clipboard": "^6.6.0", "@awesome-cordova-plugins/core": "^6.6.0", "@awesome-cordova-plugins/device": "^6.6.0", - "@awesome-cordova-plugins/diagnostic": "^6.6.0", "@awesome-cordova-plugins/file": "^6.6.0", "@awesome-cordova-plugins/file-opener": "^6.6.0", "@awesome-cordova-plugins/geolocation": "^6.6.0", @@ -80,7 +79,6 @@ "cordova-plugin-wkuserscript": "^1.0.1", "cordova-plugin-wkwebview-cookies": "^1.0.1", "cordova-sqlite-storage": "^6.1.0", - "cordova.plugins.diagnostic": "^7.1.4", "core-js": "^3.9.1", "es6-promise-plugin": "^4.2.2", "ionicons": "^7.0.0", @@ -2573,22 +2571,6 @@ "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-11.0.3.tgz", "integrity": "sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==" }, - "node_modules/@awesome-cordova-plugins/diagnostic": { - "version": "6.6.0", - "license": "MIT", - "dependencies": { - "@types/cordova": "latest" - }, - "peerDependencies": { - "@awesome-cordova-plugins/core": "^6.0.1", - "rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0" - } - }, - "node_modules/@awesome-cordova-plugins/diagnostic/node_modules/@types/cordova": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-11.0.3.tgz", - "integrity": "sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==" - }, "node_modules/@awesome-cordova-plugins/file": { "version": "6.6.0", "license": "MIT", @@ -11904,13 +11886,6 @@ "version": "2.0.20", "license": "MIT" }, - "node_modules/colors": { - "version": "1.4.0", - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "license": "MIT", @@ -13003,20 +12978,6 @@ "version": "4.1.0", "license": "MIT" }, - "node_modules/cordova.plugins.diagnostic": { - "version": "7.1.4", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "colors": "1.4.0", - "elementtree": "^0.1.6", - "minimist": "1.2.6" - } - }, - "node_modules/cordova.plugins.diagnostic/node_modules/minimist": { - "version": "1.2.6", - "license": "MIT" - }, "node_modules/cordova/node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", diff --git a/package.json b/package.json index 440f6af0424..cea8eeef226 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "@awesome-cordova-plugins/clipboard": "^6.6.0", "@awesome-cordova-plugins/core": "^6.6.0", "@awesome-cordova-plugins/device": "^6.6.0", - "@awesome-cordova-plugins/diagnostic": "^6.6.0", "@awesome-cordova-plugins/file": "^6.6.0", "@awesome-cordova-plugins/file-opener": "^6.6.0", "@awesome-cordova-plugins/geolocation": "^6.6.0", @@ -114,7 +113,6 @@ "cordova-plugin-wkuserscript": "^1.0.1", "cordova-plugin-wkwebview-cookies": "^1.0.1", "cordova-sqlite-storage": "^6.1.0", - "cordova.plugins.diagnostic": "^7.1.4", "core-js": "^3.9.1", "es6-promise-plugin": "^4.2.2", "ionicons": "^7.0.0", @@ -236,11 +234,6 @@ "cordova-plugin-wkuserscript": {}, "cordova-plugin-wkwebview-cookies": {}, "cordova-sqlite-storage": {}, - "cordova.plugins.diagnostic": { - "ANDROID_SUPPORT_VERSION": "28.+", - "ANDROIDX_VERSION": "1.0.0", - "ANDROIDX_APPCOMPAT_VERSION": "1.6.1" - }, "nl.kingsquare.cordova.background-audio": {} } } diff --git a/src/core/features/fileuploader/components/audio-recorder/audio-recorder.component.ts b/src/core/features/fileuploader/components/audio-recorder/audio-recorder.component.ts index 6c8eba5828b..2c2a3187f55 100644 --- a/src/core/features/fileuploader/components/audio-recorder/audio-recorder.component.ts +++ b/src/core/features/fileuploader/components/audio-recorder/audio-recorder.component.ts @@ -15,7 +15,7 @@ import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy } from '@angular/core'; import { CoreModalComponent } from '@classes/modal-component'; import { CorePlatform } from '@services/platform'; -import { Diagnostic, DomSanitizer, Translate } from '@singletons'; +import { DomSanitizer, Translate } from '@singletons'; import { BehaviorSubject, combineLatest, Observable, OperatorFunction } from 'rxjs'; import { Mp3MediaRecorder } from 'mp3-mediarecorder'; import { map, shareReplay, tap } from 'rxjs/operators'; @@ -26,6 +26,7 @@ import { CAPTURE_ERROR_NO_MEDIA_FILES, CoreCaptureError } from '@classes/errors/ import { CoreFileUploaderAudioRecording } from '@features/fileuploader/services/fileuploader'; import { CoreFile, CoreFileProvider } from '@services/file'; import { CorePath } from '@singletons/path'; +import { CoreNative } from '@features/native/services/native'; @Component({ selector: 'core-fileuploader-audio-recorder', @@ -204,17 +205,19 @@ export class CoreFileUploaderAudioRecorderComponent extends CoreModalComponent { - if (!CorePlatform.isMobile()) { + const diagnostic = await CoreNative.plugin('diagnostic')?.getInstance(); + + if (!CorePlatform.isMobile() || !diagnostic) { return; } - const status = await Diagnostic.requestMicrophoneAuthorization(); + const status = await diagnostic.requestMicrophoneAuthorization(); switch (status) { - case Diagnostic.permissionStatus.DENIED_ONCE: - case Diagnostic.permissionStatus.DENIED_ALWAYS: + case diagnostic.permissionStatus.deniedOnce: + case diagnostic.permissionStatus.deniedAlways: throw new Error(Translate.instant('core.fileuploader.microphonepermissiondenied')); - case Diagnostic.permissionStatus.RESTRICTED: + case diagnostic.permissionStatus.restricted: throw new Error(Translate.instant('core.fileuploader.microphonepermissionrestricted')); } } diff --git a/src/core/features/native/native.module.ts b/src/core/features/native/native.module.ts index 102e004f482..39ab660048c 100644 --- a/src/core/features/native/native.module.ts +++ b/src/core/features/native/native.module.ts @@ -19,7 +19,6 @@ import { Camera } from '@awesome-cordova-plugins/camera/ngx'; import { Chooser } from '@features/native/plugins/chooser'; import { Clipboard } from '@awesome-cordova-plugins/clipboard/ngx'; import { Device } from '@awesome-cordova-plugins/device/ngx'; -import { Diagnostic } from '@awesome-cordova-plugins/diagnostic/ngx'; import { File } from '@awesome-cordova-plugins/file/ngx'; import { FileOpener } from '@awesome-cordova-plugins/file-opener/ngx'; import { Geolocation } from '@awesome-cordova-plugins/geolocation/ngx'; @@ -43,7 +42,6 @@ export const CORE_NATIVE_SERVICES = [ Chooser, Clipboard, Device, - Diagnostic, File, FileOpener, Geolocation, @@ -68,7 +66,6 @@ export const CORE_NATIVE_SERVICES = [ Camera, Clipboard, Device, - Diagnostic, File, FileOpener, Geolocation, diff --git a/src/core/features/settings/pages/general/general.ts b/src/core/features/settings/pages/general/general.ts index 638b088ef50..176a04772bb 100644 --- a/src/core/features/settings/pages/general/general.ts +++ b/src/core/features/settings/pages/general/general.ts @@ -20,13 +20,14 @@ import { CoreLang } from '@services/lang'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreSettingsHelper, CoreColorScheme, CoreZoomLevel } from '../../services/settings-helper'; import { CoreIframeUtils } from '@services/utils/iframe'; -import { Diagnostic, Translate } from '@singletons'; +import { Translate } from '@singletons'; import { CoreSites } from '@services/sites'; import { CoreUtils } from '@services/utils/utils'; import { AlertButton } from '@ionic/angular'; import { CoreNavigator } from '@services/navigator'; import { CorePlatform } from '@services/platform'; import { CoreAnalytics } from '@services/analytics'; +import { CoreNative } from '@features/native/services/native'; /** * Page that displays the general settings. @@ -263,7 +264,7 @@ export class CoreSettingsGeneralPage { ev.stopPropagation(); ev.preventDefault(); - Diagnostic.switchToSettings(); + CoreNative.plugin('diagnostic')?.switchToSettings(); } } diff --git a/src/core/services/geolocation.ts b/src/core/services/geolocation.ts index c696550f3cb..2f3df421bbd 100644 --- a/src/core/services/geolocation.ts +++ b/src/core/services/geolocation.ts @@ -17,12 +17,13 @@ import { Coordinates } from '@awesome-cordova-plugins/geolocation'; import { CoreApp } from '@services/app'; import { CoreAnyError, CoreError } from '@classes/errors/error'; -import { Geolocation, Diagnostic, makeSingleton } from '@singletons'; +import { Geolocation, makeSingleton } from '@singletons'; import { CoreUtils } from './utils/utils'; import { CorePlatform } from './platform'; import { CoreSilentError } from '@classes/errors/silenterror'; import { CoreSubscriptions } from '@singletons/subscriptions'; import { CoreLogger } from '@singletons/logger'; +import { CoreNative } from '@features/native/services/native'; @Injectable({ providedIn: 'root' }) export class CoreGeolocationProvider { @@ -79,7 +80,13 @@ export class CoreGeolocationProvider { * @throws {CoreGeolocationError} */ async enableLocation(): Promise { - let locationEnabled = await Diagnostic.isLocationEnabled(); + const diagnostic = CoreNative.plugin('diagnostic'); + + if (!diagnostic) { + return; + } + + let locationEnabled = await diagnostic.isLocationEnabled(); if (locationEnabled) { // Location is enabled. @@ -87,10 +94,10 @@ export class CoreGeolocationProvider { } if (!CorePlatform.isIOS()) { - Diagnostic.switchToLocationSettings(); + diagnostic.switchToLocationSettings(); await CoreApp.waitForResume(30000); - locationEnabled = await Diagnostic.isLocationEnabled(); + locationEnabled = await diagnostic.isLocationEnabled(); } if (!locationEnabled) { @@ -105,26 +112,34 @@ export class CoreGeolocationProvider { * @throws {CoreGeolocationError} */ protected async doAuthorizeLocation(failOnDeniedOnce: boolean = false): Promise { - const authorizationStatus = await Diagnostic.getLocationAuthorizationStatus(); + const diagnostic = await CoreNative.plugin('diagnostic')?.getInstance(); + + if (!diagnostic) { + return; + } + + const authorizationStatus = await diagnostic.getLocationAuthorizationStatus(); this.logger.log(`Authorize location: status ${authorizationStatus}`); switch (authorizationStatus) { - case Diagnostic.permissionStatus.DENIED_ONCE: + case diagnostic.permissionStatus.deniedOnce: if (failOnDeniedOnce) { throw new CoreGeolocationError(CoreGeolocationErrorReason.PERMISSION_DENIED); } + + case diagnostic.permissionStatus.granted: + case diagnostic.permissionStatus.grantedWhenInUse: + // Location is authorized. + return; + // Fall through. - case Diagnostic.permissionStatus.NOT_REQUESTED: + case diagnostic.permissionStatus.notRequested: this.logger.log('Request location authorization.'); await this.requestLocationAuthorization(); this.logger.log('Location authorization granted.'); await CoreApp.waitForResume(500); await this.doAuthorizeLocation(true); - return; - case Diagnostic.permissionStatus.GRANTED: - case Diagnostic.permissionStatus.GRANTED_WHEN_IN_USE: - // Location is authorized. return; default: throw new CoreGeolocationError(CoreGeolocationErrorReason.PERMISSION_DENIED); @@ -151,7 +166,13 @@ export class CoreGeolocationProvider { * @returns If location can be requested. */ async canRequest(): Promise { - return CoreUtils.promiseWorks(Diagnostic.getLocationAuthorizationStatus()); + const diagnostic = CoreNative.plugin('diagnostic'); + + if (diagnostic) { + return CoreUtils.promiseWorks(diagnostic.getLocationAuthorizationStatus()); + } + + return false; } /** @@ -159,7 +180,7 @@ export class CoreGeolocationProvider { */ protected async requestLocationAuthorization(): Promise { if (!CorePlatform.isIOS()) { - await Diagnostic.requestLocationAuthorization(); + await CoreNative.plugin('diagnostic')?.requestLocationAuthorization(); return; } @@ -168,7 +189,8 @@ export class CoreGeolocationProvider { return new Promise((resolve, reject) => { // Don't display an error if app is sent to the background, just finish the process. const unsubscribe = CoreSubscriptions.once(CorePlatform.pause, () => reject(new CoreSilentError())); - Diagnostic.requestLocationAuthorization().then(() => resolve(), reject).finally(() => unsubscribe()); + CoreNative.plugin('diagnostic')?.requestLocationAuthorization() + .then(() => resolve(), reject).finally(() => unsubscribe()); }); } diff --git a/src/core/services/utils/iframe.ts b/src/core/services/utils/iframe.ts index 0410a29899e..6837b26255f 100644 --- a/src/core/services/utils/iframe.ts +++ b/src/core/services/utils/iframe.ts @@ -24,7 +24,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreUrlUtils } from '@services/utils/url'; import { CoreUtils } from '@services/utils/utils'; -import { makeSingleton, NgZone, Translate, Diagnostic } from '@singletons'; +import { makeSingleton, NgZone, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreUrl } from '@singletons/url'; import { CoreWindow } from '@singletons/window'; @@ -36,6 +36,7 @@ import { FrameElement } from '@classes/element-controllers/FrameElementControlle import { CoreMimetypeUtils } from './mimetype'; import { CoreFilepool } from '@services/filepool'; import { CoreSite } from '@classes/sites/site'; +import { CoreNative } from '@features/native/services/native'; type CoreFrameElement = FrameElement & { window?: Window; @@ -627,7 +628,7 @@ export class CoreIframeUtilsProvider { { text: Translate.instant('core.opensettings'), handler: (): void => { - Diagnostic.switchToSettings(); + CoreNative.plugin('diagnostic')?.switchToSettings(); }, }, ], diff --git a/src/core/singletons/index.ts b/src/core/singletons/index.ts index 1bef7dd7db0..eaba8d5164a 100644 --- a/src/core/singletons/index.ts +++ b/src/core/singletons/index.ts @@ -40,7 +40,6 @@ import { import { Badge as BadgeService } from '@awesome-cordova-plugins/badge/ngx'; import { Camera as CameraService } from '@awesome-cordova-plugins/camera/ngx'; import { Clipboard as ClipboardService } from '@awesome-cordova-plugins/clipboard/ngx'; -import { Diagnostic as DiagnosticService } from '@awesome-cordova-plugins/diagnostic/ngx'; import { Device as DeviceService } from '@awesome-cordova-plugins/device/ngx'; import { File as FileService } from '@awesome-cordova-plugins/file/ngx'; import { FileOpener as FileOpenerService } from '@awesome-cordova-plugins/file-opener/ngx'; @@ -171,7 +170,6 @@ export function makeSingleton( // Convert ionic-native services to singleton. export const Badge = makeSingleton(BadgeService); export const Clipboard = makeSingleton(ClipboardService); -export const Diagnostic = makeSingleton(DiagnosticService); export const File = makeSingleton(FileService); export const FileOpener = makeSingleton(FileOpenerService); export const Geolocation = makeSingleton(GeolocationService);