From bf30e343a329cd3fb3c8db370a37b208b79300f0 Mon Sep 17 00:00:00 2001 From: GeneralWar Date: Tue, 14 Jan 2020 18:03:43 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E6=96=B0=E5=A2=9EiOS=E6=8B=8D=E7=85=A7?= =?UTF-8?q?=E5=92=8C=E7=9B=B8=E5=86=8C=E9=80=89=E5=8F=96=E5=8A=9F=E8=83=BD?= =?UTF-8?q?,=E6=9A=82=E6=97=B6=E4=B8=8D=E6=94=AF=E6=8C=81=E7=BC=96?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime-src/Classes/sdks/utils/UtilsSdk.h | 28 ++- .../Classes/sdks/utils/UtilsSdk.mm | 68 ++++++ .../sdks/utils/pictures/PicturePicker.h | 35 +++ .../sdks/utils/pictures/PicturePicker.mm | 205 ++++++++++++++++++ .../Smart_Pi.xcodeproj/project.pbxproj | 20 ++ .../runtime-src/proj.ios_mac/ios/Info.plist | 6 +- 6 files changed, 352 insertions(+), 10 deletions(-) create mode 100644 frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h create mode 100644 frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.mm diff --git a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h index 097eec2..12a01d0 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h +++ b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h @@ -10,7 +10,10 @@ #include "Sdk.h" -class UtilsSdk : public Sdk { +class UtilsSdk : public Sdk +{ +private: + std::map _callbacks; public: UtilsSdk() : Sdk("UtilsSdk") {} @@ -22,6 +25,7 @@ class UtilsSdk : public Sdk { // memory unsigned long long getTotalMemorySize(); unsigned long long getAvailableMemorySize(); + unsigned long long getIOSProcessMemoryUsage(); // file system storage unsigned long long getFileSystemTotalSize(); @@ -30,17 +34,23 @@ class UtilsSdk : public Sdk { static std::string getUUID(); static int getSignatureCode(); - enum NetworkType { - NO_NETWORK = 0, - WIFI, - MOBILE, + enum NetworkType + { + NO_NETWORK = 0, + WIFI, + MOBILE, }; static NetworkType getNetworkType(); -#ifdef SDK_BUGLY - static void setBuglyUserID(const std::string& id); - static void setBuglyUserData(const std::string& params); -#endif + #ifdef SDK_BUGLY + static void setBuglyUserID(const std::string &id); + static void setBuglyUserData(const std::string ¶ms); + #endif + + void takeOrPickPhoto(const std::string& method, const std::string& path, const SdkCallback &callback); + + void callbackToMainThread(const std::string key, const std::string argument); + void invoke(const std::string& key, const std::string& argument); }; #endif /* UtilsSdk_hpp */ diff --git a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm index 7936c71..7ff7632 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm +++ b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm @@ -7,11 +7,15 @@ #include "UtilsSdk.h" #include "SdkManager.h" +#include "scripting/js-bindings/manual/jsb_conversions.hpp" #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #import #import #import "Reachability.h" +#import +#import +#import #endif #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) @@ -24,6 +28,8 @@ #include "Bugly/CocosPlugin/bugly/CrashReport.h" #endif +#include "pictures/PicturePicker.h" + USING_NS_CC; static std::string size2string(size_t size){ @@ -91,6 +97,9 @@ callback(""); } #endif + else if ("takePhoto" == method || "pickPhoto" == method || "takeOrPickPhoto" == method){ + this->takeOrPickPhoto(method, params, callback); + } else { callback(""); } @@ -364,3 +373,62 @@ } } #endif + +void UtilsSdk::takeOrPickPhoto(const std::string& method, const std::string& path, const SdkCallback &callback) +{ + char buffer[128] = {}; + sprintf(buffer, "%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000)); + std::string key = std::string(buffer) + ":takePhoto"; + auto finder = _callbacks.find(key); + if (_callbacks.end() != finder) + { + SE_REPORT_ERROR("key [%s] for callback already exists!", key.c_str()); + callback(""); + return; + } + _callbacks[key] = callback; + + FileUtils::getInstance()->createDirectory(path); + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + PicturePicker* picker = [[PicturePicker alloc] initWithKey:[NSString stringWithUTF8String:key.c_str()] :this]; +#endif + if ("takeOrPickPhoto" == method) + { +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + [picker takeOrPickPhoto:[NSString stringWithUTF8String:(path + "/" + buffer + ".png").c_str()]]; +#endif + } + else if ("takePhoto" == method) + { +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + [picker takePhoto:[NSString stringWithUTF8String:(path + "/" + buffer + ".png").c_str()]]; +#endif + } + else if ("pickPhoto" == method) + { +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + [picker pickPhoto:[NSString stringWithUTF8String:(path + "/" + buffer + ".png").c_str()]]; +#endif + } +} + +void UtilsSdk::callbackToMainThread(const std::string key, const std::string argument) +{ +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + dispatch_async(dispatch_get_main_queue(), ^{ + this->invoke(key, argument); + }); +#endif +} + +void UtilsSdk::invoke(const std::string& key, const std::string& argument) +{ + auto finder = _callbacks.find(key); + if (_callbacks.end() != finder) + { + SdkCallback& callback = finder->second; + callback(argument); + _callbacks.erase(finder); + } +} diff --git a/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h b/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h new file mode 100644 index 0000000..6df778d --- /dev/null +++ b/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h @@ -0,0 +1,35 @@ +// +// PicturePicker.h +// Smart_Pi +// +// 暂时不支持编辑功能。 +// +// Created by 朱嘉灵 on 2020/1/14. +// + +#ifndef PicturePicker_h +#define PicturePicker_h + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) +#include +#import +#import + +@interface PicturePicker : NSObject +{ + UIViewController* _viewController; + + NSString* _key; + NSString* _filename; + void* _owner; +} + +-(id) initWithKey:(NSString*)key :(void*)owner; +-(void) takeOrPickPhoto:(NSString*)filename; +-(void) takePhoto:(NSString*)filename; +-(void) pickPhoto:(NSString*)filename; + +@end +#endif + +#endif /* PicturePicker_h */ diff --git a/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.mm b/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.mm new file mode 100644 index 0000000..0b54e33 --- /dev/null +++ b/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.mm @@ -0,0 +1,205 @@ +// +// PicturePicker.m +// Smart_Pi-mobile +// +// Created by 朱嘉灵 on 2020/1/14. +// + +#import "PicturePicker.h" +#include "../UtilsSdk.h" +#pragma mark - 01.使用相机相册要导入头文件的 +#import +#import +#import + +@interface PicturePicker () +#pragma mark - 02.拖线一个imageView控件用来展示选中的图片 & 创建一个弹框; +@property (nonatomic, strong) UIImagePickerController *imagePickerController; +@end + +@implementation PicturePicker +#pragma mark - 03.懒加载初始化弹框; +- (UIImagePickerController *)imagePickerController { + if (_imagePickerController == nil) { + _imagePickerController = [[UIImagePickerController alloc] init]; + _imagePickerController.delegate = self; //delegate遵循了两个代理 + _imagePickerController.allowsEditing = NO; + } + return _imagePickerController; +} + +- (id)initWithKey:(NSString*)key :(void*)owner { + self = [super init]; + + _key = [NSString stringWithString:key]; + [_key retain]; + _owner = owner; + + UIWindow *window = [[UIApplication sharedApplication].delegate window]; + _viewController = [window rootViewController]; + + return self; +} + +-(void) dealloc { + if (_key){ + [_key release]; + } + if (_filename){ + [_filename release]; + } + [super dealloc]; +} + +#pragma mark - 05.在租来的触发方法里添加事件; +- (void)takeOrPickPhoto:(NSString*)filename { + _filename = [NSString stringWithString:filename]; + [_filename retain]; + //MARK: - 06.点击图片调起弹窗并检查权限; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + UIAlertAction *camera = [UIAlertAction actionWithTitle:@"使用相机拍摄" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [self checkCameraPermission];//调用检查相机权限方法 + }]; + UIAlertAction *album = [UIAlertAction actionWithTitle:@"从相册中选择" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [self checkAlbumPermission];//调起检查相册权限方法 + }]; + UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + [_viewController dismissViewControllerAnimated:YES completion:nil]; + [self response:@""]; + }]; + + [alert addAction:camera]; + [alert addAction:album]; + [alert addAction:cancel]; + + [_viewController presentViewController:alert animated:YES completion:nil]; +} + +- (void)takePhoto:(NSString *)filename +{ + _filename = [NSString stringWithString:filename]; + [_filename retain]; + + [self checkCameraPermission]; +} + +#pragma mark - Camera(检查相机权限方法) +- (void)checkCameraPermission { + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + if (status == AVAuthorizationStatusNotDetermined) { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + if (granted) { + [self callCamera]; + } + }]; + } else if (status == AVAuthorizationStatusDenied || status == AVAuthorizationStatusRestricted) { +// [self alertAlbum];//如果没有权限给出提示 + [self response:@""]; + } else { + [self callCamera];//有权限进入调起相机方法 + } +} + +- (void)callCamera { +#pragma mark - 07.判断相机是否可用,如果可用调起 + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + self.imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; + [_viewController presentViewController:self.imagePickerController animated:YES completion:^{}]; + } else {//不可用只能GG了 + NSLog(@"木有相机"); + [self response:@""]; + } +} + +- (void)pickPhoto:(NSString *)filename +{ + _filename = [NSString stringWithString:filename]; + [_filename retain]; + + [self checkAlbumPermission]; +} + +#pragma mark - Album(相册流程与相机流程相同,相册是不存在硬件问题的,只要有权限就可以直接调用) +- (void)checkAlbumPermission { + PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; + if (status == PHAuthorizationStatusNotDetermined) { + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (status == PHAuthorizationStatusAuthorized) { + [self selectAlbum]; + } + }); + }]; + } else if (status == PHAuthorizationStatusDenied || status == PHAuthorizationStatusRestricted) { + [self alertAlbum]; + } else { + [self selectAlbum]; + } +} + +- (void)selectAlbum { + //判断相册是否可用 + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { + self.imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + [_viewController presentViewController:self.imagePickerController animated:YES completion:^{}]; + } +} + +- (void)alertAlbum { + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:@"请在设置中打开相册" preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [_viewController dismissViewControllerAnimated:YES completion:nil]; + [self response:@""]; + }]; + [alert addAction:cancel]; + [_viewController presentViewController:alert animated:YES completion:nil]; +} + +//- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_NA,__MAC_NA,__IPHONE_2_0,__IPHONE_3_0) +//{ +// [picker dismissViewControllerAnimated:YES completion:nil]; +// image = [editingInfo valueForKey:UIImagePickerControllerEditedImage]; +// BOOL result = [UIImageJPEGRepresentation(image, 1) writeToFile:_filename atomically:YES]; +// if (result) +// { +// [self response:_filename]; +// } +// else +// { +// [self response:@""]; +// } +// [self autorelease]; +//} + +- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { + [picker dismissViewControllerAnimated:YES completion:nil]; + UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage]; + BOOL result = [UIImageJPEGRepresentation(image, 1) writeToFile:_filename atomically:YES]; + if (result) + { + [self response:_filename]; + } + else + { + [self response:@""]; + } + [self autorelease]; +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker +{ + [picker dismissViewControllerAnimated:YES completion:nil]; + [self response:@""]; +} + +- (void)response:(NSString*)filename +{ + if (_owner) + { + UtilsSdk* utils = reinterpret_cast(_owner); + utils->callbackToMainThread(_key.UTF8String, filename.UTF8String); + } +} + +@end diff --git a/frameworks/runtime-src/proj.ios_mac/Smart_Pi.xcodeproj/project.pbxproj b/frameworks/runtime-src/proj.ios_mac/Smart_Pi.xcodeproj/project.pbxproj index faa99d4..658ba41 100755 --- a/frameworks/runtime-src/proj.ios_mac/Smart_Pi.xcodeproj/project.pbxproj +++ b/frameworks/runtime-src/proj.ios_mac/Smart_Pi.xcodeproj/project.pbxproj @@ -120,6 +120,8 @@ DEFCD3C121D1E13E00A872C6 /* hl_sha384wrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DEFCD39121D1E13E00A872C6 /* hl_sha384wrapper.cpp */; }; DEFCD3C421D1E18700A872C6 /* HashSdk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DEFCD3C221D1E18700A872C6 /* HashSdk.cpp */; }; DEFCD3C521D1E18700A872C6 /* HashSdk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DEFCD3C221D1E18700A872C6 /* HashSdk.cpp */; }; + E20D6CC423CD911900DBFC39 /* PicturePicker.mm in Sources */ = {isa = PBXBuildFile; fileRef = E20D6CC323CD911900DBFC39 /* PicturePicker.mm */; }; + E20D6CC723CD972B00DBFC39 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E20D6CC623CD972B00DBFC39 /* Photos.framework */; }; E2735178215492D700BCBE60 /* courses in Resources */ = {isa = PBXBuildFile; fileRef = E2735177215492D700BCBE60 /* courses */; }; E285AE8B2154E017008821D8 /* project.manifest in Resources */ = {isa = PBXBuildFile; fileRef = E285AE8A2154E017008821D8 /* project.manifest */; }; E2E8A2512361C32A0085A21B /* FirstViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = E2E8A2502361C32A0085A21B /* FirstViewController.mm */; }; @@ -320,6 +322,9 @@ DEFCD39121D1E13E00A872C6 /* hl_sha384wrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hl_sha384wrapper.cpp; sourceTree = ""; }; DEFCD3C221D1E18700A872C6 /* HashSdk.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = HashSdk.cpp; sourceTree = ""; }; DEFCD3C321D1E18700A872C6 /* HashSdk.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HashSdk.h; sourceTree = ""; }; + E20D6CC323CD911900DBFC39 /* PicturePicker.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = PicturePicker.mm; sourceTree = ""; }; + E20D6CC523CD914700DBFC39 /* PicturePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PicturePicker.h; sourceTree = ""; }; + E20D6CC623CD972B00DBFC39 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk/System/Library/Frameworks/Photos.framework; sourceTree = DEVELOPER_DIR; }; E2735177215492D700BCBE60 /* courses */ = {isa = PBXFileReference; lastKnownFileType = folder; name = courses; path = ../../../courses; sourceTree = ""; }; E285AE8A2154E017008821D8 /* project.manifest */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = project.manifest; path = ../../../project.manifest; sourceTree = ""; }; E2E8A24F2361C32A0085A21B /* FirstViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FirstViewController.h; path = ios/FirstViewController.h; sourceTree = ""; }; @@ -360,6 +365,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E20D6CC723CD972B00DBFC39 /* Photos.framework in Frameworks */, DE36599820FF29DB00D046A0 /* Bugly.framework in Frameworks */, DE10A5C820CFE80800B856E3 /* CoreTelephony.framework in Frameworks */, DE10A5C620CFE7FC00B856E3 /* libc++.tbd in Frameworks */, @@ -456,6 +462,7 @@ A92275401517C094001B78AA /* Frameworks */ = { isa = PBXGroup; children = ( + E20D6CC623CD972B00DBFC39 /* Photos.framework */, DE10A5C720CFE80800B856E3 /* CoreTelephony.framework */, DE10A5C520CFE7FC00B856E3 /* libc++.tbd */, 1AC62C121F4D1E7F00F43CB1 /* JavaScriptCore.framework */, @@ -682,6 +689,7 @@ DE4883DD20FDB31F006717A2 /* utils */ = { isa = PBXGroup; children = ( + E20D6CC223CD90B500DBFC39 /* pictures */, DE4883DE20FDB31F006717A2 /* UtilsSdk.h */, DE4883DF20FDB31F006717A2 /* UtilsSdk.mm */, ); @@ -759,6 +767,15 @@ path = src; sourceTree = ""; }; + E20D6CC223CD90B500DBFC39 /* pictures */ = { + isa = PBXGroup; + children = ( + E20D6CC523CD914700DBFC39 /* PicturePicker.h */, + E20D6CC323CD911900DBFC39 /* PicturePicker.mm */, + ); + path = pictures; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -944,6 +961,7 @@ 509D4AC917EBB2AB00697056 /* RootViewController.mm in Sources */, DE6EEB5621D36CBB00FA65AE /* SettingsBundleHelper.cpp in Sources */, DE36599F20FF29DB00D046A0 /* BuglySdk.cpp in Sources */, + E20D6CC423CD911900DBFC39 /* PicturePicker.mm in Sources */, DE10A5D720CFFB4A00B856E3 /* WXApiResponseHandler.m in Sources */, DE36599720FF29DB00D046A0 /* CrashReport.mm in Sources */, DEFCD3B421D1E13E00A872C6 /* hl_sha1wrapper.cpp in Sources */, @@ -1128,6 +1146,7 @@ "$(SRCROOT)/../Classes/sdks/wechat/ios/", "$(SRCROOT)/../Classes/sdks/Bugly/CocosPlugin/agent/iOS/BuglyAgent", ); + MARKETING_VERSION = 1.55.0; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.congmingpai.mobile.stable; PRODUCT_NAME = Stable; @@ -1170,6 +1189,7 @@ "$(SRCROOT)/../Classes/sdks/wechat/ios/", "$(SRCROOT)/../Classes/sdks/Bugly/CocosPlugin/agent/iOS/BuglyAgent", ); + MARKETING_VERSION = 1.55.0; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.congmingpai.mobile.stable; PRODUCT_NAME = Stable; diff --git a/frameworks/runtime-src/proj.ios_mac/ios/Info.plist b/frameworks/runtime-src/proj.ios_mac/ios/Info.plist index c0b3722..3b08314 100755 --- a/frameworks/runtime-src/proj.ios_mac/ios/Info.plist +++ b/frameworks/runtime-src/proj.ios_mac/ios/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.42.0 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleURLTypes @@ -48,6 +48,10 @@ NSAllowsArbitraryLoads + NSCameraUsageDescription + App需获取调用系统相机的权限 + NSPhotoLibraryUsageDescription + App需获取调用系统相册的权限 UILaunchStoryboardName LaunchScreen UIPrerenderedIcon From c9b0875f87c4d180bb81d4f0a9db6267efa807f8 Mon Sep 17 00:00:00 2001 From: GeneralWar Date: Tue, 14 Jan 2020 18:04:51 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=97=A0=E6=95=88?= =?UTF-8?q?=E7=9A=84include?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime-src/Classes/sdks/utils/pictures/PicturePicker.h | 1 - 1 file changed, 1 deletion(-) diff --git a/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h b/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h index 6df778d..ac6efd0 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h +++ b/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h @@ -11,7 +11,6 @@ #define PicturePicker_h #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) -#include #import #import From f2277cec247f73854be393abe089ea22c3803714 Mon Sep 17 00:00:00 2001 From: GeneralWar Date: Thu, 16 Jan 2020 18:07:52 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AE=89=E5=8D=93?= =?UTF-8?q?=E7=9B=B8=E6=9C=BA=E6=8B=8D=E7=85=A7=E5=92=8C=E7=9B=B8=E5=86=8C?= =?UTF-8?q?=E9=80=89=E5=8F=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cocos2d-x/cocos/base/CCDirector.cpp | 17 + frameworks/cocos2d-x/cocos/base/CCDirector.h | 5 +- .../libcocos2dx/android-libcocos2dx.iml | 100 ++++++ .../runtime-src/Classes/sdks/Android.mk | 1 + .../runtime-src/Classes/sdks/utils/UtilsSdk.h | 3 +- .../Classes/sdks/utils/UtilsSdk.mm | 30 +- .../Classes/sdks/utils/photos/PhotoPicker.cpp | 61 ++++ .../Classes/sdks/utils/photos/PhotoPicker.h | 59 ++++ .../{pictures => photos}/PicturePicker.mm | 16 +- .../sdks/utils/pictures/PicturePicker.h | 34 -- .../app/AndroidManifest.xml | 22 +- .../proj.android-studio/app/app-Smart_Pi.iml | 317 ++++++++++++++++++ .../proj.android-studio/app/build.gradle | 6 +- .../app/res/values/strings.xml | 2 +- .../app/res/xml/file_paths.xml | 4 + .../org/cocos2dx/javascript/AppActivity.java | 5 + .../org/cocos2dx/javascript/PathUtils.java | 113 +++++++ .../org/cocos2dx/javascript/PhotoPicker.java | 163 +++++++++ 18 files changed, 907 insertions(+), 51 deletions(-) create mode 100644 frameworks/cocos2d-x/cocos/platform/android/libcocos2dx/android-libcocos2dx.iml create mode 100644 frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.cpp create mode 100644 frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.h rename frameworks/runtime-src/Classes/sdks/utils/{pictures => photos}/PicturePicker.mm (97%) delete mode 100644 frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h create mode 100644 frameworks/runtime-src/proj.android-studio/app/app-Smart_Pi.iml create mode 100644 frameworks/runtime-src/proj.android-studio/app/res/xml/file_paths.xml create mode 100644 frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PathUtils.java create mode 100644 frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PhotoPicker.java diff --git a/frameworks/cocos2d-x/cocos/base/CCDirector.cpp b/frameworks/cocos2d-x/cocos/base/CCDirector.cpp index 7f2920f..ce4b574 100644 --- a/frameworks/cocos2d-x/cocos/base/CCDirector.cpp +++ b/frameworks/cocos2d-x/cocos/base/CCDirector.cpp @@ -1437,6 +1437,18 @@ void Director::mainLoop() } else if (! _invalid) { + // version: 1.55.0 + // reason: 为实现拍照功能而添加,JAVA层的拍照功能在子线程中完成,而V8不允许跨线程调用Isolate,无法直接执行回调函数,因此添加此缓存,待下一帧执行。 + if (_cachedFunctionsNextUpdate.size()) + { + for (auto iterator : _cachedFunctionsNextUpdate) + { + std::function method = iterator; + method(); + } + _cachedFunctionsNextUpdate.clear(); + } + drawScene(); // release the objects @@ -1459,5 +1471,10 @@ void Director::setAnimationInterval(float interval) } } +void Director::runInNextUpdate(std::function method) +{ + _cachedFunctionsNextUpdate.push_back(method); +} + NS_CC_END diff --git a/frameworks/cocos2d-x/cocos/base/CCDirector.h b/frameworks/cocos2d-x/cocos/base/CCDirector.h index 3c881aa..fb22311 100644 --- a/frameworks/cocos2d-x/cocos/base/CCDirector.h +++ b/frameworks/cocos2d-x/cocos/base/CCDirector.h @@ -514,6 +514,7 @@ class CC_DLL Director : public Ref void setCullingEnabled (bool enable) { _isCullingEnabled = enable; } bool isCullingEnabled () const { return _isCullingEnabled; } + void runInNextUpdate(std::function method); protected: void reset(); @@ -636,7 +637,9 @@ class CC_DLL Director : public Ref friend class GLView; bool _isCullingEnabled; - + + // 为实现拍照功能而添加,JAVA层的拍照功能在子线程中完成,而V8不允许跨线程调用Isolate,无法直接执行回调函数,因此添加此缓存,待下一帧执行。 + std::list> _cachedFunctionsNextUpdate; private: /** Use std::vector to implement a quick matrix stack. diff --git a/frameworks/cocos2d-x/cocos/platform/android/libcocos2dx/android-libcocos2dx.iml b/frameworks/cocos2d-x/cocos/platform/android/libcocos2dx/android-libcocos2dx.iml new file mode 100644 index 0000000..283ada7 --- /dev/null +++ b/frameworks/cocos2d-x/cocos/platform/android/libcocos2dx/android-libcocos2dx.iml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frameworks/runtime-src/Classes/sdks/Android.mk b/frameworks/runtime-src/Classes/sdks/Android.mk index 4c48486..e728d48 100644 --- a/frameworks/runtime-src/Classes/sdks/Android.mk +++ b/frameworks/runtime-src/Classes/sdks/Android.mk @@ -19,6 +19,7 @@ LOCAL_SRC_FILES := SdkManager.cpp \ Bugly/BuglySdk.cpp \ wechat/WechatSdk.mm \ hash/HashSdk.cpp \ + utils/photos/PhotoPicker.cpp \ LOCAL_CPP_EXTENSION := .mm .cpp .cc diff --git a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h index 12a01d0..3eaa71a 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h +++ b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h @@ -48,7 +48,8 @@ class UtilsSdk : public Sdk #endif void takeOrPickPhoto(const std::string& method, const std::string& path, const SdkCallback &callback); - + + // 将结果同步至主线程中进行处理 void callbackToMainThread(const std::string key, const std::string argument); void invoke(const std::string& key, const std::string& argument); }; diff --git a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm index 7ff7632..26e6c37 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm +++ b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm @@ -7,7 +7,6 @@ #include "UtilsSdk.h" #include "SdkManager.h" -#include "scripting/js-bindings/manual/jsb_conversions.hpp" #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) #import @@ -21,6 +20,7 @@ #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include #include "platform/android/jni/JniHelper.h" +#include #define CLASS_NAME "org/cocos2dx/javascript/UtilsSdk" #endif @@ -28,7 +28,7 @@ #include "Bugly/CocosPlugin/bugly/CrashReport.h" #endif -#include "pictures/PicturePicker.h" +#include "utils/photos/PhotoPicker.h" USING_NS_CC; @@ -377,12 +377,21 @@ void UtilsSdk::takeOrPickPhoto(const std::string& method, const std::string& path, const SdkCallback &callback) { char buffer[128] = {}; +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) sprintf(buffer, "%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000)); +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + time_t current; + time(¤t); + sprintf(buffer, "%lld", (long long)current); +#endif std::string key = std::string(buffer) + ":takePhoto"; auto finder = _callbacks.find(key); if (_callbacks.end() != finder) { +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) SE_REPORT_ERROR("key [%s] for callback already exists!", key.c_str()); +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) +#endif callback(""); return; } @@ -391,24 +400,32 @@ FileUtils::getInstance()->createDirectory(path); #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) - PicturePicker* picker = [[PicturePicker alloc] initWithKey:[NSString stringWithUTF8String:key.c_str()] :this]; + PhotoPicker* picker = [[PhotoPicker alloc] initWithKey:[NSString stringWithUTF8String:key.c_str()] :this]; +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + PhotoPicker* picker = new PhotoPicker(key, this); #endif if ("takeOrPickPhoto" == method) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) [picker takeOrPickPhoto:[NSString stringWithUTF8String:(path + "/" + buffer + ".png").c_str()]]; +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + picker->takeOrPickPhoto(path + "/" + buffer + ".png"); #endif } else if ("takePhoto" == method) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) [picker takePhoto:[NSString stringWithUTF8String:(path + "/" + buffer + ".png").c_str()]]; +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + picker->takePhoto(path + "/" + buffer + ".png"); #endif } else if ("pickPhoto" == method) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) [picker pickPhoto:[NSString stringWithUTF8String:(path + "/" + buffer + ".png").c_str()]]; +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + picker->pickPhoto(path + "/" + buffer + ".png"); #endif } } @@ -419,6 +436,13 @@ dispatch_async(dispatch_get_main_queue(), ^{ this->invoke(key, argument); }); +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + // 安卓系统中似乎没有原生的方式进行任务的线程间同步,资料显示可以通过libuv中uv_async_send实现,因需要添加第三方库,故未尝试。 + Director::getInstance()->runInNextUpdate( + [=] + { + this->invoke(key, argument); + }); #endif } diff --git a/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.cpp b/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.cpp new file mode 100644 index 0000000..82950d7 --- /dev/null +++ b/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.cpp @@ -0,0 +1,61 @@ +// +// PhotoPicker.m +// Smart_Pi-mobile +// +// Created by 朱嘉灵 on 2020/1/14. +// + +#include "PhotoPicker.h" +#include "../UtilsSdk.h" +#include +#include "platform/android/jni/JniHelper.h" +using namespace cocos2d; + +extern "C" +{ + void Java_org_cocos2dx_javascript_PhotoPicker_response(JNIEnv *env, jobject thiz, long long handle, jstring jFilename) + { + cocos2d::log("Java_org_cocos2dx_javascript_PhotoPicker_response"); + PhotoPicker* picker = reinterpret_cast(handle); + const char* filename = env->GetStringUTFChars(jFilename, JNI_FALSE); + picker->response(reinterpret_cast(filename)); + } +} + +void PhotoPicker::takeOrPickPhoto(const std::string &filename) +{ + _filename = filename; + this->callActivity("takeOrPickPhoto"); +} + +void PhotoPicker::takePhoto(const std::string &filename) +{ + _filename = filename; + this->callActivity("takePhoto"); +} + +void PhotoPicker::pickPhoto(const std::string &filename) +{ + _filename = filename; + this->callActivity("pickPhoto"); +} + +void PhotoPicker::callActivity(const std::string &method) +{ + JniMethodInfo minfo; + if (JniHelper::getStaticMethodInfo(minfo, "org/cocos2dx/javascript/PhotoPicker", "takeOrPickPhoto", "(JLjava/lang/String;Ljava/lang/String;)V")) { + jlong instance = reinterpret_cast(this); + JNIEnv* env = JniHelper::getEnv(); + jstring jMethod = env->NewStringUTF(method.c_str()); + jstring jFilename = env->NewStringUTF(_filename.c_str()); + minfo.env->CallStaticVoidMethod(minfo.classID, minfo.methodID, instance, jMethod, jFilename); + env->DeleteLocalRef(jFilename); + env->DeleteLocalRef(jMethod); + } +} + +void PhotoPicker::response(const std::string &filename) +{ + UtilsSdk* utils = reinterpret_cast(_owner); + utils->callbackToMainThread(_key, filename); +} diff --git a/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.h b/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.h new file mode 100644 index 0000000..84bdc88 --- /dev/null +++ b/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.h @@ -0,0 +1,59 @@ +// +// PhotoPicker.h +// Smart_Pi +// +// 暂时不支持编辑功能。 +// +// Created by 朱嘉灵 on 2020/1/14. +// + +#ifndef PhotoPicker_h +#define PhotoPicker_h + +#include "cocos2d.h" + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + +#import +#import + +@interface PhotoPicker : NSObject +{ + UIViewController* _viewController; + + NSString* _key; + NSString* _filename; + void* _owner; +} + +-(id) initWithKey:(NSString*)key :(void*)owner; +-(void) takeOrPickPhoto:(NSString*)filename; +-(void) takePhoto:(NSString*)filename; +-(void) pickPhoto:(NSString*)filename; + +@end + +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + +#include + +class PhotoPicker +{ +private: + std::string _key; + std::string _filename; + void* _owner; +public: + PhotoPicker(const std::string& key, void* owner) : _key(key), _owner(owner) { } + ~PhotoPicker() { } + + void takeOrPickPhoto(const std::string& filename); + void takePhoto(const std::string& filename); + void pickPhoto(const std::string& filename); + + void callActivity(const std::string& method); + void response(const std::string& filename); +}; +#endif + +#endif /* PhotoPicker_h */ diff --git a/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.mm b/frameworks/runtime-src/Classes/sdks/utils/photos/PicturePicker.mm similarity index 97% rename from frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.mm rename to frameworks/runtime-src/Classes/sdks/utils/photos/PicturePicker.mm index 0b54e33..63730d1 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.mm +++ b/frameworks/runtime-src/Classes/sdks/utils/photos/PicturePicker.mm @@ -1,23 +1,23 @@ // -// PicturePicker.m +// PhotoPicker.mm // Smart_Pi-mobile // // Created by 朱嘉灵 on 2020/1/14. // -#import "PicturePicker.h" +#import "PhotoPicker.h" #include "../UtilsSdk.h" #pragma mark - 01.使用相机相册要导入头文件的 #import #import #import -@interface PicturePicker () +@interface PhotoPicker () #pragma mark - 02.拖线一个imageView控件用来展示选中的图片 & 创建一个弹框; @property (nonatomic, strong) UIImagePickerController *imagePickerController; @end -@implementation PicturePicker +@implementation PhotoPicker #pragma mark - 03.懒加载初始化弹框; - (UIImagePickerController *)imagePickerController { if (_imagePickerController == nil) { @@ -30,11 +30,11 @@ - (UIImagePickerController *)imagePickerController { - (id)initWithKey:(NSString*)key :(void*)owner { self = [super init]; - + _key = [NSString stringWithString:key]; [_key retain]; _owner = owner; - + UIWindow *window = [[UIApplication sharedApplication].delegate window]; _viewController = [window rootViewController]; @@ -80,7 +80,7 @@ - (void)takePhoto:(NSString *)filename { _filename = [NSString stringWithString:filename]; [_filename retain]; - + [self checkCameraPermission]; } @@ -116,7 +116,7 @@ - (void)pickPhoto:(NSString *)filename { _filename = [NSString stringWithString:filename]; [_filename retain]; - + [self checkAlbumPermission]; } diff --git a/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h b/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h deleted file mode 100644 index ac6efd0..0000000 --- a/frameworks/runtime-src/Classes/sdks/utils/pictures/PicturePicker.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// PicturePicker.h -// Smart_Pi -// -// 暂时不支持编辑功能。 -// -// Created by 朱嘉灵 on 2020/1/14. -// - -#ifndef PicturePicker_h -#define PicturePicker_h - -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) -#import -#import - -@interface PicturePicker : NSObject -{ - UIViewController* _viewController; - - NSString* _key; - NSString* _filename; - void* _owner; -} - --(id) initWithKey:(NSString*)key :(void*)owner; --(void) takeOrPickPhoto:(NSString*)filename; --(void) takePhoto:(NSString*)filename; --(void) pickPhoto:(NSString*)filename; - -@end -#endif - -#endif /* PicturePicker_h */ diff --git a/frameworks/runtime-src/proj.android-studio/app/AndroidManifest.xml b/frameworks/runtime-src/proj.android-studio/app/AndroidManifest.xml index 3c8e134..beaf553 100755 --- a/frameworks/runtime-src/proj.android-studio/app/AndroidManifest.xml +++ b/frameworks/runtime-src/proj.android-studio/app/AndroidManifest.xml @@ -2,6 +2,7 @@ @@ -16,6 +17,7 @@ + @@ -35,7 +37,18 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frameworks/runtime-src/proj.android-studio/app/build.gradle b/frameworks/runtime-src/proj.android-studio/app/build.gradle index 8254143..3397987 100755 --- a/frameworks/runtime-src/proj.android-studio/app/build.gradle +++ b/frameworks/runtime-src/proj.android-studio/app/build.gradle @@ -3,12 +3,12 @@ import org.apache.tools.ant.taskdefs.condition.Os apply plugin: 'com.android.application' android { - compileSdkVersion 22 + compileSdkVersion 23 buildToolsVersion '26.0.2' defaultConfig { // applicationId "com.congmingpai.mobile" - minSdkVersion 22 + minSdkVersion 23 targetSdkVersion PROP_TARGET_SDK_VERSION // versionCode 1 // versionName "1.0" @@ -151,4 +151,6 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':libcocos2dx') implementation 'com.tencent.bugly:nativecrashreport:latest.release' + implementation 'com.android.support:support-compat:26.0.0' // 版本与buildToolsVersion一致,为了通过ActivityCompat解决动态申请权限问题,否则无法调用相机 + implementation 'com.android.support:support-v4:26.0.0' // 版本与buildToolsVersion一致,添加对FileProvider的支持,7.0以上的系统将file://限制为应用内部uri,需要通过FileProvider传递给外部应用 } diff --git a/frameworks/runtime-src/proj.android-studio/app/res/values/strings.xml b/frameworks/runtime-src/proj.android-studio/app/res/values/strings.xml index 222ab42..fd29c9c 100755 --- a/frameworks/runtime-src/proj.android-studio/app/res/values/strings.xml +++ b/frameworks/runtime-src/proj.android-studio/app/res/values/strings.xml @@ -1,3 +1,3 @@ - 聪明派 + STABLE diff --git a/frameworks/runtime-src/proj.android-studio/app/res/xml/file_paths.xml b/frameworks/runtime-src/proj.android-studio/app/res/xml/file_paths.xml new file mode 100644 index 0000000..f3ca085 --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/res/xml/file_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/AppActivity.java b/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/AppActivity.java index 2650317..aa23381 100755 --- a/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/AppActivity.java +++ b/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/AppActivity.java @@ -148,4 +148,9 @@ public void uncaughtException(Thread thread, Throwable ex) { mDefaultExceptionHandler.uncaughtException(thread, ex); } } + + static public void takeOrPickPhoto(String method) + { + + } } diff --git a/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PathUtils.java b/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PathUtils.java new file mode 100644 index 0000000..71ee496 --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PathUtils.java @@ -0,0 +1,113 @@ +package org.cocos2dx.javascript; + + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +public class PathUtils { + /** + * 根据Uri获取图片的绝对路径 + * + * @param context 上下文对象 + * @param uri 图片的Uri + * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null + */ + public static String getRealPathFromUri(Context context, Uri uri) { + int sdkVersion = Build.VERSION.SDK_INT; + if (sdkVersion >= 19) { + return getRealPathFromUriAboveApi19(context, uri); + } else { + return getRealPathFromUriBelowAPI19(context, uri); + } + } + + /** + * 适配api19以下(不包括api19),根据uri获取图片的绝对路径 + * + * @param context 上下文对象 + * @param uri 图片的Uri + * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null + */ + private static String getRealPathFromUriBelowAPI19(Context context, Uri uri) { + return getDataColumn(context, uri, null, null); + } + + /** + * 适配api19及以上,根据uri获取图片的绝对路径 + * + * @param context 上下文对象 + * @param uri 图片的Uri + * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null + */ + @SuppressLint("NewApi") + private static String getRealPathFromUriAboveApi19(Context context, Uri uri) { + String filePath = null; + if (DocumentsContract.isDocumentUri(context, uri)) { + // 如果是document类型的 uri, 则通过document id来进行处理 + String documentId = DocumentsContract.getDocumentId(uri); + if (isMediaDocument(uri)) { + // 使用':'分割 + String id = documentId.split(":")[1]; + + String selection = MediaStore.Images.Media._ID + "=?"; + String[] selectionArgs = {id}; + filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs); + } else if (isDownloadsDocument(uri)) { + Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId)); + filePath = getDataColumn(context, contentUri, null, null); + } + } else if ("content".equalsIgnoreCase(uri.getScheme())) { + // 如果是 content 类型的 Uri + filePath = getDataColumn(context, uri, null, null); + } else if ("file".equals(uri.getScheme())) { + // 如果是 file 类型的 Uri,直接获取图片对应的路径 + filePath = uri.getPath(); + } + return filePath; + } + + /** + * 获取数据库表中的 _data 列,即返回Uri对应的文件路径 + * + */ + private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + String path = null; + + String[] projection = new String[]{MediaStore.Images.Media.DATA}; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + int columnIndex = cursor.getColumnIndexOrThrow(projection[0]); + path = cursor.getString(columnIndex); + } + } catch (Exception e) { + if (cursor != null) { + cursor.close(); + } + } + return path; + } + + /** + * @param uri the Uri to check + * @return Whether the Uri authority is MediaProvider + */ + private static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri the Uri to check + * @return Whether the Uri authority is DownloadsProvider + */ + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } +} diff --git a/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PhotoPicker.java b/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PhotoPicker.java new file mode 100644 index 0000000..e2f81f9 --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PhotoPicker.java @@ -0,0 +1,163 @@ +package org.cocos2dx.javascript; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.StrictMode; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v4.content.FileProvider; + +import org.cocos2dx.lib.Cocos2dxHelper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; + +public class PhotoPicker extends Activity { + static final String KEY_METHOD = "method"; + static final String KEY_INSTANCE = "instance"; + static final String KEY_FILENAME = "filename"; + + static final int REQUEST_TAKE_PHOTO = 1; + static final int REQUEST_PICK_PHOTO = 2; + + private long mInstance = 0; + private String mFilename = ""; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // 解决7.0 "exposed beyond app through ClipData.Item.getUri"的错误 + StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); + StrictMode.setVmPolicy(builder.build()); + builder.detectFileUriExposure(); + + Intent intent = this.getIntent(); + boolean needCheckPermission = Build.VERSION.SDK_INT >= 23; + mInstance = intent.getLongExtra(KEY_INSTANCE, 0); + mFilename = intent.getStringExtra(KEY_FILENAME); + String method = intent.getStringExtra(KEY_METHOD); + switch (method) { + case "takeOrPickPhoto": + break; + case "takePhoto": + if (!needCheckPermission || this.checkCameraPermission()) { + this.takePhoto(); + } + break; + case "pickPhoto": + if (!needCheckPermission || this.checkAlbumPermission()){ + this.pickPhoto(); + } + break; + } + } + + private boolean checkCameraPermission() { + int oldStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); + if (PackageManager.PERMISSION_GRANTED == oldStatus) { + return true; + } + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1); + return false; + } + + private void takePhoto() { + File file = new File(mFilename); + if (file.exists()) { + file.delete(); + } + try { + if (file.createNewFile()) { + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this, "com.congmingpai.mobild.fileprovider", file)); + this.startActivityForResult(intent, REQUEST_TAKE_PHOTO); + return; + } + } catch (Exception e) { + e.printStackTrace(); + } + this.response(""); + } + + private boolean checkAlbumPermission() { + // 似乎不需要申请权限 + return true; + } + + private void pickPhoto(){ + Intent intent = new Intent(Intent.ACTION_PICK); + intent.setType("image/*"); + this.startActivityForResult(intent, REQUEST_PICK_PHOTO); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + for (int i = 0; i < permissions.length; ++i) { + switch (permissions[i]) { + case Manifest.permission.CAMERA: + this.takePhoto(); + break; + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_TAKE_PHOTO: + this.response(RESULT_OK == resultCode ? mFilename : ""); + break; + case REQUEST_PICK_PHOTO: + if (RESULT_OK == resultCode) { + Uri uri = data.getData(); + try { + String filename = PathUtils.getRealPathFromUri(this, uri); + FileInputStream inStream = new FileInputStream(filename); + FileOutputStream outStream = new FileOutputStream(mFilename, false); + byte[] buffer = new byte[inStream.available()]; + inStream.read(buffer); + outStream.write(buffer); + outStream.close(); + inStream.close(); + + this.response(mFilename); + return; + } + catch(Exception e){ + e.printStackTrace(); + } + } + this.response(""); + break; + } + } + + private void response(String filename) { + PhotoPicker.response(mInstance, mFilename); + this.finish(); + } + + static public void takeOrPickPhoto(long instance, String method, String filename) { + Activity current = Cocos2dxHelper.getActivity(); + Intent intent = new Intent(current, PhotoPicker.class); + intent.putExtra(KEY_INSTANCE, instance); + intent.putExtra(KEY_METHOD, method); + intent.putExtra(KEY_FILENAME, filename); + current.startActivity(intent); + } + + static native void response(long instance, String filename); +} \ No newline at end of file From f46182da4bef041885fd7306df2b44ee9e9b8b58 Mon Sep 17 00:00:00 2001 From: GeneralWar Date: Thu, 16 Jan 2020 18:22:50 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E6=96=87=E4=BB=B6=E7=9A=84=E6=9D=83=E9=99=90?= =?UTF-8?q?=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/cocos2dx/javascript/PhotoPicker.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PhotoPicker.java b/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PhotoPicker.java index e2f81f9..e587fec 100644 --- a/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PhotoPicker.java +++ b/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PhotoPicker.java @@ -27,6 +27,9 @@ public class PhotoPicker extends Activity { static final String KEY_INSTANCE = "instance"; static final String KEY_FILENAME = "filename"; + static final int PERMISSION_TAKE = 1; + static final int PERMISSION_PICK = 2; + static final int REQUEST_TAKE_PHOTO = 1; static final int REQUEST_PICK_PHOTO = 2; @@ -68,7 +71,7 @@ private boolean checkCameraPermission() { if (PackageManager.PERMISSION_GRANTED == oldStatus) { return true; } - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1); + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_TAKE); return false; } @@ -91,8 +94,12 @@ private void takePhoto() { } private boolean checkAlbumPermission() { - // 似乎不需要申请权限 - return true; + int oldStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); + if (PackageManager.PERMISSION_GRANTED == oldStatus) { + return true; + } + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_PICK); + return false; } private void pickPhoto(){ @@ -105,10 +112,13 @@ private void pickPhoto(){ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); for (int i = 0; i < permissions.length; ++i) { - switch (permissions[i]) { - case Manifest.permission.CAMERA: + switch (requestCode) { + case PERMISSION_TAKE: this.takePhoto(); break; + case PERMISSION_PICK: + this.pickPhoto(); + break; } } } From 06afb5660656ccb16c7576d0303f68d80cc4eb72 Mon Sep 17 00:00:00 2001 From: GeneralWar Date: Fri, 17 Jan 2020 12:35:49 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frameworks/cocos2d-x/cocos/base/CCDirector.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/frameworks/cocos2d-x/cocos/base/CCDirector.cpp b/frameworks/cocos2d-x/cocos/base/CCDirector.cpp index ce4b574..f76e204 100644 --- a/frameworks/cocos2d-x/cocos/base/CCDirector.cpp +++ b/frameworks/cocos2d-x/cocos/base/CCDirector.cpp @@ -1439,6 +1439,7 @@ void Director::mainLoop() { // version: 1.55.0 // reason: 为实现拍照功能而添加,JAVA层的拍照功能在子线程中完成,而V8不允许跨线程调用Isolate,无法直接执行回调函数,因此添加此缓存,待下一帧执行。 + // 已通过Context.runOnUIThread和主线程创建Handler尝试,无效。 if (_cachedFunctionsNextUpdate.size()) { for (auto iterator : _cachedFunctionsNextUpdate) From 6e54a4a6822d5b9e2e0449a1ea6615f651dfc512 Mon Sep 17 00:00:00 2001 From: GeneralWar Date: Fri, 17 Jan 2020 14:14:25 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E5=92=8C=E6=9D=A1=E4=BB=B6=E7=BC=96=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frameworks/cocos2d-x/cocos/base/CCDirector.cpp | 4 ++++ frameworks/cocos2d-x/cocos/base/CCDirector.h | 6 +++++- .../runtime-src/Classes/sdks/utils/UtilsSdk.mm | 2 +- .../{PicturePicker.mm => PhotoPicker.mm} | 0 .../Smart_Pi.xcodeproj/project.pbxproj | 18 +++++++++--------- 5 files changed, 19 insertions(+), 11 deletions(-) rename frameworks/runtime-src/Classes/sdks/utils/photos/{PicturePicker.mm => PhotoPicker.mm} (100%) diff --git a/frameworks/cocos2d-x/cocos/base/CCDirector.cpp b/frameworks/cocos2d-x/cocos/base/CCDirector.cpp index f76e204..ba58765 100644 --- a/frameworks/cocos2d-x/cocos/base/CCDirector.cpp +++ b/frameworks/cocos2d-x/cocos/base/CCDirector.cpp @@ -1437,6 +1437,7 @@ void Director::mainLoop() } else if (! _invalid) { +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) // version: 1.55.0 // reason: 为实现拍照功能而添加,JAVA层的拍照功能在子线程中完成,而V8不允许跨线程调用Isolate,无法直接执行回调函数,因此添加此缓存,待下一帧执行。 // 已通过Context.runOnUIThread和主线程创建Handler尝试,无效。 @@ -1449,6 +1450,7 @@ void Director::mainLoop() } _cachedFunctionsNextUpdate.clear(); } +#endif drawScene(); @@ -1472,10 +1474,12 @@ void Director::setAnimationInterval(float interval) } } +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) void Director::runInNextUpdate(std::function method) { _cachedFunctionsNextUpdate.push_back(method); } +#endif NS_CC_END diff --git a/frameworks/cocos2d-x/cocos/base/CCDirector.h b/frameworks/cocos2d-x/cocos/base/CCDirector.h index fb22311..c3e6ca8 100644 --- a/frameworks/cocos2d-x/cocos/base/CCDirector.h +++ b/frameworks/cocos2d-x/cocos/base/CCDirector.h @@ -513,8 +513,10 @@ class CC_DLL Director : public Ref void setCullingEnabled (bool enable) { _isCullingEnabled = enable; } bool isCullingEnabled () const { return _isCullingEnabled; } - + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) void runInNextUpdate(std::function method); +#endif protected: void reset(); @@ -638,8 +640,10 @@ class CC_DLL Director : public Ref bool _isCullingEnabled; +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) // 为实现拍照功能而添加,JAVA层的拍照功能在子线程中完成,而V8不允许跨线程调用Isolate,无法直接执行回调函数,因此添加此缓存,待下一帧执行。 std::list> _cachedFunctionsNextUpdate; +#endif private: /** Use std::vector to implement a quick matrix stack. diff --git a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm index 26e6c37..7917983 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm +++ b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm @@ -389,7 +389,7 @@ if (_callbacks.end() != finder) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) - SE_REPORT_ERROR("key [%s] for callback already exists!", key.c_str()); +// SE_REPORT_ERROR("key [%s] for callback already exists!", key.c_str()); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #endif callback(""); diff --git a/frameworks/runtime-src/Classes/sdks/utils/photos/PicturePicker.mm b/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.mm similarity index 100% rename from frameworks/runtime-src/Classes/sdks/utils/photos/PicturePicker.mm rename to frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.mm diff --git a/frameworks/runtime-src/proj.ios_mac/Smart_Pi.xcodeproj/project.pbxproj b/frameworks/runtime-src/proj.ios_mac/Smart_Pi.xcodeproj/project.pbxproj index 658ba41..7e8a49d 100755 --- a/frameworks/runtime-src/proj.ios_mac/Smart_Pi.xcodeproj/project.pbxproj +++ b/frameworks/runtime-src/proj.ios_mac/Smart_Pi.xcodeproj/project.pbxproj @@ -120,7 +120,7 @@ DEFCD3C121D1E13E00A872C6 /* hl_sha384wrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DEFCD39121D1E13E00A872C6 /* hl_sha384wrapper.cpp */; }; DEFCD3C421D1E18700A872C6 /* HashSdk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DEFCD3C221D1E18700A872C6 /* HashSdk.cpp */; }; DEFCD3C521D1E18700A872C6 /* HashSdk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DEFCD3C221D1E18700A872C6 /* HashSdk.cpp */; }; - E20D6CC423CD911900DBFC39 /* PicturePicker.mm in Sources */ = {isa = PBXBuildFile; fileRef = E20D6CC323CD911900DBFC39 /* PicturePicker.mm */; }; + E20825B523D171DC00D373E2 /* PhotoPicker.mm in Sources */ = {isa = PBXBuildFile; fileRef = E20825B323D171DB00D373E2 /* PhotoPicker.mm */; }; E20D6CC723CD972B00DBFC39 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E20D6CC623CD972B00DBFC39 /* Photos.framework */; }; E2735178215492D700BCBE60 /* courses in Resources */ = {isa = PBXBuildFile; fileRef = E2735177215492D700BCBE60 /* courses */; }; E285AE8B2154E017008821D8 /* project.manifest in Resources */ = {isa = PBXBuildFile; fileRef = E285AE8A2154E017008821D8 /* project.manifest */; }; @@ -322,8 +322,8 @@ DEFCD39121D1E13E00A872C6 /* hl_sha384wrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hl_sha384wrapper.cpp; sourceTree = ""; }; DEFCD3C221D1E18700A872C6 /* HashSdk.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = HashSdk.cpp; sourceTree = ""; }; DEFCD3C321D1E18700A872C6 /* HashSdk.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HashSdk.h; sourceTree = ""; }; - E20D6CC323CD911900DBFC39 /* PicturePicker.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = PicturePicker.mm; sourceTree = ""; }; - E20D6CC523CD914700DBFC39 /* PicturePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PicturePicker.h; sourceTree = ""; }; + E20825B323D171DB00D373E2 /* PhotoPicker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PhotoPicker.mm; sourceTree = ""; }; + E20825B423D171DB00D373E2 /* PhotoPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoPicker.h; sourceTree = ""; }; E20D6CC623CD972B00DBFC39 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk/System/Library/Frameworks/Photos.framework; sourceTree = DEVELOPER_DIR; }; E2735177215492D700BCBE60 /* courses */ = {isa = PBXFileReference; lastKnownFileType = folder; name = courses; path = ../../../courses; sourceTree = ""; }; E285AE8A2154E017008821D8 /* project.manifest */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = project.manifest; path = ../../../project.manifest; sourceTree = ""; }; @@ -689,7 +689,7 @@ DE4883DD20FDB31F006717A2 /* utils */ = { isa = PBXGroup; children = ( - E20D6CC223CD90B500DBFC39 /* pictures */, + E20D6CC223CD90B500DBFC39 /* photos */, DE4883DE20FDB31F006717A2 /* UtilsSdk.h */, DE4883DF20FDB31F006717A2 /* UtilsSdk.mm */, ); @@ -767,13 +767,13 @@ path = src; sourceTree = ""; }; - E20D6CC223CD90B500DBFC39 /* pictures */ = { + E20D6CC223CD90B500DBFC39 /* photos */ = { isa = PBXGroup; children = ( - E20D6CC523CD914700DBFC39 /* PicturePicker.h */, - E20D6CC323CD911900DBFC39 /* PicturePicker.mm */, + E20825B423D171DB00D373E2 /* PhotoPicker.h */, + E20825B323D171DB00D373E2 /* PhotoPicker.mm */, ); - path = pictures; + path = photos; sourceTree = ""; }; /* End PBXGroup section */ @@ -961,7 +961,6 @@ 509D4AC917EBB2AB00697056 /* RootViewController.mm in Sources */, DE6EEB5621D36CBB00FA65AE /* SettingsBundleHelper.cpp in Sources */, DE36599F20FF29DB00D046A0 /* BuglySdk.cpp in Sources */, - E20D6CC423CD911900DBFC39 /* PicturePicker.mm in Sources */, DE10A5D720CFFB4A00B856E3 /* WXApiResponseHandler.m in Sources */, DE36599720FF29DB00D046A0 /* CrashReport.mm in Sources */, DEFCD3B421D1E13E00A872C6 /* hl_sha1wrapper.cpp in Sources */, @@ -984,6 +983,7 @@ DECED05A20F8B5F0000DC6B7 /* Reachability.m in Sources */, DE10A5A520CFC37400B856E3 /* SdkManager.cpp in Sources */, 4D3EB2581F8F4A6C007DA644 /* jsb_module_register.cpp in Sources */, + E20825B523D171DC00D373E2 /* PhotoPicker.mm in Sources */, DE10A5DA20CFFB4A00B856E3 /* SendMessageToWXReq+requestWithTextOrMediaMessage.m in Sources */, DEFCD3B821D1E13E00A872C6 /* hl_sha256.cpp in Sources */, DECED06120F8BC08000DC6B7 /* ReachabilitySdk.mm in Sources */, From 551777dcb918f315fd0c22ae1d71853ba2fd5624 Mon Sep 17 00:00:00 2001 From: GeneralWar Date: Fri, 17 Jan 2020 14:50:40 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E4=BB=A5=E5=A4=96?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=9A=84=E4=BB=A3=E7=A0=81=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Classes/sdks/utils/UtilsSdk.mm | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm index 7917983..d67883a 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm +++ b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm @@ -87,6 +87,10 @@ NetworkType type = getNetworkType(); callback(int32tostring(type)); } + else if(method == "getIOSProcessMemoryUsage") { + unsigned long long size = getIOSProcessMemoryUsage(); + callback(uint64tostring(size)); + } #ifdef SDK_BUGLY else if(method == "setBuglyUserID") { setBuglyUserID(params); @@ -154,6 +158,24 @@ return 0; } +unsigned long long UtilsSdk::getIOSProcessMemoryUsage() { +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + task_vm_info info; + mach_msg_type_number_t count = TASK_VM_INFO_COUNT; + int r = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count); + if (r == KERN_SUCCESS) + { + NSLog(@"Memory: %ldd", (unsigned long) info.phys_footprint); + return info.phys_footprint; + } + else + { + return -1; + } +#endif + return 0; +} + unsigned long long UtilsSdk::getFileSystemTotalSize() { #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) From a602c27e30e8699d5ed9504eb2cf848ba8471305 Mon Sep 17 00:00:00 2001 From: GeneralWar Date: Tue, 21 Jan 2020 15:34:50 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=AE=89=E5=8D=93?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E7=9B=B8=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime-src/Classes/sdks/utils/UtilsSdk.h | 8 +- .../Classes/sdks/utils/UtilsSdk.mm | 54 ++-- .../Classes/sdks/utils/photos/PhotoPicker.cpp | 6 +- .../app/AndroidManifest.xml | 6 +- .../app/res/drawable/photo_background.png | Bin 0 -> 59905 bytes .../app/res/drawable/photo_button_cancel.png | Bin 0 -> 4586 bytes .../app/res/drawable/photo_button_capture.png | Bin 0 -> 4086 bytes .../app/res/drawable/photo_button_check.png | Bin 0 -> 3323 bytes .../app/res/values-hdpi/dimens.xml | 9 + .../app/res/values-mdpi/dimens.xml | 9 + .../app/res/values-xhdpi/dimens.xml | 9 + .../app/res/values-xxhdpi/dimens.xml | 9 + .../app/res/values/strings.xml | 1 + .../java/com/congmingpai/mobile/Camera.java | 192 ++++++++++++ .../com/congmingpai/mobile}/PathUtils.java | 2 +- .../com/congmingpai/mobile/PhotoPicker.java | 283 ++++++++++++++++++ .../main/res/layout/activity_photopicker.xml | 69 +++++ .../org/cocos2dx/javascript/PhotoPicker.java | 173 ----------- 18 files changed, 621 insertions(+), 209 deletions(-) create mode 100644 frameworks/runtime-src/proj.android-studio/app/res/drawable/photo_background.png create mode 100644 frameworks/runtime-src/proj.android-studio/app/res/drawable/photo_button_cancel.png create mode 100644 frameworks/runtime-src/proj.android-studio/app/res/drawable/photo_button_capture.png create mode 100644 frameworks/runtime-src/proj.android-studio/app/res/drawable/photo_button_check.png create mode 100644 frameworks/runtime-src/proj.android-studio/app/res/values-hdpi/dimens.xml create mode 100644 frameworks/runtime-src/proj.android-studio/app/res/values-mdpi/dimens.xml create mode 100644 frameworks/runtime-src/proj.android-studio/app/res/values-xhdpi/dimens.xml create mode 100644 frameworks/runtime-src/proj.android-studio/app/res/values-xxhdpi/dimens.xml create mode 100644 frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/Camera.java rename frameworks/runtime-src/proj.android-studio/app/src/{org/cocos2dx/javascript => main/java/com/congmingpai/mobile}/PathUtils.java (99%) create mode 100644 frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/PhotoPicker.java create mode 100644 frameworks/runtime-src/proj.android-studio/app/src/main/res/layout/activity_photopicker.xml delete mode 100644 frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PhotoPicker.java diff --git a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h index 3eaa71a..4f32543 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h +++ b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.h @@ -46,12 +46,14 @@ class UtilsSdk : public Sdk static void setBuglyUserID(const std::string &id); static void setBuglyUserData(const std::string ¶ms); #endif - + + std::string getTimestamp(); + void takeOrPickPhoto(const std::string& method, const std::string& path, const SdkCallback &callback); // 将结果同步至主线程中进行处理 - void callbackToMainThread(const std::string key, const std::string argument); - void invoke(const std::string& key, const std::string& argument); + void invokeCallbackOnMainThread(const std::string key, const std::string argument); + void invokeCallback(const std::string &key, const std::string &argument); }; #endif /* UtilsSdk_hpp */ diff --git a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm index d67883a..8315b1e 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm +++ b/frameworks/runtime-src/Classes/sdks/utils/UtilsSdk.mm @@ -396,7 +396,7 @@ } #endif -void UtilsSdk::takeOrPickPhoto(const std::string& method, const std::string& path, const SdkCallback &callback) +std::string UtilsSdk::getTimestamp() { char buffer[128] = {}; #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) @@ -406,53 +406,55 @@ time(¤t); sprintf(buffer, "%lld", (long long)current); #endif - std::string key = std::string(buffer) + ":takePhoto"; + return buffer; +} + +void UtilsSdk::takeOrPickPhoto(const std::string& method, const std::string& path, const SdkCallback &callback) +{ + std::string timestamp = this->getTimestamp(); + std::string key = std::string(timestamp) + ":" + method; auto finder = _callbacks.find(key); if (_callbacks.end() != finder) { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) -// SE_REPORT_ERROR("key [%s] for callback already exists!", key.c_str()); -#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) -#endif callback(""); return; } _callbacks[key] = callback; - + FileUtils::getInstance()->createDirectory(path); #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) PhotoPicker* picker = [[PhotoPicker alloc] initWithKey:[NSString stringWithUTF8String:key.c_str()] :this]; + if ("takeOrPickPhoto" == method) + { + [picker takeOrPickPhoto:[NSString stringWithUTF8String:(path + "/" + timestamp + ".png").c_str()]]; + } + else if ("takePhoto" == method) + { + [picker takePhoto:[NSString stringWithUTF8String:(path + "/" + timestamp + ".png").c_str()]]; + } + else if ("pickPhoto" == method) + { + [picker pickPhoto:[NSString stringWithUTF8String:(path + "/" + timestamp + ".png").c_str()]]; + } #elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) PhotoPicker* picker = new PhotoPicker(key, this); -#endif if ("takeOrPickPhoto" == method) { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) - [picker takeOrPickPhoto:[NSString stringWithUTF8String:(path + "/" + buffer + ".png").c_str()]]; -#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) - picker->takeOrPickPhoto(path + "/" + buffer + ".png"); -#endif + picker->takeOrPickPhoto(path + "/" + timestamp + ".png"); } else if ("takePhoto" == method) { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) - [picker takePhoto:[NSString stringWithUTF8String:(path + "/" + buffer + ".png").c_str()]]; -#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) - picker->takePhoto(path + "/" + buffer + ".png"); -#endif + picker->takePhoto(path + "/" + timestamp + ".png"); } else if ("pickPhoto" == method) { -#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) - [picker pickPhoto:[NSString stringWithUTF8String:(path + "/" + buffer + ".png").c_str()]]; -#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) - picker->pickPhoto(path + "/" + buffer + ".png"); -#endif + picker->pickPhoto(path + "/" + timestamp + ".png"); } +#endif } -void UtilsSdk::callbackToMainThread(const std::string key, const std::string argument) +void UtilsSdk::invokeCallbackOnMainThread(const std::string key, const std::string argument) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) dispatch_async(dispatch_get_main_queue(), ^{ @@ -463,12 +465,12 @@ Director::getInstance()->runInNextUpdate( [=] { - this->invoke(key, argument); + this->invokeCallback(key, argument); }); #endif } -void UtilsSdk::invoke(const std::string& key, const std::string& argument) +void UtilsSdk::invokeCallback(const std::string &key, const std::string &argument) { auto finder = _callbacks.find(key); if (_callbacks.end() != finder) diff --git a/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.cpp b/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.cpp index 82950d7..66ee658 100644 --- a/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.cpp +++ b/frameworks/runtime-src/Classes/sdks/utils/photos/PhotoPicker.cpp @@ -13,7 +13,7 @@ using namespace cocos2d; extern "C" { - void Java_org_cocos2dx_javascript_PhotoPicker_response(JNIEnv *env, jobject thiz, long long handle, jstring jFilename) + void Java_com_congmingpai_mobile_PhotoPicker_response(JNIEnv *env, jobject thiz, long long handle, jstring jFilename) { cocos2d::log("Java_org_cocos2dx_javascript_PhotoPicker_response"); PhotoPicker* picker = reinterpret_cast(handle); @@ -43,7 +43,7 @@ void PhotoPicker::pickPhoto(const std::string &filename) void PhotoPicker::callActivity(const std::string &method) { JniMethodInfo minfo; - if (JniHelper::getStaticMethodInfo(minfo, "org/cocos2dx/javascript/PhotoPicker", "takeOrPickPhoto", "(JLjava/lang/String;Ljava/lang/String;)V")) { + if (JniHelper::getStaticMethodInfo(minfo, "com/congmingpai/mobile/PhotoPicker", "takeOrPickPhoto", "(JLjava/lang/String;Ljava/lang/String;)V")) { jlong instance = reinterpret_cast(this); JNIEnv* env = JniHelper::getEnv(); jstring jMethod = env->NewStringUTF(method.c_str()); @@ -57,5 +57,5 @@ void PhotoPicker::callActivity(const std::string &method) void PhotoPicker::response(const std::string &filename) { UtilsSdk* utils = reinterpret_cast(_owner); - utils->callbackToMainThread(_key, filename); + utils->invokeCallbackOnMainThread(_key, filename); } diff --git a/frameworks/runtime-src/proj.android-studio/app/AndroidManifest.xml b/frameworks/runtime-src/proj.android-studio/app/AndroidManifest.xml index beaf553..8b81db8 100755 --- a/frameworks/runtime-src/proj.android-studio/app/AndroidManifest.xml +++ b/frameworks/runtime-src/proj.android-studio/app/AndroidManifest.xml @@ -41,7 +41,7 @@ + android:theme="@android:style/Theme.Light.NoTitleBar.Fullscreen"> diff --git a/frameworks/runtime-src/proj.android-studio/app/res/drawable/photo_background.png b/frameworks/runtime-src/proj.android-studio/app/res/drawable/photo_background.png new file mode 100644 index 0000000000000000000000000000000000000000..3798d096b97aa8ff56e4c75ae3ff49aca27e4318 GIT binary patch literal 59905 zcmbSzRalg3+vqbiBB_FsBZ#21NT&iSN=T!`&`5X3j3S_*geW16bc50X|1;r(A3BmvUj~%+r z)xt_rQ}N!vcR|0&us?Hmca{_o@bdEF_Y&rJalPohg3ryz(cRRW z&(V$JKPM3zpi%E_Hnip(6n@O@^CeW=wZe2pUn{N{@)kj7edfTs=L}i zL@~8jbTapFuyk}+R+M3fzVKVvSV&rkn+uBHHW%YF6PFO=yKNyT%x8xGLrhe}+)6^! z%JjB{<$uok-}c`UyQQcga!=uwlHxrfA*DOwLbpWj-VwVibWc%9P*_3yKYNuO-P}zb z%`N|Pw+(dnzkBcgKle&1xLTUJJGp8*Iobb*3m!jna(8lj=Hv`lP=Ir*nwr}<;(xz^ zr_n!8Rb2AH3Gjj`5J_|E*alYHPL?rmcMI>(XS%}^i z6gC$Z6SfexVE=c&#s4qz1R$UU@SO60an67K31Ub5kN=ee=$HRWlBFXgKCY0QOp^|7 z1F&>WS@Djx_xQ#X>3hpjqvcZ^xtR7^Qc&%OAkHW69G=A5b6n|&#N;Ih zYv-!gQa-Dr14qV-Jqys&`2oEWM;kMk?S>*dM`{Ha-I8CUcRp;ic)R^c5Hm&FX1w!) z9VyRinZOEN+OG1IXLx16EtJTQBA1LJ0M*; z?0fZXI~7#jJ@^a$Z%cEfTAc4Qn)K`^A{g-}0MAHty0ywlBN{vEGq?Q%btc@(JgAv3%JE zXCL^C>_%rJr#)3mPZNFzs)h!zi%K4SLYGv#yZo}#^>tl+=;@^vMx}#vcQasRbXj~J zy;T77IEEg9HzB}8lnC|e?Y7U|O@z?!#^d6dvFNuR`B;^{s|C}K7ayJS44hx0KHNuI zZpz`8H~WwA)0OoN1|BT2ai_qz-d8y}Ucg?-K0R&IZd%8tQfndql8@plP~@$r(n5yk z_EIetB}$f;KXkBWbZ?r;MI)N1KKBm>l7s)Q?0o^(e!Eth;ldleF6Ln&k{FQru!JHP zL24bF(5!;_3#fY#pphB0!=-y0*LB7mh_p2E_LY3QRG==~Yd=9ZF-!NC?)sNM-QU0V zCn%SIrnS6AASzssX2YsOsjFedL!!s!*Ee$fBl=hf7SN7+t(fxE8Og(y^@Jw zz@Wz}S#$uw-zAg;^1KP#C;eMZ+_8IcKIIuL>mQZS9yKNj0*=8IIQ$wg#R0y*JAtSZ zQOsZGPF!LaYgEHe6Swvcrr2qKnezS+!uEUE@;(efVrcxUu$(uVGN>|1O7Q^&{(u!$ z7@S^J{WJ?*65n&hY;=Z~sZb LJyW@B;pODH#z;|8`50S^m>>`_1Kw?wf6u7HOgZ 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≥I8um|1!f&2?@*Q-hHn>e$O2*p`)Cu%B_kD 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 iOBey>WiT$_{3O zG!!JYmAYLEflxpUbc|Kiy>&>D|7idKQanCBesgn^#3VzZP_ePG7yPf1a!7o3cJ}@I zcP1vLi#RC=hr`d$&;NBZGBW<<3%F26N5_+s6B76K_Ii7JFDxvOxT2zhlauqJGfA;n zEJ-iS%F2qAu(7d0axXeuD5-pGY|PcwH7F=ZT3Xu1#%6hWnY5Ifn_E{`x3jY|Jv}`; zJ3Bl){NGh1#^dp%1CnY51O!NKS6A0XLR3@~3DVQk|9|Jk5Fp00%fkrNF9Aa`aEAj( zT>+9@;MWe&q5*C;;OrFK5ybH^00S}rXV~Rs1jolf7Yaz1aCiXp?gJlZqAvN~b$UP` zfSqk%s0z%f!SUgN@FgJ53+{0MWhT(}4s^AF>QbO|8A!7OS=M7&Dqx}kdOObt{sAgd zpt1;bwt}J@;4Ms0f&%|1pg0$(O9L4;Fg6SpJ|0QX?kR}l?^1*A_dw|uAmD)nHy9rU zL;V1^3oPk@9Sbm}06&%v#c9F*-oCCf*xERBc(}{Y3{)6F(kqZ0i;oEf_a%U)9Js{? z7UqG}pI~kV6l4KCCeGfR(8; zEpnX5RYG&a+2|1Pbin<^L8z@HuCL*4@q@i>5G)7sy})=IaD@?9mcj20FkA;R9Kf?X zARKe_*BqEc8$EGY=8MXJrXL z!lA;Xmt^l}sAmMBG!mc|z2U5Cq@T6?F)=3-dMwuSP{rUB>n4kke?hc_S?1F=OAp{Y z(NXw2?Qv6iRqpnZS0-6ul94)J%`2{O9(Q13Pyvy1?L}|bK92QH2FeG_EVQL4#kShn zrd7H;#>V{|gg|IDG!e?ikH0oO3JhlL=jmP@zM7eY%s?U$WoDb597NlHjv8k1(}69y zl)LsSD#d(A7{4eIp(T)JOT%w^xgWvGCcvZ|UNKj%Q1JUoNGJXXi_L44LkFQX&xlPe zQv0W0N9x9YxA362-G?2}PqNd`GqwlO zN|CHq5=)YY{F*WQ(ODU6VyG@;o1%5S+nw<@5zmiDwI|l1%D3R90;wTITUkH4Cfe9) zMl7;;`O>;?c{znE?&;=j&54a<7%cc^LDO?TZ_zxb52X^)A24sJN-_7-vvg9-BpP9P z$IV78V2E4Z?0tH_!t-|C`{qdy8VH2K=3q~20mik_nRO4#o3ADFWU8A^o2>exVm`<- z8sY1l3<&cM@r7h1AxrT3-EU_xtZYj3sk~oi_0}YNO#`c})76_&-;49>;$2jw-xUbK7F*n|8j{epi776`wi>a*00Z?IUNOky69>F z8H(fTw#lQ0ZP6bZZ$5Dyk(-{thQyHE^NcRSUK`y~QPtvsTg^;2dH(Q#YL_0U?mnF4 zRKHn-&wOv&gX%OJ*1k@mkEMKh&xZYbEWYM>-;)468Mrru8s9mS*Up0MV_ez)Gd98+$j{CH+W2&a_qTF zqk;G`iJ~{+V^Uv)x%R5z57*GA8fg{hC>){B^hI`j>6(+OP)6TzjZWADf(>&b z+u9m?YLzW$C*Kn>|4B4^m&_42h$I&#uo}T)hhGy}{=ihF@PFP=pRbqX^kRSNrU?%= zLgL0W{e?zPVB7=c#X>k;%)ay1)FT_w9%&+r$s)VmrzdEMrz~L$?(!`{!&)*duB^6c zK*dB`c(XJ;Zqk{4ksgMQ--vAFKk0;&?3$aH%w|#G)EstrAZ`86qFLA2ri!l0oc=h~ zeio0%s>VS%E#}PYZfK=T1ki=q4mhQp{*uziSa$_RPksFh${7lcFI#u#6+kU!4){tW zw%6#-`)AdJE;eS*K96bqvSI9>+@HutJ+QTUJ)ah1BvNe|YHN1&WrWq~8(Ur_s(#@Y z*hU9~I#Y+>2$wM2JEI4tv_A`X-ZE`XGNJZ3MJ<#Yy6Snhg;hUrl#u0bNwXQI4b;DC zx_UZL!2ICax`Z%*lF#>S4LHO_7?o_TdLF(lu4$G_`SB-auz>Q_q3?`iRFWsvuzB~*?v&V+A|uFz@}Q|g}L%3BCikLu6Gpd%s@%gvNnWO+3N8Iw9JY~yW) z*$adV*oE1>;>la~GN+z$I)Td~?7vOrTb?pLoxrge!a}Lz74FvN_$0NE4|@DvE&gDp zt{@KOL%uF5+T87H_b`+@Y+(%gmjC#b#Zv66iQq}ShMUUU0g$##y6zwdBIH+sihBIi z5*cQrIXQv0TZueLt`$*ipB!DRNKB?sYm2luXO5MFIJ!w@GP8Nb7F|_xsx$lS{qZ$r z1!BmEtWU`zX~E-0+=sKaekvqg%bDx9RnKPL`{bI%x0~!;>DSsH@nM8UMPvnB-E(qS zAxT2!E65<-*l5E&l`gDZa3QlD3BT0EFk<>3GC$ji-G2pTxx8|e56yKjOs9`hA$^1Z&JPAEer~ zlZ?c0w=BU_h7zHFi^p-&=Xz^j%6BJop^`+9-$iYj%H|--DpwR(9V7Sq?X!z-`;R9S zhLNqnX+~p>jHlL^T-d1LB5ynfI@Tc$ytP5d%Q?i-2uP~&(Jh45`5nu|%U1jnQe>hi z-_ltds}4{6`Bjo&mQax?sv_v-r8aed-ew4&;2KD(%HbNfUSs^-^%=8knc&c4$(iV} z)~NI1&-v{xO?0N~q-8ly`<-#CgN1S++6pZg__MtH=g5k&z+6nNZG6y#e0)3R3YL+F z)gfcd|C)VsgvO1}A&tRw<7qRSoHYsc&P-{2ccrwkt2x=IoSbY;*}u9g*;uCs)EMry zIB~BuIdPA8%zrEKQ&hIo|9H=Ucl4SE);9kyw_CX^=!rJj%y31Yjgi;%Tu(Z=KF>D| zZ?jvJC-%#C1SDXt$~%}zJ``G9Xl{t}AUyPN$Bx_~roOUclsOWeg5|>%x?^P@J!$&H z;nrUu?)X(TgE}v#n@`f?%heWM`;npRuUW3O&(X#@@@wUNqq0|Hw#v?0UJ$`c+_+KI zeG?H%MSh*I;!k8z8SzUCsG{#HbKS`?i0D&iRt|u21s)2xAT=MNq(o_kLw+Skj-;jE zom7(_LZ>#~O!al_w!kqWOozGrNBw7=C4ap5=|3CKHK5DAnGR$z90-;7E5jeB42`D5 zy7w;Oo{ew#z+X<&?zI!`h4>al5qc2=3Q*?7`vNZ%@=ES&u=BHS_EP(NEu=?`Z*Os( zocT1f?48Ol-dFS@Jd@}-wk@={OrNvKH1=-JYFr5ybnG{M?|EG4kCXdGL?glW`450> YM*da+MCZ`||GoyAs(Of872Ama0Afwk4gdfE literal 0 HcmV?d00001 diff --git a/frameworks/runtime-src/proj.android-studio/app/res/values-hdpi/dimens.xml b/frameworks/runtime-src/proj.android-studio/app/res/values-hdpi/dimens.xml new file mode 100644 index 0000000..771acb9 --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/res/values-hdpi/dimens.xml @@ -0,0 +1,9 @@ + + + + 150px + 75px + 81.75px + 109.5px + 114px + diff --git a/frameworks/runtime-src/proj.android-studio/app/res/values-mdpi/dimens.xml b/frameworks/runtime-src/proj.android-studio/app/res/values-mdpi/dimens.xml new file mode 100644 index 0000000..54b2f5b --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/res/values-mdpi/dimens.xml @@ -0,0 +1,9 @@ + + + + 100px + 50px + 54.5px + 73px + 76px + diff --git a/frameworks/runtime-src/proj.android-studio/app/res/values-xhdpi/dimens.xml b/frameworks/runtime-src/proj.android-studio/app/res/values-xhdpi/dimens.xml new file mode 100644 index 0000000..492a889 --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/res/values-xhdpi/dimens.xml @@ -0,0 +1,9 @@ + + + + 200px + 100px + 109px + 146px + 152px + diff --git a/frameworks/runtime-src/proj.android-studio/app/res/values-xxhdpi/dimens.xml b/frameworks/runtime-src/proj.android-studio/app/res/values-xxhdpi/dimens.xml new file mode 100644 index 0000000..5f854f2 --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/res/values-xxhdpi/dimens.xml @@ -0,0 +1,9 @@ + + + + 300px + 150px + 163.5px + 219px + 228px + diff --git a/frameworks/runtime-src/proj.android-studio/app/res/values/strings.xml b/frameworks/runtime-src/proj.android-studio/app/res/values/strings.xml index fd29c9c..63015e9 100755 --- a/frameworks/runtime-src/proj.android-studio/app/res/values/strings.xml +++ b/frameworks/runtime-src/proj.android-studio/app/res/values/strings.xml @@ -1,3 +1,4 @@ STABLE + com.congmingpai.mobile.fileprovider diff --git a/frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/Camera.java b/frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/Camera.java new file mode 100644 index 0000000..51f2651 --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/Camera.java @@ -0,0 +1,192 @@ +package com.congmingpai.mobile; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.hardware.camera2.*; +import android.media.Image; +import android.media.ImageReader; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class Camera extends CameraDevice.StateCallback { + static public abstract class Callback { + public abstract void response(@Nullable Bitmap bitmap); + } + + private CameraManager mCameraManager = null; + private CameraDevice mCameraDevice = null; + + private SurfaceHolder mTarget = null; + private Context mOwner = null; + + private Callback mCallback = null; + + private CameraCaptureSession mCaptureSession = null; + private ImageReader mImageReader = null; + + private Bitmap mResult = null; + public Bitmap getResult() { return mResult; } + + public Camera(@NonNull Context owner, @NonNull SurfaceHolder target, @NonNull Callback callback){ + mOwner = owner; + mTarget = target; + mCallback = callback; + } + + @SuppressLint("MissingPermission") + public boolean open(){ + Log.d("Camera", "try to open camera"); + if (null != mCameraDevice){ + this.captureToRender(); + return true; + } + +// CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(camera.getId()); +// StreamConfigurationMap configurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); +// Size[] supportedSizes = configurationMap.getOutputSizes(ImageFormat.JPEG); +// mTarget.setFixedSize(supportedSizes[0].getWidth(), supportedSizes[0].getHeight()); + mTarget.setFixedSize(1280, 720); + + CameraManager manager = mCameraManager = mOwner.getSystemService(CameraManager.class); + try { + // 使用前置摄像头 + String targetId = ""; + String[] deviceIds = manager.getCameraIdList(); + for (int i = 0; i < deviceIds.length; ++i) { + CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(deviceIds[i]); + if (CameraCharacteristics.LENS_FACING_FRONT == characteristics.get(CameraCharacteristics.LENS_FACING)) { + targetId = deviceIds[i]; + break; + } + } + manager.openCamera("" == targetId ? deviceIds[0] : targetId, this, null); + return true; + } catch (CameraAccessException e) { + e.printStackTrace(); + mCallback.response(null); + } + return false; + } + + public boolean capture(){ + if (null == mCaptureSession){ + return false; + } + try { + mCaptureSession.stopRepeating(); + + CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + requestBuilder.addTarget(mImageReader.getSurface()); + mCaptureSession.capture(requestBuilder.build(), mCaptureCallback, null); + } catch (CameraAccessException e) { + e.printStackTrace(); + mCallback.response(null); + return false; + } + return true; + } + + private CameraCaptureSession.StateCallback mStateCallback = new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + try { + CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + requestBuilder.addTarget(mTarget.getSurface()); + session.setRepeatingRequest(requestBuilder.build(), mCaptureCallback, null); + mCaptureSession = session; + } catch (CameraAccessException e) { + e.printStackTrace(); + mCallback.response(null); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession session) { + mCallback.response(null); + } + }; + + private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { + super.onCaptureFailed(session, request, failure); + mCallback.response(null); + } + }; + + private ImageReader.OnImageAvailableListener mImageAvailableListener = new ImageReader.OnImageAvailableListener() { + @Override + public void onImageAvailable(ImageReader reader) { + Bitmap bitmap = null; + try { + Image image = reader.acquireNextImage(); + + Image.Plane[] planes = image.getPlanes(); + ByteBuffer byteBuffer = planes[0].getBuffer(); + byte[] buffer = new byte[byteBuffer.limit()]; + byteBuffer.get(buffer); + bitmap = Bitmap.createBitmap(BitmapFactory.decodeByteArray(buffer, 0, buffer.length)); + bitmap = Bitmap.createScaledBitmap(bitmap, -bitmap.getWidth(), bitmap.getHeight(), false); + + image.close(); + }catch (Exception e){ + e.printStackTrace(); + } + mResult = bitmap; + mCallback.response(bitmap); + } + }; + + private void captureToRender() { + if (null == mCameraDevice){ + return; + } + + mResult = null; + + try { + Log.d("Camera", "try to create capture session"); + List surfaces = new ArrayList(); + Surface surface = mTarget.getSurface(); + surfaces.add(surface); + + Rect rect = mTarget.getSurfaceFrame(); + ImageReader imageReader = mImageReader = ImageReader.newInstance(rect.width(), rect.height(), ImageFormat.JPEG, 1); + imageReader.setOnImageAvailableListener(mImageAvailableListener, null); + surfaces.add(imageReader.getSurface()); + + mCameraDevice.createCaptureSession(surfaces, mStateCallback, null); + } catch (CameraAccessException e) { + e.printStackTrace(); + mCallback.response(null); + } + } + + @Override + public void onOpened(@NonNull CameraDevice camera) { + Log.d("Camera", "camera open"); + mCameraDevice = camera; + this.captureToRender(); + } + + @Override + public void onDisconnected(@NonNull CameraDevice camera) { + + } + + @Override + public void onError(@NonNull CameraDevice camera, int error) { + + } +} diff --git a/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PathUtils.java b/frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/PathUtils.java similarity index 99% rename from frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PathUtils.java rename to frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/PathUtils.java index 71ee496..9ec2f38 100644 --- a/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/PathUtils.java +++ b/frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/PathUtils.java @@ -1,4 +1,4 @@ -package org.cocos2dx.javascript; +package com.congmingpai.mobile; import android.annotation.SuppressLint; diff --git a/frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/PhotoPicker.java b/frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/PhotoPicker.java new file mode 100644 index 0000000..6a8a2dd --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/src/main/java/com/congmingpai/mobile/PhotoPicker.java @@ -0,0 +1,283 @@ +package com.congmingpai.mobile; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.net.Uri; +import android.os.Bundle; +import android.os.StrictMode; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; + +import com.congmingpai.mobile.stable.R; + +import org.cocos2dx.lib.Cocos2dxHelper; + +import java.io.FileInputStream; +import java.io.FileOutputStream; + +public class PhotoPicker extends Activity implements View.OnClickListener { + static final String KEY_METHOD = "method"; + static final String KEY_INSTANCE = "instance"; + static final String KEY_FILENAME = "filename"; + + static final String METHOD_TAKE = "takePhoto"; + static final String METHOD_PICK = "pickPhoto"; + + static final int PERMISSION_TAKE = 1; + static final int PERMISSION_PICK = 2; + + static final int REQUEST_TAKE_PHOTO = 1; + static final int REQUEST_PICK_PHOTO = 2; + + private long mInstance = 0; + private String mFilename = ""; + + private Button mTakeButton = null; + private Button mCheckButton = null; + private Button mCancelButton = null; + private SurfaceView mSurfaceView = null; + + private Camera mCamera = null; + private PhotoPicker mSelf = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mSelf = this; + + Intent intent = this.getIntent(); + mInstance = intent.getLongExtra(KEY_INSTANCE, 0); + mFilename = intent.getStringExtra(KEY_FILENAME); + String method = intent.getStringExtra(KEY_METHOD); + switch (method) { + case "takeOrPickPhoto": + break; + case METHOD_TAKE: + if (this.checkCameraPermission()) { + this.takePhoto(); + } + break; + case METHOD_PICK: + // 解决7.0 "exposed beyond app through ClipData.Item.getUri"的错误 + StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); + StrictMode.setVmPolicy(builder.build()); + builder.detectFileUriExposure(); + + if (this.checkAlbumPermission()){ + this.pickPhoto(); + } + break; + } + } + + private void initializeContent() { + Log.d("PhotoPicker", "initializeContent"); + Point screenSize = new Point(); + this.getWindowManager().getDefaultDisplay().getSize(screenSize); + + WindowManager.LayoutParams windowParams = this.getWindow().getAttributes(); + // 锁定宽高比,与游戏内一致 + windowParams.height = screenSize.y; + windowParams.width = (int)(windowParams.height * 1280.0 / 720); + // 隐藏下方导航栏,全屏显示 + windowParams.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE; // SYSTEM_UI_FLAG_IMMERSIVE为沉浸式体验,点击也不显示导航栏 + + this.setContentView(R.layout.activity_photopicker); + + mTakeButton = (Button)this.findViewById(R.id.photo_button_take_photo); + mTakeButton.setOnClickListener(this); + mTakeButton.setVisibility(View.INVISIBLE); + mCheckButton = (Button)this.findViewById(R.id.photo_button_check); + mCheckButton.setVisibility(View.INVISIBLE); + mCheckButton.setOnClickListener(this); + mCancelButton = (Button)this.findViewById(R.id.photo_button_cancel); + mCancelButton.setVisibility(View.INVISIBLE); + mCancelButton.setOnClickListener(this); + + Log.d("PhotoPicker", "initialize surface view"); + mSurfaceView = (SurfaceView)this.findViewById(R.id.photo_surface_view); + SurfaceHolder holder = mSurfaceView.getHolder(); + holder.setKeepScreenOn(true); + holder.addCallback(mSurfaceHolderCallback); +// holder.lockCanvas(); + } + + private Camera.Callback mCameraCallback = new Camera.Callback() { + @Override + public void response(Bitmap bitmap) { + if (null == bitmap) { + // 理论上的拍照失败 + mTakeButton.setVisibility(View.VISIBLE); + } + else { + mCheckButton.setVisibility(View.VISIBLE); + mCancelButton.setVisibility(View.VISIBLE); + } + } + }; + + private SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.d("PhotoPicker", "surface view created"); + mTakeButton.setVisibility(View.VISIBLE); + + mCamera = new Camera(mSelf, holder, mCameraCallback); + mCamera.open(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + + } + }; + + private void initializeCamera() { + + } + + @Override + public void onClick(final View v) { + if (v == mTakeButton && null != mCamera) { + if (mCamera.capture()) { + mTakeButton.setVisibility(View.INVISIBLE); + } + else { + this.response(""); + } + } + if (v == mCancelButton && null != mCamera) { + mCamera.open(); + mTakeButton.setVisibility(View.VISIBLE); + mCheckButton.setVisibility(View.INVISIBLE); + mCancelButton.setVisibility(View.INVISIBLE); + } + if (v == mCheckButton && null != mCamera) { + try { + Bitmap bitmap = mCamera.getResult(); + FileOutputStream outStream = new FileOutputStream(mFilename, false); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream); + outStream.close(); + mSelf.response(mFilename); + } catch (Exception e) { + e.printStackTrace(); + mSelf.response(""); + } + } + } + + private boolean checkCameraPermission() { + // TODO: 也许可以使用this.getApplicationContext()替代? + int oldStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); + if (PackageManager.PERMISSION_GRANTED == oldStatus) { + return true; + } + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_TAKE); + return false; + } + + private void takePhoto() { + this.initializeContent(); + this.initializeCamera(); + } + + private boolean checkAlbumPermission() { + int oldStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); + if (PackageManager.PERMISSION_GRANTED == oldStatus) { + return true; + } + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_PICK); + return false; + } + + private void pickPhoto(){ + Intent intent = new Intent(Intent.ACTION_PICK); + intent.setType("image/*"); + this.startActivityForResult(intent, REQUEST_PICK_PHOTO); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + for (int i = 0; i < permissions.length; ++i) { + switch (requestCode) { + case PERMISSION_TAKE: + this.takePhoto(); + break; + case PERMISSION_PICK: + this.pickPhoto(); + break; + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_TAKE_PHOTO: + this.response(RESULT_OK == resultCode ? mFilename : ""); + break; + case REQUEST_PICK_PHOTO: + if (RESULT_OK == resultCode) { + Uri uri = data.getData(); + try { + String filename = PathUtils.getRealPathFromUri(this, uri); + FileInputStream inStream = new FileInputStream(filename); + FileOutputStream outStream = new FileOutputStream(mFilename, false); + byte[] buffer = new byte[inStream.available()]; + inStream.read(buffer); + outStream.write(buffer); + outStream.close(); + inStream.close(); + + this.response(mFilename); + return; + } + catch(Exception e){ + e.printStackTrace(); + } + } + this.response(""); + break; + } + } + + private void response(String filename) { + PhotoPicker.response(mInstance, mFilename); + this.finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + this.response(""); + } + + static public void takeOrPickPhoto(long instance, String method, String filename) { + Activity current = Cocos2dxHelper.getActivity(); + Intent intent = new Intent(current, PhotoPicker.class); + intent.putExtra(KEY_INSTANCE, instance); + intent.putExtra(KEY_METHOD, method); + intent.putExtra(KEY_FILENAME, filename); + current.startActivity(intent); + } + + static native void response(long instance, String filename); +} \ No newline at end of file diff --git a/frameworks/runtime-src/proj.android-studio/app/src/main/res/layout/activity_photopicker.xml b/frameworks/runtime-src/proj.android-studio/app/src/main/res/layout/activity_photopicker.xml new file mode 100644 index 0000000..1751d32 --- /dev/null +++ b/frameworks/runtime-src/proj.android-studio/app/src/main/res/layout/activity_photopicker.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + +