From 5264d8696a397db869c2f368a02d90e8a65714e0 Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Thu, 26 Jul 2018 12:15:57 +0100 Subject: [PATCH 01/13] [Android API 23+][mobilePickPhoto] Request for camera permissions on runtime --- .../src/java/com/runrev/android/Engine.java | 68 +++++++++++++++---- .../com/runrev/android/LiveCodeActivity.java | 10 +++ 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/engine/src/java/com/runrev/android/Engine.java b/engine/src/java/com/runrev/android/Engine.java index af01d4297d1..49728804e9b 100644 --- a/engine/src/java/com/runrev/android/Engine.java +++ b/engine/src/java/com/runrev/android/Engine.java @@ -53,6 +53,8 @@ import android.provider.MediaStore.Images.Media; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.Manifest; +import java.lang.Object; import java.net.*; import java.io.*; @@ -1859,22 +1861,62 @@ else if (t_caminfo.facing == CameraCompat.CameraInfo.CAMERA_FACING_FRONT) return new String(t_directions); } + private String m_source; + public static final int CAMERA_PERMISSION_REQUEST_CODE = 1; 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"); - } + m_photo_width = p_width; + m_photo_height = p_height; + m_source = p_source; + + // Camera permission not granted, so ask for it + if (Build.VERSION.SDK_INT >= 23 && getContext().checkSelfPermission(Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) + { + Activity t_activity = (LiveCodeActivity)getContext(); + t_activity.requestPermissions(new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE); + } + // Camera permission already granted, or we are in a device running Android API < 23 + else + { + onCameraPermissionGranted(m_source); + } } + + private void onCameraPermissionGranted(String p_source) + { + 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"); + } + } + + private void onCameraRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) + { + if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) + { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) + { + onCameraPermissionGranted(m_source); + } + else + { + doPhotoPickerError("Permission denied. You can change this in the Settings app"); + } + } + } + + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) + { + onCameraRequestPermissionResult(requestCode, permissions, grantResults); + } + public void showCamera() { 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); + } + } From 9a8c15bfe0d774b9bb6d79e53ef197ebe3d893f1 Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Thu, 26 Jul 2018 22:23:11 +0100 Subject: [PATCH 02/13] [Android API 23+] Request for location permissions on runtime --- .../src/java/com/runrev/android/Engine.java | 20 +++++++++++++-- .../java/com/runrev/android/SensorModule.java | 25 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/engine/src/java/com/runrev/android/Engine.java b/engine/src/java/com/runrev/android/Engine.java index 49728804e9b..3629eab7192 100644 --- a/engine/src/java/com/runrev/android/Engine.java +++ b/engine/src/java/com/runrev/android/Engine.java @@ -1863,6 +1863,7 @@ else if (t_caminfo.facing == CameraCompat.CameraInfo.CAMERA_FACING_FRONT) private String m_source; public static final int CAMERA_PERMISSION_REQUEST_CODE = 1; + public static final int LOCATION_PERMISSION_REQUEST_CODE = 3; public void showPhotoPicker(String p_source, int p_width, int p_height) { m_photo_width = p_width; @@ -1912,12 +1913,27 @@ private void onCameraRequestPermissionResult(int requestCode, String[] permissio } } + private void onLocationRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) + { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) + { + m_sensor_module.createLocationTracker(); + } + else + { + Log.e(TAG,"Permission denied. You can change this in the Settings app"); + } + } + + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - onCameraRequestPermissionResult(requestCode, permissions, grantResults); + if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) + onCameraRequestPermissionResult(requestCode, permissions, grantResults); + else if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) + onLocationRequestPermissionResult(requestCode, permissions, grantResults); } - public void showCamera() { // 2012-01-18-IM temp file may be created in app cache folder, in which case diff --git a/engine/src/java/com/runrev/android/SensorModule.java b/engine/src/java/com/runrev/android/SensorModule.java index 29fabd62746..40ef8f493a5 100644 --- a/engine/src/java/com/runrev/android/SensorModule.java +++ b/engine/src/java/com/runrev/android/SensorModule.java @@ -19,6 +19,9 @@ import android.content.*; import android.os.*; import android.util.*; +import android.Manifest; +import java.lang.Object.*; +import android.app.Activity; import java.util.*; @@ -44,6 +47,8 @@ abstract class Tracker public static final int COARSE_TRACKING = 1; public static final int FINE_TRACKING = 2; + public static final int LOCATION_PERMISSION_REQUEST_CODE = 3; + protected boolean m_paused; protected int m_tracking_requested; protected int m_tracking_internal; @@ -230,6 +235,21 @@ class LocationTracker extends Tracker LocationListener m_gps_location_listener; public LocationTracker() + { + if (Build.VERSION.SDK_INT >= 23 && ((m_engine.getContext().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) + != m_engine.getContext().getPackageManager().PERMISSION_GRANTED || m_engine.getContext().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) + != m_engine.getContext().getPackageManager().PERMISSION_GRANTED))) + { + Activity t_activity = (LiveCodeActivity)m_engine.getContext(); + t_activity.requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); + } + else + { + createLocationTracker(); + } + } + + private void createLocationTracker() { // Get the number of seconds since the device was booted double t_seconds_since_boot; @@ -689,4 +709,9 @@ public void finish() m_accel_tracker.stopTracking(); m_location_tracker.stopTracking(); } + + public void createLocationTracker() + { + m_location_tracker.createLocationTracker(); + } } From 0b857f1baa6ce695a2abf5f1cd5ce11966a4aa7a Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Thu, 26 Jul 2018 22:27:07 +0100 Subject: [PATCH 03/13] Removed unnecessary comparison --- engine/src/java/com/runrev/android/Engine.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/engine/src/java/com/runrev/android/Engine.java b/engine/src/java/com/runrev/android/Engine.java index 3629eab7192..3c278ad3fb6 100644 --- a/engine/src/java/com/runrev/android/Engine.java +++ b/engine/src/java/com/runrev/android/Engine.java @@ -1900,16 +1900,13 @@ else if (p_source.equals("library")) private void onCameraRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) { - if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) - { - onCameraPermissionGranted(m_source); - } - else - { - doPhotoPickerError("Permission denied. You can change this in the Settings app"); - } + onCameraPermissionGranted(m_source); + } + else + { + doPhotoPickerError("Permission denied. You can change this in the Settings app"); } } @@ -1925,7 +1922,6 @@ private void onLocationRequestPermissionResult(int requestCode, String[] permiss } } - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) From 3224c7d09c47a1dd8c08951301e023a89e002d07 Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Fri, 27 Jul 2018 18:21:56 +0100 Subject: [PATCH 04/13] [Runtime permissions] Created API that checks for permission --- .../src/java/com/runrev/android/Engine.java | 91 +++++++++++-------- .../java/com/runrev/android/SensorModule.java | 20 ---- engine/src/mblandroidcamera.cpp | 6 +- engine/src/mblandroidcontact.cpp | 20 ++++ engine/src/mblandroiddc.cpp | 29 ++++++ engine/src/mblandroidfs.cpp | 8 ++ engine/src/mblandroidsensor.cpp | 10 +- 7 files changed, 123 insertions(+), 61 deletions(-) diff --git a/engine/src/java/com/runrev/android/Engine.java b/engine/src/java/com/runrev/android/Engine.java index 3c278ad3fb6..5b779ac7f75 100644 --- a/engine/src/java/com/runrev/android/Engine.java +++ b/engine/src/java/com/runrev/android/Engine.java @@ -1153,6 +1153,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); + } //////////////////////////////////////////////////////////////////////////////// @@ -1861,31 +1866,49 @@ else if (t_caminfo.facing == CameraCompat.CameraInfo.CAMERA_FACING_FRONT) return new String(t_directions); } - private String m_source; public static final int CAMERA_PERMISSION_REQUEST_CODE = 1; - public static final int LOCATION_PERMISSION_REQUEST_CODE = 3; - public void showPhotoPicker(String p_source, int p_width, int p_height) - { - m_photo_width = p_width; - m_photo_height = p_height; - m_source = p_source; - - // Camera permission not granted, so ask for it - if (Build.VERSION.SDK_INT >= 23 && getContext().checkSelfPermission(Manifest.permission.CAMERA) + public static final int COARSE_LOCATION_PERMISSION_REQUEST_CODE = 2; + public static final int FINE_LOCATION_PERMISSION_REQUEST_CODE = 3; + public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 4; + public static final int WRITE_CONTACTS_PERMISSION_REQUEST_CODE = 5; + public static final int WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6; + + 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[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE); + t_activity.requestPermissions(new String[]{p_permission}, mapPermissionToRequestCode(p_permission)); } - // Camera permission already granted, or we are in a device running Android API < 23 else - { - onCameraPermissionGranted(m_source); - } - } + onAskPermissionDone(true); + return true; + } - private void onCameraPermissionGranted(String p_source) + private int mapPermissionToRequestCode(String p_permission) + { + if (p_permission.equals("android.permission.CAMERA")) + return CAMERA_PERMISSION_REQUEST_CODE; + else if (p_permission.equals("android.permission.ACCESS_COARSE_LOCATION")) + return COARSE_LOCATION_PERMISSION_REQUEST_CODE; + else if (p_permission.equals("android.permission.ACCESS_FINE_LOCATION")) + return FINE_LOCATION_PERMISSION_REQUEST_CODE; + else if (p_permission.equals("android.permission.READ_CONTACTS")) + return READ_CONTACTS_PERMISSION_REQUEST_CODE; + else if (p_permission.equals("android.permission.WRITE_CONTACTS")) + return WRITE_CONTACTS_PERMISSION_REQUEST_CODE; + else if (p_permission.equals("android.permission.WRITE_EXTERNAL_STORAGE")) + return WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE; + else + return -1; + } + + 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")) @@ -1896,38 +1919,27 @@ else if (p_source.equals("library")) { doPhotoPickerError("source not available"); } + } - private void onCameraRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) + private void onCameraPermissionGranted(String p_source) { - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) - { - onCameraPermissionGranted(m_source); - } + if (p_source.equals("camera")) + showCamera(); + else if (p_source.equals("album")) + showLibrary(); + else if (p_source.equals("library")) + showLibrary(); else { - doPhotoPickerError("Permission denied. You can change this in the Settings app"); + doPhotoPickerError("source not available"); } } - private void onLocationRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) - { - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) - { - m_sensor_module.createLocationTracker(); - } - else - { - Log.e(TAG,"Permission denied. You can change this in the Settings app"); - } - } - + // sent by the callback public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) - onCameraRequestPermissionResult(requestCode, permissions, grantResults); - else if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) - onLocationRequestPermissionResult(requestCode, permissions, grantResults); + onAskPermissionDone(grantResults[0] == PackageManager.PERMISSION_GRANTED); } public void showCamera() @@ -3791,6 +3803,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/SensorModule.java b/engine/src/java/com/runrev/android/SensorModule.java index 40ef8f493a5..fc6ffb82cbc 100644 --- a/engine/src/java/com/runrev/android/SensorModule.java +++ b/engine/src/java/com/runrev/android/SensorModule.java @@ -235,21 +235,6 @@ class LocationTracker extends Tracker LocationListener m_gps_location_listener; public LocationTracker() - { - if (Build.VERSION.SDK_INT >= 23 && ((m_engine.getContext().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) - != m_engine.getContext().getPackageManager().PERMISSION_GRANTED || m_engine.getContext().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) - != m_engine.getContext().getPackageManager().PERMISSION_GRANTED))) - { - Activity t_activity = (LiveCodeActivity)m_engine.getContext(); - t_activity.requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); - } - else - { - createLocationTracker(); - } - } - - private void createLocationTracker() { // Get the number of seconds since the device was booted double t_seconds_since_boot; @@ -709,9 +694,4 @@ public void finish() m_accel_tracker.stopTracking(); m_location_tracker.stopTracking(); } - - public void createLocationTracker() - { - m_location_tracker.createLocationTracker(); - } } 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..377ff388d83 100644 --- a/engine/src/mblandroiddc.cpp +++ b/engine/src/mblandroiddc.cpp @@ -2817,6 +2817,35 @@ 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, True, True); + + return s_permission_granted; +} + +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..e451d356859 100644 --- a/engine/src/mblandroidfs.cpp +++ b/engine/src/mblandroidfs.cpp @@ -415,8 +415,16 @@ 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) diff --git a/engine/src/mblandroidsensor.cpp b/engine/src/mblandroidsensor.cpp index 4851f473e2d..be8f0acf323 100644 --- a/engine/src/mblandroidsensor.cpp +++ b/engine/src/mblandroidsensor.cpp @@ -58,9 +58,17 @@ 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; } From 912d98e5965412fffd28618d78b691242ae0b7bf Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Fri, 27 Jul 2018 18:30:19 +0100 Subject: [PATCH 05/13] Removed unused code --- engine/src/java/com/runrev/android/Engine.java | 16 ---------------- .../java/com/runrev/android/SensorModule.java | 6 ------ engine/src/mblandroiddc.cpp | 5 ----- 3 files changed, 27 deletions(-) diff --git a/engine/src/java/com/runrev/android/Engine.java b/engine/src/java/com/runrev/android/Engine.java index 5b779ac7f75..ca6596d80c1 100644 --- a/engine/src/java/com/runrev/android/Engine.java +++ b/engine/src/java/com/runrev/android/Engine.java @@ -53,8 +53,6 @@ import android.provider.MediaStore.Images.Media; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.Manifest; -import java.lang.Object; import java.net.*; import java.io.*; @@ -1922,20 +1920,6 @@ else if (p_source.equals("library")) } - private void onCameraPermissionGranted(String p_source) - { - 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) { diff --git a/engine/src/java/com/runrev/android/SensorModule.java b/engine/src/java/com/runrev/android/SensorModule.java index fc6ffb82cbc..58b0f77a68c 100644 --- a/engine/src/java/com/runrev/android/SensorModule.java +++ b/engine/src/java/com/runrev/android/SensorModule.java @@ -19,10 +19,6 @@ import android.content.*; import android.os.*; import android.util.*; -import android.Manifest; -import java.lang.Object.*; -import android.app.Activity; - import java.util.*; import android.hardware.*; @@ -47,8 +43,6 @@ abstract class Tracker public static final int COARSE_TRACKING = 1; public static final int FINE_TRACKING = 2; - public static final int LOCATION_PERMISSION_REQUEST_CODE = 3; - protected boolean m_paused; protected int m_tracking_requested; protected int m_tracking_internal; diff --git a/engine/src/mblandroiddc.cpp b/engine/src/mblandroiddc.cpp index 377ff388d83..f5ff3cd2181 100644 --- a/engine/src/mblandroiddc.cpp +++ b/engine/src/mblandroiddc.cpp @@ -2839,11 +2839,6 @@ JNIEXPORT void JNICALL Java_com_runrev_android_Engine_doAskPermissionDone(JNIEnv MCAndroidBreakWait(); } - - - - - ///////////////////////////////////////////////////////////////////////////////// bool android_run_on_main_thread(void *p_callback, void *p_callback_state, int p_options); From 0509d62f90b120bd1181619a10d554177a023480 Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Sat, 28 Jul 2018 07:55:06 +0100 Subject: [PATCH 06/13] Used blocking wait --- .../src/java/com/runrev/android/Engine.java | 28 ++----------------- engine/src/mblandroiddc.cpp | 2 +- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/engine/src/java/com/runrev/android/Engine.java b/engine/src/java/com/runrev/android/Engine.java index ca6596d80c1..280f3399568 100644 --- a/engine/src/java/com/runrev/android/Engine.java +++ b/engine/src/java/com/runrev/android/Engine.java @@ -1864,44 +1864,20 @@ else if (t_caminfo.facing == CameraCompat.CameraInfo.CAMERA_FACING_FRONT) return new String(t_directions); } - public static final int CAMERA_PERMISSION_REQUEST_CODE = 1; - public static final int COARSE_LOCATION_PERMISSION_REQUEST_CODE = 2; - public static final int FINE_LOCATION_PERMISSION_REQUEST_CODE = 3; - public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 4; - public static final int WRITE_CONTACTS_PERMISSION_REQUEST_CODE = 5; - public static final int WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6; - + 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}, mapPermissionToRequestCode(p_permission)); + t_activity.requestPermissions(new String[]{p_permission}, PERMISSION_REQUEST_CODE); } else onAskPermissionDone(true); return true; } - private int mapPermissionToRequestCode(String p_permission) - { - if (p_permission.equals("android.permission.CAMERA")) - return CAMERA_PERMISSION_REQUEST_CODE; - else if (p_permission.equals("android.permission.ACCESS_COARSE_LOCATION")) - return COARSE_LOCATION_PERMISSION_REQUEST_CODE; - else if (p_permission.equals("android.permission.ACCESS_FINE_LOCATION")) - return FINE_LOCATION_PERMISSION_REQUEST_CODE; - else if (p_permission.equals("android.permission.READ_CONTACTS")) - return READ_CONTACTS_PERMISSION_REQUEST_CODE; - else if (p_permission.equals("android.permission.WRITE_CONTACTS")) - return WRITE_CONTACTS_PERMISSION_REQUEST_CODE; - else if (p_permission.equals("android.permission.WRITE_EXTERNAL_STORAGE")) - return WRITE_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE; - else - return -1; - } - public void showPhotoPicker(String p_source, int p_width, int p_height) { m_photo_width = p_width; diff --git a/engine/src/mblandroiddc.cpp b/engine/src/mblandroiddc.cpp index f5ff3cd2181..81a38421a7a 100644 --- a/engine/src/mblandroiddc.cpp +++ b/engine/src/mblandroiddc.cpp @@ -2826,7 +2826,7 @@ bool MCAndroidCheckRuntimePermission(MCStringRef p_permission) MCAndroidEngineRemoteCall("askPermission", "bx", &t_result, p_permission); while (s_in_permission_dialog) - MCscreen -> wait(60.0, True, True); + MCscreen -> wait(60.0, False, True); return s_permission_granted; } From 5c9490e443d5d446023c877d267f64c25ad62c57 Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Sat, 28 Jul 2018 09:04:54 +0100 Subject: [PATCH 07/13] Implemented "androidRequestPermission()" function --- engine/src/exec-misc.cpp | 9 +++++++++ engine/src/exec.h | 1 + engine/src/mblandroidmisc.cpp | 8 ++++++++ engine/src/mblandroidutil.h | 1 + engine/src/mblhandlers.cpp | 27 +++++++++++++++++++++++++++ engine/src/mblsyntax.h | 1 + 6 files changed, 47 insertions(+) diff --git a/engine/src/exec-misc.cpp b/engine/src/exec-misc.cpp index f5bc5eebf22..e5a92186558 100644 --- a/engine/src/exec-misc.cpp +++ b/engine/src/exec-misc.cpp @@ -481,6 +481,15 @@ 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 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..6df71a37ed0 100644 --- a/engine/src/exec.h +++ b/engine/src/exec.h @@ -4043,6 +4043,7 @@ 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 MCMiscExecEnableRemoteControl(MCExecContext& ctxt); void MCMiscExecDisableRemoteControl(MCExecContext& ctxt); diff --git a/engine/src/mblandroidmisc.cpp b/engine/src/mblandroidmisc.cpp index 5bc82aef127..49fd9a28b4e 100644 --- a/engine/src/mblandroidmisc.cpp +++ b/engine/src/mblandroidmisc.cpp @@ -802,6 +802,14 @@ bool MCSystemBuildInfo(MCStringRef p_key, MCStringRef& r_value) //////////////////////////////////////////////////////////////////////////////// +bool MCSystemRequestPermission(MCStringRef p_permission, bool& r_granted) +{ + r_granted = MCAndroidCheckRuntimePermission(p_permission); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// diff --git a/engine/src/mblandroidutil.h b/engine/src/mblandroidutil.h index 42fd778739e..874574ed256 100644 --- a/engine/src/mblandroidutil.h +++ b/engine/src/mblandroidutil.h @@ -63,6 +63,7 @@ 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); typedef struct _android_device_configuration { diff --git a/engine/src/mblhandlers.cpp b/engine/src/mblhandlers.cpp index ab260dc5817..33aea63342c 100644 --- a/engine/src/mblhandlers.cpp +++ b/engine/src/mblhandlers.cpp @@ -3377,6 +3377,32 @@ Exec_stat MCHandleBuildInfo(void *context, MCParameter *p_parameters) return ES_ERROR; } +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)); + + if (t_success) + MCMiscExecRequestPermission(ctxt, *t_permission, t_granted); + + if (!ctxt . HasError()) + { + if (t_granted) + ctxt.SetTheResultToValue(kMCTrueString); + else + ctxt.SetTheResultToValue(kMCFalseString); + + return ES_NORMAL; + } + + ctxt.SetTheResultToEmpty(); + return ES_ERROR; +} + ////////////////////////////////////////////////////////////////////////////////////// static MCMediaType MCMediaTypeFromString(MCStringRef p_string) @@ -4529,6 +4555,7 @@ static const MCPlatformMessageSpec s_platform_messages[] = {false, "mobileLocationAuthorizationStatus", MCHandleLocationAuthorizationStatus, nil}, {false, "mobileBuildInfo", MCHandleBuildInfo, nil}, + {false, "androidRequestPermission", MCHandleRequestPermission, nil}, {false, "mobileCanMakePurchase", MCHandleCanMakePurchase, nil}, {false, "mobileEnablePurchaseUpdates", MCHandleEnablePurchaseUpdates, nil}, diff --git a/engine/src/mblsyntax.h b/engine/src/mblsyntax.h index 9ccfaa68249..d668a186cef 100644 --- a/engine/src/mblsyntax.h +++ b/engine/src/mblsyntax.h @@ -466,6 +466,7 @@ 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 MCSystemEnableRemoteControl(); bool MCSystemDisableRemoteControl(); From e5a704e29c5a248afa0ce290ad09288db9277590 Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Sun, 29 Jul 2018 17:44:56 +0100 Subject: [PATCH 08/13] Added feature note --- .../feature-android_runtime_permissions.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/notes/feature-android_runtime_permissions.md 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. + From 62f16516000d3b4a740d1a10f9601aabd6df3106 Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Sun, 29 Jul 2018 19:15:13 +0100 Subject: [PATCH 09/13] [androidRequestPermission] Added dictionary entry --- .../function/androidRequestPermission.lcdoc | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 docs/dictionary/function/androidRequestPermission.lcdoc diff --git a/docs/dictionary/function/androidRequestPermission.lcdoc b/docs/dictionary/function/androidRequestPermission.lcdoc new file mode 100644 index 00000000000..bef2e45423f --- /dev/null +++ b/docs/dictionary/function/androidRequestPermission.lcdoc @@ -0,0 +1,44 @@ +Name: androidRequestPermission + +Type: function + +Syntax: androidRequestPermission() + +Summary: +Returns if permission has been granted by the user. If the user has not been asked before, a dialog is displayed, +showing a permission request for . + +Introduced: 9.0.1 + +OS: mobile + +Platforms: android + +Example: +local tCameraPermissionGranted +put androidRequestPermission("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.CAMERA": permission to access the device camera. +- "android.permission.ACCESS_COARSE_LOCATION": permission to access the device coarse location. +- "android.permission.ACCESS_FINE_LOCATION": permission to access the device fine location. +- "android.permission.WRITE_CONTACTS": permission to write date to the device contacts. +- "android.permission.READ_CONTACTS": permission to read data from the device contacts. +- "android.permission.WRITE_EXTERNAL_STORAGE": permission to write data to the device external storage. + +Returns(boolean): +True if permission has been granted, false otherwise. + + +Description: +Use the function to request permission for from the user, or to find out if permission has been granted. + +>*Note:* Permission names are case sensitive. From 4c34eaf1dca12792e5cc338fb1a634ffcc33317c Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Sun, 29 Jul 2018 23:38:55 +0100 Subject: [PATCH 10/13] Fixed a couple of issues on docs and with long lines --- .../function/androidRequestPermission.lcdoc | 8 ++++---- engine/src/mblandroidfs.cpp | 11 ++++++----- engine/src/mblandroidsensor.cpp | 3 ++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/dictionary/function/androidRequestPermission.lcdoc b/docs/dictionary/function/androidRequestPermission.lcdoc index bef2e45423f..77d1ec486ae 100644 --- a/docs/dictionary/function/androidRequestPermission.lcdoc +++ b/docs/dictionary/function/androidRequestPermission.lcdoc @@ -10,16 +10,16 @@ showing a permission request for . Introduced: 9.0.1 -OS: mobile +OS: android -Platforms: android +Platforms: mobile Example: local tCameraPermissionGranted put androidRequestPermission("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." + answer "This app is not permitted to access the device camera. You can change this" && \ + "in the Settings app." end if diff --git a/engine/src/mblandroidfs.cpp b/engine/src/mblandroidfs.cpp index e451d356859..1b18e3ffb90 100644 --- a/engine/src/mblandroidfs.cpp +++ b/engine/src/mblandroidfs.cpp @@ -419,7 +419,8 @@ 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"))) + if (MCStringBeginsWith(MCNameGetString(p_folder), MCSTR("external"), kMCStringOptionCompareCaseless) && \ + !MCAndroidCheckRuntimePermission(MCSTR("android.permission.WRITE_EXTERNAL_STORAGE"))) { r_folder = MCValueRetain(kMCEmptyString); return False; @@ -428,18 +429,18 @@ Boolean MCAndroidSystem::GetStandardFolder(MCNameRef p_folder, MCStringRef &r_fo // 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/mblandroidsensor.cpp b/engine/src/mblandroidsensor.cpp index be8f0acf323..3f1182c000b 100644 --- a/engine/src/mblandroidsensor.cpp +++ b/engine/src/mblandroidsensor.cpp @@ -64,7 +64,8 @@ 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")); + bool t_success = MCAndroidCheckRuntimePermission(MCSTR("android.permission.ACCESS_COARSE_LOCATION")) && \ + MCAndroidCheckRuntimePermission(MCSTR("android.permission.ACCESS_FINE_LOCATION")); if (!t_success) return false; } From d2c5a3a021f347d2ed4230fe219cc0caaa9fa2b9 Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Mon, 30 Jul 2018 15:06:26 +0100 Subject: [PATCH 11/13] Added "androidHasPermission" and "androidPermissionExists" commands --- .../androidRequestPermission.lcdoc | 15 ++-- .../function/androidHasPermission.lcdoc | 46 +++++++++++ .../function/androidPermissionExists.lcdoc | 33 ++++++++ engine/src/exec-misc.cpp | 16 ++++ engine/src/exec.h | 2 + engine/src/executionerrors.h | 3 + .../src/java/com/runrev/android/Engine.java | 77 +++++++++++++++++++ engine/src/mblandroiddc.cpp | 16 ++++ engine/src/mblandroidmisc.cpp | 12 +++ engine/src/mblandroidutil.h | 2 + engine/src/mblhandlers.cpp | 71 ++++++++++++++++- engine/src/mblsyntax.h | 2 + 12 files changed, 287 insertions(+), 8 deletions(-) rename docs/dictionary/{function => command}/androidRequestPermission.lcdoc (65%) create mode 100644 docs/dictionary/function/androidHasPermission.lcdoc create mode 100644 docs/dictionary/function/androidPermissionExists.lcdoc diff --git a/docs/dictionary/function/androidRequestPermission.lcdoc b/docs/dictionary/command/androidRequestPermission.lcdoc similarity index 65% rename from docs/dictionary/function/androidRequestPermission.lcdoc rename to docs/dictionary/command/androidRequestPermission.lcdoc index 77d1ec486ae..e098fea43ad 100644 --- a/docs/dictionary/function/androidRequestPermission.lcdoc +++ b/docs/dictionary/command/androidRequestPermission.lcdoc @@ -1,12 +1,11 @@ Name: androidRequestPermission -Type: function +Type: command Syntax: androidRequestPermission() Summary: -Returns if permission has been granted by the user. If the user has not been asked before, a dialog is displayed, -showing a permission request for . +Displays a dialog showing a permission request for . If a user has already granted permission for , this command does nothing. Introduced: 9.0.1 @@ -16,7 +15,11 @@ Platforms: mobile Example: local tCameraPermissionGranted -put androidRequestPermission("android.permission.CAMERA") into 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." @@ -34,11 +37,9 @@ The name of the permission to request. - "android.permission.READ_CONTACTS": permission to read data from the device contacts. - "android.permission.WRITE_EXTERNAL_STORAGE": permission to write data to the device external storage. -Returns(boolean): -True if permission has been granted, false otherwise. Description: -Use the function to request permission for from the user, or to find out if permission has been granted. +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..856aedfe73c --- /dev/null +++ b/docs/dictionary/function/androidHasPermission.lcdoc @@ -0,0 +1,46 @@ +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.CAMERA": permission to access the device camera. +- "android.permission.ACCESS_COARSE_LOCATION": permission to access the device coarse location. +- "android.permission.ACCESS_FINE_LOCATION": permission to access the device fine location. +- "android.permission.WRITE_CONTACTS": permission to write date to the device contacts. +- "android.permission.READ_CONTACTS": permission to read data from the device contacts. +- "android.permission.WRITE_EXTERNAL_STORAGE": permission to write data to the device external storage. + +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/engine/src/exec-misc.cpp b/engine/src/exec-misc.cpp index e5a92186558..432396ca6e0 100644 --- a/engine/src/exec-misc.cpp +++ b/engine/src/exec-misc.cpp @@ -489,6 +489,22 @@ void MCMiscExecRequestPermission(MCExecContext& ctxt, MCStringRef p_permission, 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) { diff --git a/engine/src/exec.h b/engine/src/exec.h index 6df71a37ed0..2644f12c558 100644 --- a/engine/src/exec.h +++ b/engine/src/exec.h @@ -4044,6 +4044,8 @@ 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 280f3399568..e88af53fd66 100644 --- a/engine/src/java/com/runrev/android/Engine.java +++ b/engine/src/java/com/runrev/android/Engine.java @@ -1878,6 +1878,83 @@ public boolean askPermission(String p_permission) 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; diff --git a/engine/src/mblandroiddc.cpp b/engine/src/mblandroiddc.cpp index 81a38421a7a..7d8801ae5f9 100644 --- a/engine/src/mblandroiddc.cpp +++ b/engine/src/mblandroiddc.cpp @@ -2831,6 +2831,22 @@ bool MCAndroidCheckRuntimePermission(MCStringRef p_permission) 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) { diff --git a/engine/src/mblandroidmisc.cpp b/engine/src/mblandroidmisc.cpp index 49fd9a28b4e..477ea6fa94b 100644 --- a/engine/src/mblandroidmisc.cpp +++ b/engine/src/mblandroidmisc.cpp @@ -808,6 +808,18 @@ bool MCSystemRequestPermission(MCStringRef p_permission, bool& r_granted) 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/mblandroidutil.h b/engine/src/mblandroidutil.h index 874574ed256..108877c0a7f 100644 --- a/engine/src/mblandroidutil.h +++ b/engine/src/mblandroidutil.h @@ -64,6 +64,8 @@ void MCAndroidObjectRemoteCall(jobject p_object, const char *p_method, const cha 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 33aea63342c..2a5f0b48422 100644 --- a/engine/src/mblhandlers.cpp +++ b/engine/src/mblhandlers.cpp @@ -3377,6 +3377,7 @@ 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); @@ -3386,12 +3387,78 @@ Exec_stat MCHandleRequestPermission(void *context, MCParameter *p_parameters) 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_granted) + if (t_permission_granted) ctxt.SetTheResultToValue(kMCTrueString); else ctxt.SetTheResultToValue(kMCFalseString); @@ -4556,6 +4623,8 @@ static const MCPlatformMessageSpec s_platform_messages[] = {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/mblsyntax.h b/engine/src/mblsyntax.h index d668a186cef..7f653d92584 100644 --- a/engine/src/mblsyntax.h +++ b/engine/src/mblsyntax.h @@ -467,6 +467,8 @@ bool MCSystemFileGetDataProtection(MCStringRef p_path, MCStringRef& r_protection 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(); From 120b71fde68f4c4cac94d4632778a888d5a3190d Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Tue, 31 Jul 2018 09:30:57 +0100 Subject: [PATCH 12/13] Added stubs for iphone and updated docs --- .../command/androidRequestPermission.lcdoc | 34 +++++++++++++++---- .../function/androidHasPermission.lcdoc | 34 +++++++++++++++---- engine/src/mbliphonemisc.mm | 22 ++++++++++++ 3 files changed, 76 insertions(+), 14 deletions(-) diff --git a/docs/dictionary/command/androidRequestPermission.lcdoc b/docs/dictionary/command/androidRequestPermission.lcdoc index e098fea43ad..63a2ed2b168 100644 --- a/docs/dictionary/command/androidRequestPermission.lcdoc +++ b/docs/dictionary/command/androidRequestPermission.lcdoc @@ -17,7 +17,7 @@ Example: local tCameraPermissionGranted put androidHasPermission("android.permission.CAMERA") into tCameraPermissionGranted if not tCameraPermissionGranted then - androidRequestPermission("android.permission.CAMERA") + androidRequestPermission "android.permission.CAMERA" end if put androidHasPermission("android.permission.CAMERA") into tCameraPermissionGranted if not tCameraPermissionGranted then @@ -30,12 +30,32 @@ Parameters: permissionName (enum): The name of the permission to request. -- "android.permission.CAMERA": permission to access the device camera. -- "android.permission.ACCESS_COARSE_LOCATION": permission to access the device coarse location. -- "android.permission.ACCESS_FINE_LOCATION": permission to access the device fine location. -- "android.permission.WRITE_CONTACTS": permission to write date to the device contacts. -- "android.permission.READ_CONTACTS": permission to read data from the device contacts. -- "android.permission.WRITE_EXTERNAL_STORAGE": permission to write data to the device external storage. +- "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. diff --git a/docs/dictionary/function/androidHasPermission.lcdoc b/docs/dictionary/function/androidHasPermission.lcdoc index 856aedfe73c..48219cce0e2 100644 --- a/docs/dictionary/function/androidHasPermission.lcdoc +++ b/docs/dictionary/function/androidHasPermission.lcdoc @@ -17,7 +17,7 @@ Example: local tLocationPermissionGranted put androidHasPermission("android.permission.ACCESS_FINE_LOCATION") into tLocationPermissionGranted if not tLocationPermissionGranted then - androidRequestPermission("android.permission.ACCESS_FINE_LOCATION") + 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" && \ @@ -29,12 +29,32 @@ Parameters: permissionName (enum): The name of the permission to request. -- "android.permission.CAMERA": permission to access the device camera. -- "android.permission.ACCESS_COARSE_LOCATION": permission to access the device coarse location. -- "android.permission.ACCESS_FINE_LOCATION": permission to access the device fine location. -- "android.permission.WRITE_CONTACTS": permission to write date to the device contacts. -- "android.permission.READ_CONTACTS": permission to read data from the device contacts. -- "android.permission.WRITE_EXTERNAL_STORAGE": permission to write data to the device external storage. +- "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. 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; +} + +//////////////////////////////////////////////////////////////////////////////// From 39b59eeee08b75e6968c872bc865258757f11c73 Mon Sep 17 00:00:00 2001 From: livecodepanos Date: Tue, 31 Jul 2018 09:32:48 +0100 Subject: [PATCH 13/13] [androidRequestPermission] Docs: removed parentheses from syntax --- docs/dictionary/command/androidRequestPermission.lcdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dictionary/command/androidRequestPermission.lcdoc b/docs/dictionary/command/androidRequestPermission.lcdoc index 63a2ed2b168..07e5f1ec831 100644 --- a/docs/dictionary/command/androidRequestPermission.lcdoc +++ b/docs/dictionary/command/androidRequestPermission.lcdoc @@ -2,7 +2,7 @@ Name: androidRequestPermission Type: command -Syntax: androidRequestPermission() +Syntax: androidRequestPermission Summary: Displays a dialog showing a permission request for . If a user has already granted permission for , this command does nothing.