diff --git a/docs/dictionary/command/androidRequestPermission.lcdoc b/docs/dictionary/command/androidRequestPermission.lcdoc new file mode 100644 index 00000000000..07e5f1ec831 --- /dev/null +++ b/docs/dictionary/command/androidRequestPermission.lcdoc @@ -0,0 +1,65 @@ +Name: androidRequestPermission + +Type: command + +Syntax: androidRequestPermission + +Summary: +Displays a dialog showing a permission request for . If a user has already granted permission for , this command does nothing. + +Introduced: 9.0.1 + +OS: android + +Platforms: mobile + +Example: +local tCameraPermissionGranted +put androidHasPermission("android.permission.CAMERA") into tCameraPermissionGranted +if not tCameraPermissionGranted then + androidRequestPermission "android.permission.CAMERA" +end if +put androidHasPermission("android.permission.CAMERA") into tCameraPermissionGranted +if not tCameraPermissionGranted then + answer "This app is not permitted to access the device camera. You can change this" && \ + "in the Settings app." +end if + + +Parameters: +permissionName (enum): +The name of the permission to request. + +- "android.permission.READ_CALENDAR": permission to allow an application to read the device's calendar. +- "android.permission.WRITE_CALENDAR": permission to allow an application to write to the device's calendar. +- "android.permission.CAMERA": permission to allow an application to access the device's camera. +- "android.permission.ACCESS_COARSE_LOCATION": permission to allow an application to access the device's coarse location. +- "android.permission.ACCESS_FINE_LOCATION": permission to allow an application to access the device's fine location. +- "android.permission.READ_CONTACTS": permission to allow an application to read data from the device's contacts. +- "android.permission.WRITE_CONTACTS": permission to allow an application to write date to the device's contacts. +- "android.permission.GET_ACCOUNTS": permission to allow an application to access to the list of accounts in the Accounts Service. +- "android.permission.RECORD_AUDIO": permission to allow an application to allow an application to record audio. +- "android.permission.READ_EXTERNAL_STORAGE": permission to allow an application to read data from the device's external storage. +- "android.permission.WRITE_EXTERNAL_STORAGE": permission to allow an application to write data to the device's external storage. +- "android.permission.READ_PHONE_STATE": permission to allow an application to access phone state, including the phone number of the device, current cellular network information, the status of any ongoing calls, and a list of any PhoneAccounts registered on the device. +- "android.permission.READ_PHONE_NUMBERS": permission to allow an application to access the device's phone number(s). +- "android.permission.CALL_PHONE": permission to allow an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call. +- "android.permission.ANSWER_PHONE_CALLS": permission to allow an application to answer an incoming phone call. +- "android.permission.READ_CALL_LOG": permission to allow an application to read the user's call log. +- "android.permission.WRITE_CALL_LOG": permission to allow an application to write to the user's call log. +- "android.permission.ADD_VOICEMAIL": permission to allow an application to add voicemails into the system. +- "android.permission.USE_SIP": permission to allow an application to use SIP service. +- "android.permission.PROCESS_OUTGOING_CALLS": permission to allow an application to see the number being dialed during an outgoing call with the option to redirect the call to a different number or abort the call altogether. +- "android.permission.SEND_SMS": permission to allow an application to send SMS messages. +- "android.permission.RECEIVE_SMS": permission to allow an application to receive SMS messages +- "android.permission.READ_SMS": permission to allow an application to read SMS messages. +- "android.permission.RECEIVE_WAP_PUSH": permission to allow an application to receive WAP push messages. +- "android.permission.RECEIVE_MMS": permission to allow an application to receive MMS messages. +- "android.permission.BODY_SENSORS": permission to allow an application to access data from sensors that the user uses to measure what is happening inside his/her body, such as heart rate. + + + +Description: +Use the command to request permission for from the user. + +>*Note:* Permission names are case sensitive. diff --git a/docs/dictionary/function/androidHasPermission.lcdoc b/docs/dictionary/function/androidHasPermission.lcdoc new file mode 100644 index 00000000000..48219cce0e2 --- /dev/null +++ b/docs/dictionary/function/androidHasPermission.lcdoc @@ -0,0 +1,66 @@ +Name: androidHasPermission + +Type: function + +Syntax: androidHasPermission() + +Summary: +Returns if permission has been granted by the user. + +Introduced: 9.0.1 + +OS: android + +Platforms: mobile + +Example: +local tLocationPermissionGranted +put androidHasPermission("android.permission.ACCESS_FINE_LOCATION") into tLocationPermissionGranted +if not tLocationPermissionGranted then + androidRequestPermission "android.permission.ACCESS_FINE_LOCATION" +end if +if not tLocationPermissionGranted then + answer "This app is not permitted to access the device location. You can change this" && \ + "in the Settings app." +end if + + +Parameters: +permissionName (enum): +The name of the permission to request. + +- "android.permission.READ_CALENDAR": permission to allow an application to read the device's calendar. +- "android.permission.WRITE_CALENDAR": permission to allow an application to write to the device's calendar. +- "android.permission.CAMERA": permission to allow an application to access the device's camera. +- "android.permission.ACCESS_COARSE_LOCATION": permission to allow an application to access the device's coarse location. +- "android.permission.ACCESS_FINE_LOCATION": permission to allow an application to access the device's fine location. +- "android.permission.READ_CONTACTS": permission to allow an application to read data from the device's contacts. +- "android.permission.WRITE_CONTACTS": permission to allow an application to write date to the device's contacts. +- "android.permission.GET_ACCOUNTS": permission to allow an application to access to the list of accounts in the Accounts Service. +- "android.permission.RECORD_AUDIO": permission to allow an application to allow an application to record audio. +- "android.permission.READ_EXTERNAL_STORAGE": permission to allow an application to read data from the device's external storage. +- "android.permission.WRITE_EXTERNAL_STORAGE": permission to allow an application to write data to the device's external storage. +- "android.permission.READ_PHONE_STATE": permission to allow an application to access phone state, including the phone number of the device, current cellular network information, the status of any ongoing calls, and a list of any PhoneAccounts registered on the device. +- "android.permission.READ_PHONE_NUMBERS": permission to allow an application to access the device's phone number(s). +- "android.permission.CALL_PHONE": permission to allow an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call. +- "android.permission.ANSWER_PHONE_CALLS": permission to allow an application to answer an incoming phone call. +- "android.permission.READ_CALL_LOG": permission to allow an application to read the user's call log. +- "android.permission.WRITE_CALL_LOG": permission to allow an application to write to the user's call log. +- "android.permission.ADD_VOICEMAIL": permission to allow an application to add voicemails into the system. +- "android.permission.USE_SIP": permission to allow an application to use SIP service. +- "android.permission.PROCESS_OUTGOING_CALLS": permission to allow an application to see the number being dialed during an outgoing call with the option to redirect the call to a different number or abort the call altogether. +- "android.permission.SEND_SMS": permission to allow an application to send SMS messages. +- "android.permission.RECEIVE_SMS": permission to allow an application to receive SMS messages +- "android.permission.READ_SMS": permission to allow an application to read SMS messages. +- "android.permission.RECEIVE_WAP_PUSH": permission to allow an application to receive WAP push messages. +- "android.permission.RECEIVE_MMS": permission to allow an application to receive MMS messages. +- "android.permission.BODY_SENSORS": permission to allow an application to access data from sensors that the user uses to measure what is happening inside his/her body, such as heart rate. + +Returns(boolean): +True if permission has been granted, false otherwise. + + +Description: +Use the function to find out if permission has been granted by the user. + +>*Note:* Permission names are case sensitive. diff --git a/docs/dictionary/function/androidPermissionExists.lcdoc b/docs/dictionary/function/androidPermissionExists.lcdoc new file mode 100644 index 00000000000..46f1341b896 --- /dev/null +++ b/docs/dictionary/function/androidPermissionExists.lcdoc @@ -0,0 +1,33 @@ +Name: androidPermissionExists + +Type: function + +Syntax: androidPermissionExists() + +Summary: +Returns true if is a valid Android permission name + +Introduced: 9.0.1 + +OS: android + +Platforms: mobile + +Example: + +if not androidPermissionExists(pPermission) then + answer pPermission && "is not a valid permission name" +end if + + +Parameters: +permissionName (string): +The name of the permission to check. + +Returns(boolean): +True if is a valid Android permission name, false otherwise. + +Description: +Use the function to check if is a valid Android permission name. + +>*Note:* Permission names are case sensitive. diff --git a/docs/notes/feature-android_runtime_permissions.md b/docs/notes/feature-android_runtime_permissions.md new file mode 100644 index 00000000000..85d603f5ca8 --- /dev/null +++ b/docs/notes/feature-android_runtime_permissions.md @@ -0,0 +1,26 @@ +# Android 6.0 runtime permissions + +Android 6.0 (API 23) Marshmallow introduced a new permissions model +that lets apps request permissions from the user at runtime, rather +than prior to installation. Apps built with LC 9.0.1 do support this +new permissions model, and request permissions automatically when the +app actually requires the services or data protected by the services. + +For example, if the app calls `mobilePickPhoto "camera"`, a dialog will +be shown to the user asking for permission to access the device camera. + +If the user does not grant permission, the call will fail. Moreover, the +app can use the function `androidRequestPermission(permissionName)` to +check if the permission for `permissionName` has been granted. + +Notes: + +- You have to make sure that you check the required permissions for your +app in the standalone settings. +- Apps that run on devices running Android 6+ will work with the new +permissions model. +- Apps that run on older devices (less than Android 6) will continue to +work with the old permissions model. +- If the user does not grant a permission when the dialog appears for the +first time, they can change this preference from the Settings app. + diff --git a/engine/src/exec-misc.cpp b/engine/src/exec-misc.cpp index f5bc5eebf22..432396ca6e0 100644 --- a/engine/src/exec-misc.cpp +++ b/engine/src/exec-misc.cpp @@ -481,6 +481,31 @@ void MCMiscGetDoNotBackupFile(MCExecContext& ctxt, MCStringRef p_path, bool& r_n ctxt.Throw(); } +void MCMiscExecRequestPermission(MCExecContext& ctxt, MCStringRef p_permission, bool& r_granted) +{ + if (MCSystemRequestPermission(p_permission, r_granted)) + return; + + ctxt.Throw(); +} + +void MCMiscExecPermissionExists(MCExecContext& ctxt, MCStringRef p_permission, bool& r_exists) +{ + if (MCSystemPermissionExists(p_permission, r_exists)) + return; + + ctxt.Throw(); +} + +void MCMiscExecHasPermission(MCExecContext& ctxt, MCStringRef p_permission, bool& r_permission_granted) +{ + if (MCSystemHasPermission(p_permission, r_permission_granted)) + return; + + ctxt.Throw(); +} + + void MCMiscSetDoNotBackupFile(MCExecContext& ctxt, MCStringRef p_path, bool p_no_backup) { if (MCSystemFileSetDoNotBackup(p_path, p_no_backup)) diff --git a/engine/src/exec.h b/engine/src/exec.h index 81f22a8fe02..2644f12c558 100644 --- a/engine/src/exec.h +++ b/engine/src/exec.h @@ -4043,6 +4043,9 @@ void MCMiscExecLibUrlDownloadToFile(MCExecContext& ctxt, MCStringRef p_url, MCSt void MCMiscExecLibUrlSetSSLVerification(MCExecContext& ctxt, bool p_enabled); void MCMiscGetBuildInfo(MCExecContext& ctxt, MCStringRef p_key, MCStringRef& r_value); +void MCMiscExecRequestPermission(MCExecContext& ctxt, MCStringRef p_permission, bool& r_granted); +void MCMiscExecPermissionExists(MCExecContext& ctxt, MCStringRef p_permission, bool& r_exists); +void MCMiscExecHasPermission(MCExecContext& ctxt, MCStringRef p_permission, bool& r_permission_granted); void MCMiscExecEnableRemoteControl(MCExecContext& ctxt); void MCMiscExecDisableRemoteControl(MCExecContext& ctxt); diff --git a/engine/src/executionerrors.h b/engine/src/executionerrors.h index 90b4e821948..7a4f1dd565c 100644 --- a/engine/src/executionerrors.h +++ b/engine/src/executionerrors.h @@ -2774,6 +2774,9 @@ enum Exec_errors // {EE-0908} fontLanguage: bad font name EE_FONTLANGUAGE_BADFONTNAME, + // {EE-0909} android permission: bad permission name + EE_BAD_PERMISSION_NAME, + }; extern const char *MCexecutionerrors; diff --git a/engine/src/java/com/runrev/android/Engine.java b/engine/src/java/com/runrev/android/Engine.java index af01d4297d1..e88af53fd66 100644 --- a/engine/src/java/com/runrev/android/Engine.java +++ b/engine/src/java/com/runrev/android/Engine.java @@ -1151,6 +1151,11 @@ public void onListPickerDone(int p_index, boolean p_done) if (m_wake_on_event) doProcess(false); } + + public void onAskPermissionDone(boolean p_granted) + { + doAskPermissionDone(p_granted); + } //////////////////////////////////////////////////////////////////////////////// @@ -1859,23 +1864,121 @@ else if (t_caminfo.facing == CameraCompat.CameraInfo.CAMERA_FACING_FRONT) return new String(t_directions); } - public void showPhotoPicker(String p_source, int p_width, int p_height) - { - m_photo_width = p_width; - m_photo_height = p_height; - - if (p_source.equals("camera")) - showCamera(); - else if (p_source.equals("album")) - showLibrary(); - else if (p_source.equals("library")) - showLibrary(); - else - { - doPhotoPickerError("source not available"); - } - } - + public static final int PERMISSION_REQUEST_CODE = 1; + public boolean askPermission(String p_permission) + { + if (Build.VERSION.SDK_INT >= 23 && getContext().checkSelfPermission(p_permission) + != PackageManager.PERMISSION_GRANTED) + { + Activity t_activity = (LiveCodeActivity)getContext(); + t_activity.requestPermissions(new String[]{p_permission}, PERMISSION_REQUEST_CODE); + } + else + onAskPermissionDone(true); + return true; + } + + public boolean checkHasPermissionGranted(String p_permission) + { + if (Build.VERSION.SDK_INT >= 23) + { + return getContext().checkSelfPermission(p_permission) == PackageManager.PERMISSION_GRANTED; + } + return true; + } + + + public boolean checkPermissionExists(String p_permission) + { + if (Build.VERSION.SDK_INT >= 23) + { + List t_group_info_list = getAllPermissionGroups(); + if (t_group_info_list == null) + return false; + + ArrayList t_group_name_list = new ArrayList(); + for (PermissionGroupInfo t_group_info : t_group_info_list) + { + String t_group_name = t_group_info.name; + if (t_group_name != null) + t_group_name_list.add(t_group_name); + } + + for (String t_group_name : t_group_name_list) + { + ArrayList t_permission_name_list = getPermissionsForGroup(t_group_name); + + if (t_permission_name_list.contains(p_permission)) + return true; + } + return false; + } + return true; + } + + private List getAllPermissionGroups() + { + final PackageManager t_package_manager = getContext().getPackageManager(); + if (t_package_manager == null) + return null; + + return t_package_manager.getAllPermissionGroups(0); + } + + private ArrayList getPermissionsForGroup(String p_group_name) + { + final PackageManager t_package_manager = getContext().getPackageManager(); + final ArrayList t_permission_name_list = new ArrayList(); + + try + { + List t_permission_info_list = + t_package_manager.queryPermissionsByGroup(p_group_name, PackageManager.GET_META_DATA); + if (t_permission_info_list != null) + { + for (PermissionInfo t_permission_info : t_permission_info_list) + { + String t_permission_name = t_permission_info.name; + t_permission_name_list.add(t_permission_name); + } + } + } + catch (PackageManager.NameNotFoundException e) + { + // e.printStackTrace(); + Log.d(TAG, "permissions not found for group = " + p_group_name); + } + + Collections.sort(t_permission_name_list); + + return t_permission_name_list; + } + + + public void showPhotoPicker(String p_source, int p_width, int p_height) + { + m_photo_width = p_width; + m_photo_height = p_height; + + if (p_source.equals("camera")) + showCamera(); + else if (p_source.equals("album")) + showLibrary(); + else if (p_source.equals("library")) + showLibrary(); + else + { + doPhotoPickerError("source not available"); + } + + } + + // sent by the callback + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) + { + onAskPermissionDone(grantResults[0] == PackageManager.PERMISSION_GRANTED); + } + public void showCamera() { // 2012-01-18-IM temp file may be created in app cache folder, in which case @@ -3737,6 +3840,7 @@ public static native void doHeadingChanged(double p_heading, double p_magnetic_h public static native void doDatePickerDone(int year, int month, int day, boolean done); public static native void doTimePickerDone(int hour, int minute, boolean done); public static native void doListPickerDone(int index, boolean done); + public static native void doAskPermissionDone(boolean granted); public static native void doMovieStopped(); public static native void doMovieTouched(); diff --git a/engine/src/java/com/runrev/android/LiveCodeActivity.java b/engine/src/java/com/runrev/android/LiveCodeActivity.java index f0ba460584a..49279915e72 100644 --- a/engine/src/java/com/runrev/android/LiveCodeActivity.java +++ b/engine/src/java/com/runrev/android/LiveCodeActivity.java @@ -23,6 +23,7 @@ import android.content.res.*; import android.widget.*; import android.util.*; +import android.content.pm.PackageManager; // This is the main activity exported by the application. This is // split into two parts, a customizable sub-class that gets dynamically @@ -198,4 +199,13 @@ protected void onActivityResult (int requestCode, int resultCode, Intent data) { s_main_view.onActivityResult(requestCode, resultCode, data); } + + // Callback sent when the app requests permissions on runtime (Android API 23+) + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) + { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + s_main_view.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } diff --git a/engine/src/java/com/runrev/android/SensorModule.java b/engine/src/java/com/runrev/android/SensorModule.java index 29fabd62746..58b0f77a68c 100644 --- a/engine/src/java/com/runrev/android/SensorModule.java +++ b/engine/src/java/com/runrev/android/SensorModule.java @@ -19,7 +19,6 @@ import android.content.*; import android.os.*; import android.util.*; - import java.util.*; import android.hardware.*; diff --git a/engine/src/mblandroidcamera.cpp b/engine/src/mblandroidcamera.cpp index 8b1e8421b6e..74a36c0b76b 100644 --- a/engine/src/mblandroidcamera.cpp +++ b/engine/src/mblandroidcamera.cpp @@ -80,9 +80,13 @@ MCCamerasFeaturesType MCSystemGetAllCameraFeatures() return t_features; } +extern bool MCAndroidCheckRuntimePermission(MCStringRef p_permission); bool MCAndroidPickPhoto(const char *p_source, int32_t p_max_width, int32_t p_max_height) { - MCAndroidEngineCall("showPhotoPicker", "vsii", nil, p_source, p_max_width, p_max_height); + if (!MCAndroidCheckRuntimePermission(MCSTR("android.permission.CAMERA"))) + return false; + + MCAndroidEngineCall("showPhotoPicker", "vsii", nil, p_source, p_max_width, p_max_height); // SN-2014-09-03: [[ Bug 13329 ]] MCAndroidPickPhoto's return value is ignored in 6.x, // but not in 7.0 - whence the failure in mobilePickPhoto return true; diff --git a/engine/src/mblandroidcontact.cpp b/engine/src/mblandroidcontact.cpp index cae48765ea6..70eb74cd9a9 100644 --- a/engine/src/mblandroidcontact.cpp +++ b/engine/src/mblandroidcontact.cpp @@ -58,9 +58,11 @@ static MCAndroidContactStatus s_contact_status = kMCAndroidContactWaiting; static int32_t s_contact_selected = 0; static MCString s_contacts_selected = ""; +extern bool MCAndroidCheckRuntimePermission(MCStringRef p_permission); bool MCSystemPickContact(int32_t& r_result) { MCLog("MCSystemPickContact"); + MCAndroidEngineRemoteCall("pickContact", "i", &r_result); s_contact_status = kMCAndroidContactWaiting; while (s_contact_status == kMCAndroidContactWaiting) @@ -156,6 +158,9 @@ bool MCSystemUpdateContact(MCArrayRef p_contact, MCStringRef p_title, MCStringRef p_message, MCStringRef p_alternate_name, int32_t &r_result) { + if (!(MCAndroidCheckRuntimePermission(MCSTR("android.permission.WRITE_CONTACTS")))) + return false; + MCLog("MCSystemUpdateContact"); bool t_success = true; @@ -192,6 +197,10 @@ void MCAndroidUpdateContactCanceled(int32_t p_contact_id) bool MCSystemGetContactData(int32_t p_contact_id, MCArrayRef &r_contact_data) { MCLog("MCSystemGetContactData: %d", p_contact_id); + + if (!(MCAndroidCheckRuntimePermission(MCSTR("android.permission.READ_CONTACTS")))) + return false; + jobject t_jmap = nil; MCAndroidEngineRemoteCall("getContactData", "mi", &t_jmap, p_contact_id); MCLog("contact map: %p", t_jmap); @@ -209,6 +218,10 @@ bool MCSystemGetContactData(int32_t p_contact_id, MCArrayRef &r_contact_data) bool MCSystemRemoveContact(int32_t p_contact_id) { MCLog("MCSystemRemoveContact: %d", p_contact_id); + + if (!(MCAndroidCheckRuntimePermission(MCSTR("android.permission.WRITE_CONTACTS")))) + return false; + MCAndroidEngineRemoteCall("removeContact", "vi", nil, p_contact_id); return true; } @@ -216,6 +229,9 @@ bool MCSystemRemoveContact(int32_t p_contact_id) bool MCSystemAddContact(MCArrayRef p_contact, int32_t &r_result) { MCLog("MCSystemAddContact"); + + if (!(MCAndroidCheckRuntimePermission(MCSTR("android.permission.WRITE_CONTACTS")))) + return false; bool t_success = true; jobject t_map = nil; @@ -229,8 +245,12 @@ bool MCSystemAddContact(MCArrayRef p_contact, int32_t &r_result) return false; } + bool MCSystemFindContact(MCStringRef p_contact_name, MCStringRef& r_result) { + if (!(MCAndroidCheckRuntimePermission(MCSTR("android.permission.READ_CONTACTS")))) + return false; + MCAndroidEngineRemoteCall("findContact", "vx", nil, p_contact_name); return MCStringCreateWithCString(s_contacts_selected . getstring(), r_result); } diff --git a/engine/src/mblandroiddc.cpp b/engine/src/mblandroiddc.cpp index 4ef4f42b4d9..7d8801ae5f9 100644 --- a/engine/src/mblandroiddc.cpp +++ b/engine/src/mblandroiddc.cpp @@ -2817,6 +2817,46 @@ void MCAndroidDisableOpenGLMode(void) //////////////////////////////////////////////////////////////////////////////// +static bool s_in_permission_dialog = false; +static bool s_permission_granted = false; +bool MCAndroidCheckRuntimePermission(MCStringRef p_permission) +{ + bool t_result; + s_in_permission_dialog = true; + MCAndroidEngineRemoteCall("askPermission", "bx", &t_result, p_permission); + + while (s_in_permission_dialog) + MCscreen -> wait(60.0, False, True); + + return s_permission_granted; +} + +bool MCAndroidCheckPermissionExists(MCStringRef p_permission) +{ + bool t_result; + MCAndroidEngineRemoteCall("checkPermissionExists", "bx", &t_result, p_permission); + + return t_result; +} + +bool MCAndroidHasPermission(MCStringRef p_permission) +{ + bool t_result; + MCAndroidEngineRemoteCall("checkHasPermissionGranted", "bx", &t_result, p_permission); + + return t_result; +} + +extern "C" JNIEXPORT void JNICALL Java_com_runrev_android_Engine_doAskPermissionDone(JNIEnv *env, jobject object, bool granted) __attribute__((visibility("default"))); +JNIEXPORT void JNICALL Java_com_runrev_android_Engine_doAskPermissionDone(JNIEnv *env, jobject object, bool granted) +{ + s_in_permission_dialog = false; + s_permission_granted = granted; + MCAndroidBreakWait(); +} + +///////////////////////////////////////////////////////////////////////////////// + bool android_run_on_main_thread(void *p_callback, void *p_callback_state, int p_options); typedef void (*MCExternalThreadOptionalCallback)(void *state); diff --git a/engine/src/mblandroidfs.cpp b/engine/src/mblandroidfs.cpp index 57f3ca7e306..1b18e3ffb90 100644 --- a/engine/src/mblandroidfs.cpp +++ b/engine/src/mblandroidfs.cpp @@ -415,23 +415,32 @@ bool MCAndroidSystem::GetTemporaryFileName(MCStringRef &r_tmp_name) return MCStringCreateWithCString(tmpnam(NULL), r_tmp_name); } +extern bool MCAndroidCheckRuntimePermission(MCStringRef p_permission); Boolean MCAndroidSystem::GetStandardFolder(MCNameRef p_folder, MCStringRef &r_folder) { + // accessing "external documents", "external cache" etc requires Write External Storage permission + if (MCStringBeginsWith(MCNameGetString(p_folder), MCSTR("external"), kMCStringOptionCompareCaseless) && \ + !MCAndroidCheckRuntimePermission(MCSTR("android.permission.WRITE_EXTERNAL_STORAGE"))) + { + r_folder = MCValueRetain(kMCEmptyString); + return False; + } + // SN-2015-04-16: [[ Bug 14295 ]] The resources folder on Mobile is the same // as the engine folder. if (MCNameIsEqualToCaseless(p_folder, MCN_engine) - || MCNameIsEqualToCaseless(p_folder, MCN_resources)) + || MCNameIsEqualToCaseless(p_folder, MCN_resources)) { MCLog("GetStandardFolder(\"%@\") -> \"%@\"", MCNameGetString(p_folder), MCcmd); - return MCStringCopy(MCcmd, r_folder); + return MCStringCopy(MCcmd, r_folder); } - MCAutoStringRef t_stdfolder; + MCAutoStringRef t_stdfolder; MCAndroidEngineCall("getSpecialFolderPath", "xx", &(&t_stdfolder), MCNameGetString(p_folder)); MCLog("GetStandardFolder(\"%@\") -> \"%@\"", p_folder, *t_stdfolder == nil ? kMCEmptyString : *t_stdfolder); - r_folder = MCValueRetain(*t_stdfolder == nil ? kMCEmptyString : *t_stdfolder); + r_folder = MCValueRetain(*t_stdfolder == nil ? kMCEmptyString : *t_stdfolder); return True; } diff --git a/engine/src/mblandroidmisc.cpp b/engine/src/mblandroidmisc.cpp index 5bc82aef127..477ea6fa94b 100644 --- a/engine/src/mblandroidmisc.cpp +++ b/engine/src/mblandroidmisc.cpp @@ -802,6 +802,26 @@ bool MCSystemBuildInfo(MCStringRef p_key, MCStringRef& r_value) //////////////////////////////////////////////////////////////////////////////// +bool MCSystemRequestPermission(MCStringRef p_permission, bool& r_granted) +{ + r_granted = MCAndroidCheckRuntimePermission(p_permission); + return true; +} + +bool MCSystemPermissionExists(MCStringRef p_permission, bool& r_exists) +{ + r_exists = MCAndroidCheckPermissionExists(p_permission); + return true; +} + +bool MCSystemHasPermission(MCStringRef p_permission, bool& r_permission_granted) +{ + r_permission_granted = MCAndroidHasPermission(p_permission); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// diff --git a/engine/src/mblandroidsensor.cpp b/engine/src/mblandroidsensor.cpp index 4851f473e2d..3f1182c000b 100644 --- a/engine/src/mblandroidsensor.cpp +++ b/engine/src/mblandroidsensor.cpp @@ -58,9 +58,18 @@ void MCSystemSensorFinalize(void) } //////////////////////////////////////////////////////////////////////////////// +extern bool MCAndroidCheckRuntimePermission(MCStringRef p_permission); bool MCSystemGetSensorAvailable(MCSensorType p_sensor, bool& r_available) -{ +{ + if (p_sensor == kMCSensorTypeLocation) + { + bool t_success = MCAndroidCheckRuntimePermission(MCSTR("android.permission.ACCESS_COARSE_LOCATION")) && \ + MCAndroidCheckRuntimePermission(MCSTR("android.permission.ACCESS_FINE_LOCATION")); + if (!t_success) + return false; + } + MCAndroidEngineRemoteCall("isSensorAvailable", "bi", &r_available, (int32_t)p_sensor); return true; } diff --git a/engine/src/mblandroidutil.h b/engine/src/mblandroidutil.h index 42fd778739e..108877c0a7f 100644 --- a/engine/src/mblandroidutil.h +++ b/engine/src/mblandroidutil.h @@ -63,6 +63,9 @@ void MCAndroidObjectCall(jobject p_object, const char *p_method, const char *p_s void MCAndroidObjectRemoteCall(jobject p_object, const char *p_method, const char *p_signature, void *p_return_value, ...); bool MCAndroidGetBuildInfo(MCStringRef t_key, MCStringRef &r_value); +bool MCAndroidCheckRuntimePermission(MCStringRef p_permission); +bool MCAndroidCheckPermissionExists(MCStringRef p_permission); +bool MCAndroidHasPermission(MCStringRef p_permission); typedef struct _android_device_configuration { diff --git a/engine/src/mblhandlers.cpp b/engine/src/mblhandlers.cpp index ab260dc5817..2a5f0b48422 100644 --- a/engine/src/mblhandlers.cpp +++ b/engine/src/mblhandlers.cpp @@ -3377,6 +3377,99 @@ Exec_stat MCHandleBuildInfo(void *context, MCParameter *p_parameters) return ES_ERROR; } +/////////////////// Android 6.0 runtime permissions ///////////////////////// +Exec_stat MCHandleRequestPermission(void *context, MCParameter *p_parameters) +{ + MCExecContext ctxt(nil, nil, nil); + + MCAutoStringRef t_permission; + bool t_success, t_granted; + + t_success = MCParseParameters(p_parameters, "x", &(&t_permission)); + + bool t_permission_exists; + MCMiscExecPermissionExists(ctxt, *t_permission, t_permission_exists); + + if (!t_permission_exists) + { + ctxt.LegacyThrow(EE_BAD_PERMISSION_NAME); + t_success = false; + } + + if (t_success) + MCMiscExecRequestPermission(ctxt, *t_permission, t_granted); + + Exec_stat t_stat; + if (!ctxt . HasError()) + t_stat = ES_NORMAL; + else + t_stat = ES_ERROR; + + ctxt.SetTheResultToEmpty(); + return t_stat; +} + +Exec_stat MCHandlePermissionExists(void *context, MCParameter *p_parameters) +{ + MCExecContext ctxt(nil, nil, nil); + + MCAutoStringRef t_permission; + bool t_success, t_exists; + + t_success = MCParseParameters(p_parameters, "x", &(&t_permission)); + + if (t_success) + MCMiscExecPermissionExists(ctxt, *t_permission, t_exists); + + if (!ctxt . HasError()) + { + if (t_exists) + ctxt.SetTheResultToValue(kMCTrueString); + else + ctxt.SetTheResultToValue(kMCFalseString); + + return ES_NORMAL; + } + + ctxt.SetTheResultToEmpty(); + return ES_ERROR; +} + +Exec_stat MCHandleHasPermission(void *context, MCParameter *p_parameters) +{ + MCExecContext ctxt(nil, nil, nil); + + MCAutoStringRef t_permission; + bool t_success, t_permission_granted; + + t_success = MCParseParameters(p_parameters, "x", &(&t_permission)); + + bool t_permission_exists; + MCMiscExecPermissionExists(ctxt, *t_permission, t_permission_exists); + + if (!t_permission_exists) + { + ctxt.LegacyThrow(EE_BAD_PERMISSION_NAME); + t_success = false; + } + + if (t_success) + MCMiscExecHasPermission(ctxt, *t_permission, t_permission_granted); + + if (!ctxt . HasError()) + { + if (t_permission_granted) + ctxt.SetTheResultToValue(kMCTrueString); + else + ctxt.SetTheResultToValue(kMCFalseString); + + return ES_NORMAL; + } + + ctxt.SetTheResultToEmpty(); + return ES_ERROR; +} + ////////////////////////////////////////////////////////////////////////////////////// static MCMediaType MCMediaTypeFromString(MCStringRef p_string) @@ -4529,6 +4622,9 @@ static const MCPlatformMessageSpec s_platform_messages[] = {false, "mobileLocationAuthorizationStatus", MCHandleLocationAuthorizationStatus, nil}, {false, "mobileBuildInfo", MCHandleBuildInfo, nil}, + {false, "androidRequestPermission", MCHandleRequestPermission, nil}, + {false, "androidPermissionExists", MCHandlePermissionExists, nil}, + {false, "androidHasPermission", MCHandleHasPermission, nil}, {false, "mobileCanMakePurchase", MCHandleCanMakePurchase, nil}, {false, "mobileEnablePurchaseUpdates", MCHandleEnablePurchaseUpdates, nil}, diff --git a/engine/src/mbliphonemisc.mm b/engine/src/mbliphonemisc.mm index 3b497d95ac1..2fbcceb7fee 100644 --- a/engine/src/mbliphonemisc.mm +++ b/engine/src/mbliphonemisc.mm @@ -26,3 +26,25 @@ int32_t MCCustomPrinterComputeFontSize(void *font) { return CTFontGetSize((CTFontRef)font); } + +//////////////////////////////////////////////////////////////////////////////// + +bool MCSystemRequestPermission(MCStringRef p_permission, bool& r_granted) +{ + // Not implemented + return false; +} + +bool MCSystemPermissionExists(MCStringRef p_permission, bool& r_exists) +{ + // Not implemented + return false; +} + +bool MCSystemHasPermission(MCStringRef p_permission, bool& r_permission_granted) +{ + // Not implemented + return false; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/engine/src/mblsyntax.h b/engine/src/mblsyntax.h index 9ccfaa68249..7f653d92584 100644 --- a/engine/src/mblsyntax.h +++ b/engine/src/mblsyntax.h @@ -466,6 +466,9 @@ bool MCSystemFileSetDataProtection(MCStringRef p_path, MCStringRef p_protection_ bool MCSystemFileGetDataProtection(MCStringRef p_path, MCStringRef& r_protection_string); bool MCSystemBuildInfo(MCStringRef p_key, MCStringRef& r_value); +bool MCSystemRequestPermission(MCStringRef p_permission, bool& r_granted); +bool MCSystemPermissionExists(MCStringRef p_permission, bool& r_exists); +bool MCSystemHasPermission(MCStringRef p_permission, bool& r_permission_granted); bool MCSystemEnableRemoteControl(); bool MCSystemDisableRemoteControl();