Merge pull request #3924 from alfonso-salces/MOBILE-4501

MOBILE-4501 cordova-plugin-moodleapp: Add diagnostic plugin
main
Noel De Martin 2024-02-14 09:30:48 +01:00 committed by GitHub
commit 829c59ea23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 2534 additions and 84 deletions

View File

@ -80,18 +80,9 @@
</platform>
<platform name="ios">
<resource-file src="GoogleService-Info.plist" />
<edit-config file="*-Info.plist" mode="merge" target="NSLocationWhenInUseUsageDescription">
<string>We need your location so you can attach it as part of your submissions.</string>
</edit-config>
<edit-config file="*-Info.plist" mode="merge" target="NSLocationAlwaysUsageDescription">
<string>We need your location so you can attach it as part of your submissions.</string>
</edit-config>
<edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription">
<string>We need camera access to take pictures so you can attach them as part of your submissions.</string>
</edit-config>
<edit-config file="*-Info.plist" mode="merge" target="NSMicrophoneUsageDescription">
<string>We need microphone access to record sounds so you can attach them as part of your submissions.</string>
</edit-config>
<edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription">
<string>We need photo library access to get pictures from there so you can attach them as part of your submissions.</string>
</edit-config>

View File

@ -15,9 +15,30 @@
<param name="android-package" value="com.moodle.moodlemobile.InstallReferrer"/>
</feature>
</config-file>
<config-file target="res/xml/config.xml" parent="/*">
<feature name="Diagnostic" >
<param name="android-package" value="com.moodle.moodlemobile.Diagnostic"/>
<param name="onload" value="true" />
</feature>
</config-file>
<js-module name="Diagnostic" src="www/android/diagnostic.js">
<merges target="com.moodle.moodlemobile.diagnostic" />
</js-module>
<source-file src="src/android/Diagnostic.java" target-dir="src/cordova/plugins" />
<source-file src="src/android/Diagnostic_Location.java" target-dir="src/cordova/plugins" />
<preference name="ANDROIDX_VERSION" default="1.0.0" />
<preference name="ANDROIDX_APPCOMPAT_VERSION" default="1.3.1" />
<framework src="androidx.legacy:legacy-support-v4:$ANDROIDX_VERSION" />
<framework src="androidx.appcompat:appcompat:$ANDROIDX_APPCOMPAT_VERSION" />
<source-file src="src/android/SecureStorage.java" target-dir="src/com/moodle/moodlemobile" />
<source-file src="src/android/InstallReferrer.java" target-dir="src/com/moodle/moodlemobile" />
</platform>
<platform name="ios">
<config-file target="config.xml" parent="/*">
<feature name="SecureStorage">
@ -27,5 +48,64 @@
<header-file src="src/ios/SecureStorage.h" />
<source-file src="src/ios/SecureStorage.m" />
<config-file target="config.xml" parent="/*">
<feature name="Diagnostic">
<param name="ios-package" value="Diagnostic" />
<param name="onload" value="true" />
</feature>
</config-file>
<js-module src="www/ios/diagnostic.js" name="Diagnostic">
<merges target="com.moodle.moodlemobile.diagnostic" />
</js-module>
<header-file src="src/ios/Diagnostic.h" />
<source-file src="src/ios/Diagnostic.m" />
<!--BEGIN_MODULE LOCATION-->
<config-file target="config.xml" parent="/*">
<feature name="Diagnostic_Location">
<param name="ios-package" value="Diagnostic_Location" />
<param name="onload" value="true" />
</feature>
</config-file>
<header-file src="src/ios/Diagnostic_Location.h" />
<source-file src="src/ios/Diagnostic_Location.m" />
<framework src="CoreLocation.framework" />
<config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription" comment="Default usage descriptions: override as necessary in .plist">
<string>We need your location so you can attach it as part of your submissions.</string>
</config-file>
<config-file target="*-Info.plist" parent="NSLocationAlwaysUsageDescription" comment="iOS 10">
<string>We need your location so you can attach it as part of your submissions.</string>
</config-file>
<!--END_MODULE LOCATION-->
<!--BEGIN_MODULE MICROPHONE-->
<config-file target="config.xml" parent="/*">
<feature name="Diagnostic_Microphone">
<param name="ios-package" value="Diagnostic_Microphone" />
<param name="onload" value="true" />
</feature>
</config-file>
<js-module src="www/ios/diagnostic.js" name="Diagnostic_Microphone">
<merges target="com.moodle.moodlemobile.microphone" />
</js-module>
<framework src="AVFoundation.framework" />
<config-file target="*-Info.plist" parent="NSMicrophoneUsageDescription">
<string>We need microphone access to record sounds so you can attach them as part of your submissions.</string>
</config-file>
<header-file src="src/ios/Diagnostic_Microphone.h" />
<source-file src="src/ios/Diagnostic_Microphone.m" />
<!--END_MODULE MICROPHONE-->
</platform>
</plugin>

