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/bugfix-21396.md b/docs/notes/bugfix-21396.md new file mode 100644 index 00000000000..c3b4faed0b7 --- /dev/null +++ b/docs/notes/bugfix-21396.md @@ -0,0 +1 @@ +# Fix crash on startup in iOS 12 beta \ No newline at end of file diff --git a/docs/notes/bugfix-21417.md b/docs/notes/bugfix-21417.md new file mode 100644 index 00000000000..e0c98c759f6 --- /dev/null +++ b/docs/notes/bugfix-21417.md @@ -0,0 +1 @@ +# Don't include any externals in emscripten standalones diff --git a/docs/notes/bugfix-21434.md b/docs/notes/bugfix-21434.md new file mode 100644 index 00000000000..e41919685a4 --- /dev/null +++ b/docs/notes/bugfix-21434.md @@ -0,0 +1 @@ +# Fix visual effects not working when acceleratedRendering is true on Android \ No newline at end of file diff --git a/docs/notes/bugfix-21443.md b/docs/notes/bugfix-21443.md new file mode 100644 index 00000000000..152fe0a6576 --- /dev/null +++ b/docs/notes/bugfix-21443.md @@ -0,0 +1 @@ +# Fix groups sometimes not redrawing when scrolled \ No newline at end of file diff --git a/docs/notes/bugfix-21496.md b/docs/notes/bugfix-21496.md new file mode 100644 index 00000000000..89812e0345b --- /dev/null +++ b/docs/notes/bugfix-21496.md @@ -0,0 +1 @@ +# Ensure emscripten aux stacks are loaded on startup 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/rsrc/emscripten-startup-template.livecodescript b/engine/rsrc/emscripten-startup-template.livecodescript index 3638d194e57..337612d6959 100644 --- a/engine/rsrc/emscripten-startup-template.livecodescript +++ b/engine/rsrc/emscripten-startup-template.livecodescript @@ -1,4 +1,3 @@ -script "__startup" constant kEngineVersion = "@ENGINE_VERSION@" -- Directories that engine expects to normally be present @@ -7,41 +6,38 @@ constant kStandardFolders = "/tmp:/livecode:/boot:/boot/standalone:/boot/fonts" -- Directory containing the initial stack files constant kStartupFolder = "/boot/standalone" -on startup - local tError, tFolder - try - ---------------------------------------------------------------- - -- Create standard filesystem layout - set the itemdelimiter to ":" - repeat for each item tFolder in kStandardFolders - if there is not a folder tFolder then - create folder tFolder - if the result is not empty then - throw the result - end if +local tError, tFolder +try + ---------------------------------------------------------------- + -- Create standard filesystem layout + set the itemdelimiter to ":" + repeat for each item tFolder in kStandardFolders + if there is not a folder tFolder then + create folder tFolder + if the result is not empty then + throw the result end if - end repeat - - ------------------------------------------------------------- - -- Validate engine version - if the version is not kEngineVersion then - throw "Engine mismatch: found" && the version & ", expected" && kEngineVersion end if - - - @STARTUP_SCRIPT@ - - catch tError - end try - - -- Set the initial working directory to the directory that contains - -- the initial stack. - set the defaultfolder to kStartupFolder - - -- Try to print something vaguely helpful to the the log - if tError is not empty then - write "startup failed:" && tError & return to stderr + end repeat + + ------------------------------------------------------------- + -- Validate engine version + if the version is not kEngineVersion then + throw "Engine mismatch: found" && the version & ", expected" && kEngineVersion end if - - return tError -end startup + + + @STARTUP_SCRIPT@ + +catch tError +end try + +-- Try to print something vaguely helpful to the the log +if tError is not empty then + write "startup failed:" && tError & return to stderr + throw tError +end if + +-- Set the initial working directory to the directory that contains +-- the initial stack. +set the defaultfolder to kStartupFolder diff --git a/engine/src/cgimageutil.cpp b/engine/src/cgimageutil.cpp index fc09c878f08..573343b62d0 100644 --- a/engine/src/cgimageutil.cpp +++ b/engine/src/cgimageutil.cpp @@ -114,7 +114,7 @@ bool MCGRasterCreateCGDataProvider(const MCGRaster &p_raster, const MCGIntegerRe t_width = p_src_rect.size.width; t_height = p_src_rect.size.height; - const uint8_t *t_src_ptr = (uint8_t*)MCGRasterGetPixelPtr(p_raster, t_x, t_y); + uint8_t *t_src_ptr = (uint8_t*)MCGRasterGetPixelPtr(p_raster, t_x, t_y); uint32_t t_dst_stride; @@ -125,8 +125,10 @@ bool MCGRasterCreateCGDataProvider(const MCGRaster &p_raster, const MCGIntegerRe if (!p_copy) { t_dst_stride = p_raster.stride; - t_data_provider = CGDataProviderCreateWithData(nil, t_src_ptr, t_height * p_raster.stride, nil); + t_data_provider = CGDataProviderCreateWithData(nil, t_src_ptr, t_height * p_raster.stride, __CGDataProviderDeallocate); t_success = t_data_provider != nil; + if (!t_success) + MCMemoryDeallocate(t_src_ptr); } else { 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 f40288da8ce..fe40eb081ab 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 @@ -2251,7 +2354,8 @@ public void run() { public void showBitmapView() { - ensureBitmapViewVisibility(); + // force visible for visual effects + m_bitmap_view.setVisibility(View.VISIBLE); } //////////////////////////////////////////////////////////////////////////////// @@ -3736,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/mac-cursor.mm b/engine/src/mac-cursor.mm index 31d2aeae782..135c6d5007f 100644 --- a/engine/src/mac-cursor.mm +++ b/engine/src/mac-cursor.mm @@ -137,7 +137,7 @@ void MCPlatformCreateCustomCursor(MCImageBitmap *p_image, MCPoint p_hotspot, MCP t_cursor -> is_standard = false; CGImageRef t_cg_image; - /* UNCHECKED */ MCImageBitmapToCGImage(p_image, false, false, t_cg_image); + /* UNCHECKED */ MCImageBitmapToCGImage(p_image, true, false, t_cg_image); // Convert the CGImage into an NSIMage NSImage *t_cursor_image; diff --git a/engine/src/mac-surface.mm b/engine/src/mac-surface.mm index cccbaa9149c..559c0b58ddb 100644 --- a/engine/src/mac-surface.mm +++ b/engine/src/mac-surface.mm @@ -317,7 +317,6 @@ CGRect MCMacFlipCGRect(const CGRect &p_rect, uint32_t p_surface_height) // IM-2014-10-03: [[ Bug 13432 ]] Render with copy blend mode to replace destination alpha with the source alpha. MCMacRenderRasterToCG(m_cg_context, t_dst_rect, m_raster, MCGRectangleMake(0, 0, m_raster.width, m_raster.height), 1.0, kMCGBlendModeCopy); - free(m_raster . pixels); m_raster . pixels = nil; } 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/mbliphonegfx.mm b/engine/src/mbliphonegfx.mm index 22ac7370a2a..a75861d9f02 100644 --- a/engine/src/mbliphonegfx.mm +++ b/engine/src/mbliphonegfx.mm @@ -102,7 +102,7 @@ static void do_update(void *p_dirty) MCGRegionRef m_region; bool m_own_region; - virtual void FlushBits(MCGIntegerRectangle p_area, void *p_bits, uint32_t p_stride) = 0; + virtual void FlushBits(MCGIntegerRectangle p_area, void *p_bits, uint32_t p_stride, bool &x_taken) = 0; public: MCIPhoneStackSurface(MCGRegionRef p_region) @@ -197,10 +197,16 @@ void UnlockPixels(MCGIntegerRectangle p_area, MCGRaster& p_raster, bool p_update if (p_raster . pixels == nil) return; + bool t_taken = false; + if (p_update) - FlushBits(p_area, p_raster . pixels, p_raster . stride); + FlushBits(p_area, p_raster . pixels, p_raster . stride, t_taken); + + if (!t_taken) + { + free(p_raster.pixels); + } - free(p_raster . pixels); } bool Composite(MCGRectangle p_dst_rect, MCGImageRef p_src, MCGRectangle p_src_rect, MCGFloat p_alpha, MCGBlendMode p_blend) @@ -337,7 +343,7 @@ void UnlockTarget(void) protected: // MM-2014-07-31: [[ ThreadedRendering ]] Updated to pass in the area we wish to draw. - void FlushBits(MCGIntegerRectangle p_area, void *p_bits, uint32_t p_stride) + void FlushBits(MCGIntegerRectangle p_area, void *p_bits, uint32_t p_stride, bool &x_taken) { void *t_target; if (!LockTarget(kMCStackSurfaceTargetCoreGraphics, t_target)) @@ -368,6 +374,8 @@ void FlushBits(MCGIntegerRectangle p_area, void *p_bits, uint32_t p_stride) if (MCGRasterToCGImage(t_raster, MCGIntegerRectangleMake(0, 0, p_area.size.width, p_area.size.height), t_colorspace, false, false, t_image)) { + x_taken = true; + CGContextDrawImage(t_context, CGRectMake((float)p_area.origin.x, (float)(m_height - (p_area.origin.y + p_area.size.height)), (float)p_area.size.width, (float)p_area.size.height), t_image); CGImageRelease(t_image); } @@ -521,7 +529,7 @@ void UnlockTarget(void) protected: // MM-2014-07-31: [[ ThreadedRendering ]] Updated to pass in the area we wish to draw. - void FlushBits(MCGIntegerRectangle p_area, void *p_bits, uint32_t p_stride) + void FlushBits(MCGIntegerRectangle p_area, void *p_bits, uint32_t p_stride, bool& x_taken) { GLuint t_texture; glGenTextures(1, &t_texture); @@ -582,7 +590,7 @@ void FlushBits(MCGIntegerRectangle p_area, void *p_bits, uint32_t p_stride) } glDeleteTextures(1, &t_texture); - } + } }; @implementation MCIPhoneOpenGLDisplayView 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(); diff --git a/engine/src/redraw.cpp b/engine/src/redraw.cpp index 9e09d160851..0fe196e8d79 100644 --- a/engine/src/redraw.cpp +++ b/engine/src/redraw.cpp @@ -599,14 +599,10 @@ void MCControl::layer_dirtyeffectiverect(const MCRectangle& p_effective_rect, bo MCRectangle32 t_device_rect; t_device_rect = MCRectangle32GetTransformedBounds(t_dirty_rect, t_transform); - // Notify any tilecache of the changes. - if (t_tilecache != nil) + // Notify any tilecache of the changes + // If the layer id is zero, there is nothing to do. + if (t_tilecache != nil && t_control -> m_layer_id != 0) { - // We must be in tile-cache mode with a top-level control, but if the layer - // id is zero, there is nothing to do. - if (t_control -> m_layer_id == 0) - return; - // How we handle the layer depends on whether it is a sprite or not. if (!t_control -> layer_issprite()) { diff --git a/ide-support/revsaveasemscriptenstandalone.livecodescript b/ide-support/revsaveasemscriptenstandalone.livecodescript index 6cb19c822a6..5a723d13c72 100644 --- a/ide-support/revsaveasemscriptenstandalone.livecodescript +++ b/ide-support/revsaveasemscriptenstandalone.livecodescript @@ -202,7 +202,10 @@ private command storeDeployedStack pZip, pDeployPath, pBuildFolder, pMainStack, put tTempStackPath into pDeployParams["stackfile"] put tTempDeployPath into pDeployParams["output"] put empty into pDeployParams["engine"] - + + // Externals are not yet supported in emscripten builds + put empty into pDeployParams["externals"] + ---------- Perform standalone deployment logDebug "deploy", "Deploying standalone" @@ -253,13 +256,9 @@ private function getStartupScript pGeneratedStartupScript throw tTemplateFile & ":" && the result end if close file tTemplateFile - - -- Trim to the actual script - delete line 1 of tScript - + -- Make substitutions in the startup script replace "@ENGINE_VERSION@" with the version in tScript - replace "@MODULE_VERSION@" with extensionLCCompileVersion() in tScript replace "@STARTUP_SCRIPT@" with pGeneratedStartupScript in tScript return tScript end getStartupScript