diff --git a/config.xml b/config.xml
index 07ec852ae..8059c0c81 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 f25baf041..4a17c33c4 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 000000000..77b97745c
--- /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 000000000..53a9e54b8
--- /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 000000000..4a7791247
--- /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 000000000..329391ece
--- /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 000000000..358bcdd4f
--- /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 000000000..c1f1b473c
--- /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 000000000..876709bfe
--- /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 000000000..3d5e2f116
--- /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