File diff suppressed because it is too large Load Diff

View File

@ -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());
}
}
};
}

View File

@ -0,0 +1,63 @@
/*
* Diagnostic.h
* Diagnostic Plugin - Core Module
*
* Copyright (c) 2015 Working Edge Ltd.
* Copyright (c) 2012 AVANTIC ESTUDIO DE INGENIEROS
*/
#import <Cordova/CDV.h>
#import <Cordova/CDVPlugin.h>
#import <WebKit/WebKit.h>
#import <mach/machine.h>
#import <sys/types.h>
#import <sys/sysctl.h>
// 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

View File

@ -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 <CoreTelephony/CTCellularData.h>
@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: @"<null>"];
}
/********************************/
#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

View File

@ -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 <Cordova/CDV.h>
#import <Cordova/CDVPlugin.h>
#import "Diagnostic.h"
#import <CoreLocation/CoreLocation.h>
@interface Diagnostic_Location : CDVPlugin <CLLocationManagerDelegate>
@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

View File

@ -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

View File

@ -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 <Cordova/CDV.h>
#import <Cordova/CDVPlugin.h>
#import "Diagnostic.h"
#import <AVFoundation/AVFoundation.h>
@interface Diagnostic_Microphone : CDVPlugin
- (void) isMicrophoneAuthorized: (CDVInvokedUrlCommand*)command;
- (void) getMicrophoneAuthorizationStatus: (CDVInvokedUrlCommand*)command;
- (void) requestMicrophoneAuthorization: (CDVInvokedUrlCommand*)command;
@end

View File

@ -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

View File

@ -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,

View File

@ -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<boolean> {
return new Promise<boolean>((resolve, reject) => cordova.exec(resolve, reject, 'Diagnostic', 'isLocationEnabled'));
}
switchToLocationSettings(): Promise<void> {
return new Promise<void>((resolve, reject) => cordova.exec(resolve, reject, 'Diagnostic', 'switchToLocationSettings'));
}
switchToSettings(): Promise<void> {
return new Promise<void>((resolve, reject) => cordova.exec(resolve, reject, 'Diagnostic', 'switchToSettings'));
}
getLocationAuthorizationStatus(): Promise<unknown> {
return new Promise<unknown>((resolve, reject) =>
cordova.exec(resolve, reject, 'Diagnostic', 'getLocationAuthorizationStatus'));
}
requestLocationAuthorization(): Promise<void> {
return new Promise<void>((resolve, reject) => cordova.exec(resolve, reject, 'Diagnostic', 'requestLocationAuthorization'));
}
requestMicrophoneAuthorization(): Promise<string> {
return new Promise<string>((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;

View File

@ -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 {

39
package-lock.json generated
View File

@ -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",

View File

@ -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": {}
}
}

View File

@ -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<C
* Make sure that microphone usage has been authorized.
*/
protected async prepareMicrophoneAuthorization(): Promise<void> {
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'));
}
}

View File

@ -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,

View File

@ -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();
}
}

View File

@ -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<void> {
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<void> {
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<boolean> {
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<void> {
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());
});
}

View File

@ -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();
},
},
],

View File

@ -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<Service extends object = object>(
// 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);