├── src ├── ios │ ├── Screenshot.h │ └── Screenshot.m ├── osx │ ├── Screenshot.h │ └── Screenshot.m └── android │ ├── PermissionHelper.java │ └── Screenshot.java ├── package.json ├── LICENSE ├── www └── Screenshot.js ├── plugin.xml └── README.md /src/ios/Screenshot.h: -------------------------------------------------------------------------------- 1 | // 2 | // Screenshot.h 3 | // 4 | // Created by Simon Madine on 29/04/2010. 5 | // Copyright 2010 The Angry Robot Zombie Factory. 6 | // - Converted to Cordova 1.6.1 by Josemando Sobral. 7 | // MIT licensed 8 | // 9 | 10 | #import 11 | #import 12 | #import 13 | 14 | @interface Screenshot : CDVPlugin { 15 | } 16 | 17 | //- (void)saveScreenshot:(NSArray*)arguments withDict:(NSDictionary*)options; 18 | - (void)saveScreenshot:(CDVInvokedUrlCommand*)command; 19 | - (void)getScreenshotAsURI:(CDVInvokedUrlCommand*)command; 20 | @end 21 | -------------------------------------------------------------------------------- /src/osx/Screenshot.h: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | #import 21 | 22 | @interface Screenshot : CDVPlugin { 23 | } 24 | 25 | - (void) saveScreenshot: (CDVInvokedUrlCommand*)command; 26 | - (void) getScreenshotAsURI: (CDVInvokedUrlCommand*)command; 27 | @end 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.darktalker.cordova.screenshot", 3 | "version": "0.1.6", 4 | "description": "screenshot PhoneGap Plugin for Android", 5 | "cordova": { 6 | "id": "com.darktalker.cordova.screenshot", 7 | "platforms": [ 8 | "android", 9 | "ios", 10 | "osx" 11 | ] 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/gitawego/cordova-screenshot.git" 16 | }, 17 | "keywords": [ 18 | "cordova", 19 | "screenshot", 20 | "ecosystem:cordova", 21 | "cordova-android", 22 | "cordova-ios", 23 | "cordova-osx" 24 | ], 25 | "engines": [ 26 | { 27 | "name": "cordova", 28 | "version": ">=3.0.0" 29 | } 30 | ], 31 | "author": "Hongbo LU", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/gitawego/cordova-screenshot/issues" 35 | }, 36 | "homepage": "https://github.com/gitawego/cordova-screenshot#readme", 37 | "devDependencies": { 38 | "sync-cordova-xml": "^0.4.0" 39 | }, 40 | "scripts": { 41 | "version": "sync-cordova-xml package.json plugin.xml --output plugin.xml && git add plugin.xml" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014 Hongbo LU 2 | Copyright (C) 2012 30ideas (http://30ide.as) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in th 6 | e Software without restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 8 | Software, and to permit persons to whom the Software is furnished to do so, subj 9 | ect to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all c 12 | opies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI 15 | ED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR 16 | A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYR 17 | IGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WIT 19 | H THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /www/Screenshot.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is adapted from the work of Michael Nachbaur 3 | * by Simon Madine of The Angry Robot Zombie Factory 4 | * - Converted to Cordova 1.6.1 by Josemando Sobral. 5 | * - Converted to Cordova 2.0.0 by Simon MacDonald 6 | * 2012-07-03 7 | * MIT licensed 8 | */ 9 | var exec = require('cordova/exec'), formats = ['png','jpg']; 10 | module.exports = { 11 | save:function(callback,format,quality, filename) { 12 | format = (format || 'png').toLowerCase(); 13 | filename = filename || 'screenshot_'+Math.round((+(new Date()) + Math.random())); 14 | if(formats.indexOf(format) === -1){ 15 | return callback && callback(new Error('invalid format '+format)); 16 | } 17 | quality = typeof(quality) !== 'number'?100:quality; 18 | exec(function(res){ 19 | callback && callback(null,res); 20 | }, function(error){ 21 | callback && callback(error); 22 | }, "Screenshot", "saveScreenshot", [format, quality, filename]); 23 | }, 24 | 25 | URI:function(callback, quality){ 26 | quality = typeof(quality) !== 'number'?100:quality; 27 | exec(function(res){ 28 | callback && callback(null, res); 29 | }, function(error){ 30 | callback && callback(error); 31 | }, "Screenshot", "getScreenshotAsURI", [quality]); 32 | 33 | }, 34 | 35 | URISync:function(callback,quality){ 36 | var method = navigator.userAgent.indexOf("Android") > -1 ? "getScreenshotAsURISync" : "getScreenshotAsURI"; 37 | quality = typeof(quality) !== 'number'?100:quality; 38 | exec(function(res){ 39 | callback && callback(null,res); 40 | }, function(error){ 41 | callback && callback(error); 42 | }, "Screenshot", method, [quality]); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.darktalker.cordova.screenshot 4 | screenshot PhoneGap Plugin for Android 5 | MIT 6 | cordova,screenshot,ecosystem:cordova,cordova-android,cordova-ios,cordova-osx 7 | git+https://github.com/gitawego/cordova-screenshot.git 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | https://github.com/gitawego/cordova-screenshot/issues 48 | Hongbo LU 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cordova-screenshot 2 | ================== 3 | 4 | [![NPM version](http://img.shields.io/npm/v/com.darktalker.cordova.screenshot.svg?style=flat)](https://www.npmjs.com/package/com.darktalker.cordova.screenshot) 5 | 6 | 7 | The Screenshot plugin allows your application to take screenshots of the current screen and save them into the phone. 8 | 9 | ## how to install 10 | 11 | install it via cordova cli 12 | 13 | ``` 14 | cordova plugin add https://github.com/gitawego/cordova-screenshot.git 15 | ``` 16 | 17 | notice: 18 | in iOS, only jpg format is supported 19 | in Android, the default WebView and [Crosswalk](https://crosswalk-project.org/documentation/cordova.html) are both supported 20 | 21 | ## usage 22 | 23 | 24 | ```js 25 | navigator.screenshot.save(function(error,res){ 26 | if(error){ 27 | console.error(error); 28 | }else{ 29 | console.log('ok',res.filePath); 30 | } 31 | }); 32 | ``` 33 | take screenshot with jpg and custom quality 34 | ```js 35 | navigator.screenshot.save(function(error,res){ 36 | if(error){ 37 | console.error(error); 38 | }else{ 39 | console.log('ok',res.filePath); 40 | } 41 | },'jpg',50); 42 | ``` 43 | 44 | define a filename 45 | ```js 46 | navigator.screenshot.save(function(error,res){ 47 | if(error){ 48 | console.error(error); 49 | }else{ 50 | console.log('ok',res.filePath); //should be path/to/myScreenshot.jpg 51 | } 52 | },'jpg',50,'myScreenShot'); 53 | ``` 54 | 55 | screenshot files are stored in /sdcard/Pictures for android. 56 | 57 | take screenshot and get it as Data URI 58 | ```js 59 | navigator.screenshot.URI(function(error,res){ 60 | if(error){ 61 | console.error(error); 62 | }else{ 63 | html = ''; 64 | document.body.innerHTML = html; 65 | } 66 | },50); 67 | ``` 68 | 69 | ## usage in AngularJS 70 | 71 | ```js 72 | .service('$cordovaScreenshot', ['$q', function ($q){ 73 | return { 74 | capture: function (filename, extension, quality){ 75 | extension = extension || 'jpg'; 76 | quality = quality || '100'; 77 | 78 | var defer = $q.defer(); 79 | 80 | navigator.screenshot.save(function (error, res){ 81 | if (error) { 82 | console.error(error); 83 | defer.reject(error); 84 | } else { 85 | console.log('screenshot saved in: ', res.filePath); 86 | defer.resolve(res.filePath); 87 | } 88 | }, extension, quality, filename); 89 | 90 | return defer.promise; 91 | } 92 | }; 93 | }]) 94 | ``` 95 | 96 | ## Known Issue 97 | ### in Android platform I receive the black image with crosswalk 98 | #### solution: 99 | 100 | add this line ```` in config.xml, see [bug](https://crosswalk-project.org/jira/browse/XWALK-2233) 101 | 102 | 103 | License 104 | ========= 105 | this repo uses the MIT license 106 | -------------------------------------------------------------------------------- /src/ios/Screenshot.m: -------------------------------------------------------------------------------- 1 | // 2 | // Screenshot.h 3 | // 4 | // Created by Simon Madine on 29/04/2010. 5 | // Copyright 2010 The Angry Robot Zombie Factory. 6 | // - Converted to Cordova 1.6.1 by Josemando Sobral. 7 | // MIT licensed 8 | // 9 | // Modifications to support orientation change by @ffd8 10 | // 11 | 12 | #import 13 | #import "Screenshot.h" 14 | 15 | @implementation Screenshot 16 | 17 | @synthesize webView; 18 | 19 | CGFloat statusBarHeight() 20 | { 21 | CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size; 22 | return MIN(statusBarSize.width, statusBarSize.height); 23 | } 24 | 25 | - (UIImage *)getScreenshot 26 | { 27 | UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; 28 | CGRect rect = [keyWindow bounds]; 29 | UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0); 30 | [keyWindow drawViewHierarchyInRect:keyWindow.bounds afterScreenUpdates:NO]; 31 | UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); 32 | UIGraphicsEndImageContext(); 33 | 34 | // cut the status bar from the screenshot 35 | CGRect smallRect = CGRectMake (0,statusBarHeight()*img.scale,rect.size.width*img.scale,rect.size.height*img.scale); 36 | 37 | CGImageRef subImageRef = CGImageCreateWithImageInRect(img.CGImage, smallRect); 38 | CGRect smallBounds = CGRectMake(0,0,CGImageGetWidth(subImageRef), CGImageGetHeight(subImageRef)); 39 | 40 | UIGraphicsBeginImageContext(smallBounds.size); 41 | CGContextRef context = UIGraphicsGetCurrentContext(); 42 | CGContextDrawImage(context,smallBounds,subImageRef); 43 | UIImage* cropped = [UIImage imageWithCGImage:subImageRef]; 44 | UIGraphicsEndImageContext(); 45 | 46 | CGImageRelease(subImageRef); 47 | 48 | return cropped; 49 | } 50 | 51 | - (void)saveScreenshot:(CDVInvokedUrlCommand*)command 52 | { 53 | NSString *filename = [command.arguments objectAtIndex:2]; 54 | NSNumber *quality = [command.arguments objectAtIndex:1]; 55 | 56 | NSString *path = [NSString stringWithFormat:@"%@.jpg",filename]; 57 | NSString *jpgPath = [NSTemporaryDirectory() stringByAppendingPathComponent:path]; 58 | 59 | UIImage *image = [self getScreenshot]; 60 | NSData *imageData = UIImageJPEGRepresentation(image,[quality floatValue]); 61 | [imageData writeToFile:jpgPath atomically:NO]; 62 | 63 | CDVPluginResult* pluginResult = nil; 64 | NSDictionary *jsonObj = [ [NSDictionary alloc] 65 | initWithObjectsAndKeys : 66 | jpgPath, @"filePath", 67 | @"true", @"success", 68 | nil 69 | ]; 70 | 71 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:jsonObj]; 72 | NSString* callbackId = command.callbackId; 73 | [self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId]; 74 | } 75 | 76 | - (void) getScreenshotAsURI:(CDVInvokedUrlCommand*)command 77 | { 78 | NSNumber *quality = command.arguments[0]; 79 | UIImage *image = [self getScreenshot]; 80 | NSData *imageData = UIImageJPEGRepresentation(image,[quality floatValue]); 81 | NSString *base64Encoded = [imageData base64EncodedStringWithOptions:0]; 82 | NSDictionary *jsonObj = @{ 83 | @"URI" : [NSString stringWithFormat:@"data:image/jpeg;base64,%@", base64Encoded] 84 | }; 85 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:jsonObj]; 86 | [self.commandDelegate sendPluginResult:pluginResult callbackId:[command callbackId]]; 87 | } 88 | @end 89 | -------------------------------------------------------------------------------- /src/osx/Screenshot.m: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | #import 21 | #import "Screenshot.h" 22 | 23 | @implementation Screenshot 24 | 25 | - (NSImage*) getScreenshot 26 | { 27 | CGImageRef screenShot = CGWindowListCreateImage(CGRectInfinite, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault); 28 | 29 | NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage:screenShot]; 30 | NSImage *image = [[NSImage alloc] init]; 31 | [image addRepresentation:bitmapRep]; 32 | return image; 33 | } 34 | 35 | - (void) saveScreenshot:(CDVInvokedUrlCommand*)command 36 | { 37 | NSString *filename = command.arguments[2]; 38 | NSNumber *quality = command.arguments[1]; 39 | 40 | NSString *path = [NSString stringWithFormat:@"%@.jpg",filename]; 41 | NSString *jpgPath = [NSTemporaryDirectory() stringByAppendingPathComponent:path]; 42 | 43 | NSImage *image = [self getScreenshot]; 44 | NSData *imageData = [image TIFFRepresentation]; 45 | 46 | NSBitmapImageRep *imgRep = [NSBitmapImageRep imageRepWithData:imageData]; 47 | NSDictionary *imageProps = @{NSImageCompressionFactor: quality}; 48 | 49 | imageData = [imgRep representationUsingType: NSJPEGFileType properties: imageProps]; 50 | [imageData writeToFile: jpgPath atomically: NO]; 51 | 52 | CDVPluginResult* pluginResult = nil; 53 | NSDictionary *jsonObj = @{@"filePath": jpgPath, @"success": @"true"}; 54 | 55 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:jsonObj]; 56 | NSString* callbackId = command.callbackId; 57 | [self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId]; 58 | } 59 | 60 | - (void) getScreenshotAsURI:(CDVInvokedUrlCommand*)command 61 | { 62 | NSNumber *quality = command.arguments[0]; 63 | NSImage *image = [self getScreenshot]; 64 | 65 | NSData *imageData = [image TIFFRepresentation]; 66 | NSBitmapImageRep *imgRep = [NSBitmapImageRep imageRepWithData:imageData]; 67 | NSDictionary *imageProps = @{NSImageCompressionFactor: quality}; 68 | imageData = [imgRep representationUsingType: NSJPEGFileType properties: imageProps]; 69 | 70 | NSString *base64Encoded = [imageData base64EncodedStringWithOptions:0]; 71 | NSDictionary *jsonObj = @{@"URI" : [NSString stringWithFormat:@"data:image/jpeg;base64,%@", base64Encoded]}; 72 | 73 | CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:jsonObj]; 74 | [self.commandDelegate sendPluginResult:pluginResult callbackId:[command callbackId]]; 75 | } 76 | @end 77 | -------------------------------------------------------------------------------- /src/android/PermissionHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | package com.darktalker.cordova.screenshot; 20 | 21 | import java.lang.reflect.InvocationTargetException; 22 | import java.lang.reflect.Method; 23 | import java.util.Arrays; 24 | 25 | import org.apache.cordova.CordovaInterface; 26 | import org.apache.cordova.CordovaPlugin; 27 | import org.apache.cordova.LOG; 28 | 29 | import android.content.pm.PackageManager; 30 | 31 | /** 32 | * This class provides reflective methods for permission requesting and checking so that plugins 33 | * written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions. 34 | */ 35 | public class PermissionHelper { 36 | private static final String LOG_TAG = "CordovaPermissionHelper"; 37 | 38 | /** 39 | * Requests a "dangerous" permission for the application at runtime. This is a helper method 40 | * alternative to cordovaInterface.requestPermission() that does not require the project to be 41 | * built with cordova-android 5.0.0+ 42 | * 43 | * @param plugin The plugin the permission is being requested for 44 | * @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult() 45 | * along with the result of the permission request 46 | * @param permission The permission to be requested 47 | */ 48 | public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) { 49 | PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission}); 50 | } 51 | 52 | /** 53 | * Requests "dangerous" permissions for the application at runtime. This is a helper method 54 | * alternative to cordovaInterface.requestPermissions() that does not require the project to be 55 | * built with cordova-android 5.0.0+ 56 | * 57 | * @param plugin The plugin the permissions are being requested for 58 | * @param requestCode A requestCode to be passed to the plugin's onRequestPermissionResult() 59 | * along with the result of the permissions request 60 | * @param permissions The permissions to be requested 61 | */ 62 | public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) { 63 | try { 64 | Method requestPermission = CordovaInterface.class.getDeclaredMethod( 65 | "requestPermissions", CordovaPlugin.class, int.class, String[].class); 66 | 67 | // If there is no exception, then this is cordova-android 5.0.0+ 68 | requestPermission.invoke(plugin.cordova, plugin, requestCode, permissions); 69 | } catch (NoSuchMethodException noSuchMethodException) { 70 | // cordova-android version is less than 5.0.0, so permission is implicitly granted 71 | LOG.d(LOG_TAG, "No need to request permissions " + Arrays.toString(permissions)); 72 | 73 | // Notify the plugin that all were granted by using more reflection 74 | deliverPermissionResult(plugin, requestCode, permissions); 75 | } catch (IllegalAccessException illegalAccessException) { 76 | // Should never be caught; this is a public method 77 | LOG.e(LOG_TAG, "IllegalAccessException when requesting permissions " + Arrays.toString(permissions), illegalAccessException); 78 | } catch(InvocationTargetException invocationTargetException) { 79 | // This method does not throw any exceptions, so this should never be caught 80 | LOG.e(LOG_TAG, "invocationTargetException when requesting permissions " + Arrays.toString(permissions), invocationTargetException); 81 | } 82 | } 83 | 84 | /** 85 | * Checks at runtime to see if the application has been granted a permission. This is a helper 86 | * method alternative to cordovaInterface.hasPermission() that does not require the project to 87 | * be built with cordova-android 5.0.0+ 88 | * 89 | * @param plugin The plugin the permission is being checked against 90 | * @param permission The permission to be checked 91 | * 92 | * @return True if the permission has already been granted and false otherwise 93 | */ 94 | public static boolean hasPermission(CordovaPlugin plugin, String permission) { 95 | try { 96 | Method hasPermission = CordovaInterface.class.getDeclaredMethod("hasPermission", String.class); 97 | 98 | // If there is no exception, then this is cordova-android 5.0.0+ 99 | return (Boolean) hasPermission.invoke(plugin.cordova, permission); 100 | } catch (NoSuchMethodException noSuchMethodException) { 101 | // cordova-android version is less than 5.0.0, so permission is implicitly granted 102 | LOG.d(LOG_TAG, "No need to check for permission " + permission); 103 | return true; 104 | } catch (IllegalAccessException illegalAccessException) { 105 | // Should never be caught; this is a public method 106 | LOG.e(LOG_TAG, "IllegalAccessException when checking permission " + permission, illegalAccessException); 107 | } catch(InvocationTargetException invocationTargetException) { 108 | // This method does not throw any exceptions, so this should never be caught 109 | LOG.e(LOG_TAG, "invocationTargetException when checking permission " + permission, invocationTargetException); 110 | } 111 | return false; 112 | } 113 | 114 | private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) { 115 | // Generate the request results 116 | int[] requestResults = new int[permissions.length]; 117 | Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED); 118 | 119 | try { 120 | Method onRequestPermissionResult = CordovaPlugin.class.getDeclaredMethod( 121 | "onRequestPermissionResult", int.class, String[].class, int[].class); 122 | 123 | onRequestPermissionResult.invoke(plugin, requestCode, permissions, requestResults); 124 | } catch (NoSuchMethodException noSuchMethodException) { 125 | // Should never be caught since the plugin must be written for cordova-android 5.0.0+ if it 126 | // made it to this point 127 | LOG.e(LOG_TAG, "NoSuchMethodException when delivering permissions results", noSuchMethodException); 128 | } catch (IllegalAccessException illegalAccessException) { 129 | // Should never be caught; this is a public method 130 | LOG.e(LOG_TAG, "IllegalAccessException when delivering permissions results", illegalAccessException); 131 | } catch(InvocationTargetException invocationTargetException) { 132 | // This method may throw a JSONException. We are just duplicating cordova-android's 133 | // exception handling behavior here; all it does is log the exception in CordovaActivity, 134 | // print the stacktrace, and ignore it 135 | LOG.e(LOG_TAG, "InvocationTargetException when delivering permissions results", invocationTargetException); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/android/Screenshot.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 30ideas (http://30ide.as) 3 | * MIT licensed 4 | * 5 | * @author Josemando Sobral 6 | * @created Jul 2nd, 2012. 7 | * improved by Hongbo LU 8 | */ 9 | package com.darktalker.cordova.screenshot; 10 | 11 | import android.Manifest; 12 | import android.content.Intent; 13 | import android.content.pm.PackageManager; 14 | import android.graphics.Bitmap; 15 | import android.graphics.Bitmap.CompressFormat; 16 | import android.net.Uri; 17 | import android.os.Environment; 18 | import android.util.Base64; 19 | import android.view.TextureView; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | 23 | import org.apache.cordova.CallbackContext; 24 | import org.apache.cordova.CordovaPlugin; 25 | import org.apache.cordova.PluginResult; 26 | import org.json.JSONArray; 27 | import org.json.JSONException; 28 | import org.json.JSONObject; 29 | 30 | import java.io.ByteArrayOutputStream; 31 | import java.io.File; 32 | import java.io.FileOutputStream; 33 | import java.io.IOException; 34 | 35 | 36 | public class Screenshot extends CordovaPlugin { 37 | private CallbackContext mCallbackContext; 38 | private String mAction; 39 | private JSONArray mArgs; 40 | 41 | 42 | private String mFormat; 43 | private String mFileName; 44 | private Integer mQuality; 45 | 46 | protected final static String[] PERMISSIONS = {Manifest.permission.WRITE_EXTERNAL_STORAGE}; 47 | public static final int PERMISSION_DENIED_ERROR = 20; 48 | public static final int SAVE_SCREENSHOT_SEC = 0; 49 | 50 | @Override 51 | public Object onMessage(String id, Object data) { 52 | if (id.equals("onGotXWalkBitmap")) { 53 | Bitmap bitmap = (Bitmap) data; 54 | if (bitmap != null) { 55 | if (mAction.equals("saveScreenshot")) { 56 | saveScreenshot(bitmap, mFormat, mFileName, mQuality); 57 | } else if (mAction.equals("getScreenshotAsURI")) { 58 | getScreenshotAsURI(bitmap, mQuality); 59 | } 60 | } 61 | } 62 | return null; 63 | } 64 | 65 | private Bitmap getBitmap() { 66 | Bitmap bitmap = null; 67 | 68 | boolean isCrosswalk = false; 69 | try { 70 | Class.forName("org.crosswalk.engine.XWalkWebViewEngine"); 71 | isCrosswalk = true; 72 | } catch (Exception e) { 73 | } 74 | 75 | if (isCrosswalk) { 76 | webView.getPluginManager().postMessage("captureXWalkBitmap", this); 77 | } else { 78 | View view = webView.getView();//.getRootView(); 79 | view.setDrawingCacheEnabled(true); 80 | bitmap = Bitmap.createBitmap(view.getDrawingCache()); 81 | view.setDrawingCacheEnabled(false); 82 | } 83 | 84 | return bitmap; 85 | } 86 | 87 | private void scanPhoto(String imageFileName) { 88 | Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); 89 | File f = new File(imageFileName); 90 | Uri contentUri = Uri.fromFile(f); 91 | mediaScanIntent.setData(contentUri); 92 | this.cordova.getActivity().sendBroadcast(mediaScanIntent); 93 | } 94 | 95 | private void saveScreenshot(Bitmap bitmap, String format, String fileName, Integer quality) { 96 | try { 97 | File folder = new File(Environment.getExternalStorageDirectory(), "Pictures"); 98 | if (!folder.exists()) { 99 | folder.mkdirs(); 100 | } 101 | 102 | File f = new File(folder, fileName + "." + format); 103 | 104 | FileOutputStream fos = new FileOutputStream(f); 105 | if (format.equals("png")) { 106 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); 107 | } else if (format.equals("jpg")) { 108 | bitmap.compress(Bitmap.CompressFormat.JPEG, quality == null ? 100 : quality, fos); 109 | } 110 | JSONObject jsonRes = new JSONObject(); 111 | jsonRes.put("filePath", f.getAbsolutePath()); 112 | PluginResult result = new PluginResult(PluginResult.Status.OK, jsonRes); 113 | mCallbackContext.sendPluginResult(result); 114 | 115 | scanPhoto(f.getAbsolutePath()); 116 | fos.close(); 117 | } catch (JSONException e) { 118 | mCallbackContext.error(e.getMessage()); 119 | 120 | } catch (IOException e) { 121 | mCallbackContext.error(e.getMessage()); 122 | 123 | } 124 | } 125 | 126 | private void getScreenshotAsURI(Bitmap bitmap, int quality) { 127 | try { 128 | ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); 129 | 130 | if (bitmap.compress(CompressFormat.JPEG, quality, jpeg_data)) { 131 | byte[] code = jpeg_data.toByteArray(); 132 | byte[] output = Base64.encode(code, Base64.NO_WRAP); 133 | String js_out = new String(output); 134 | js_out = "data:image/jpeg;base64," + js_out; 135 | JSONObject jsonRes = new JSONObject(); 136 | jsonRes.put("URI", js_out); 137 | PluginResult result = new PluginResult(PluginResult.Status.OK, jsonRes); 138 | mCallbackContext.sendPluginResult(result); 139 | 140 | js_out = null; 141 | output = null; 142 | code = null; 143 | } 144 | 145 | jpeg_data = null; 146 | 147 | } catch (JSONException e) { 148 | mCallbackContext.error(e.getMessage()); 149 | 150 | } catch (Exception e) { 151 | mCallbackContext.error(e.getMessage()); 152 | 153 | } 154 | } 155 | 156 | public void saveScreenshot() throws JSONException{ 157 | mFormat = (String) mArgs.get(0); 158 | mQuality = (Integer) mArgs.get(1); 159 | mFileName = (String) mArgs.get(2); 160 | 161 | super.cordova.getActivity().runOnUiThread(new Runnable() { 162 | @Override 163 | public void run() { 164 | if (mFormat.equals("png") || mFormat.equals("jpg")) { 165 | Bitmap bitmap = getBitmap(); 166 | if (bitmap != null) { 167 | saveScreenshot(bitmap, mFormat, mFileName, mQuality); 168 | } 169 | } else { 170 | mCallbackContext.error("format " + mFormat + " not found"); 171 | 172 | } 173 | } 174 | }); 175 | } 176 | 177 | public void getScreenshotAsURI() throws JSONException{ 178 | mQuality = (Integer) mArgs.get(0); 179 | 180 | super.cordova.getActivity().runOnUiThread(new Runnable() { 181 | @Override 182 | public void run() { 183 | Bitmap bitmap = getBitmap(); 184 | if (bitmap != null) { 185 | getScreenshotAsURI(bitmap, mQuality); 186 | } 187 | } 188 | }); 189 | } 190 | 191 | public void getScreenshotAsURISync() throws JSONException{ 192 | mQuality = (Integer) mArgs.get(0); 193 | 194 | Runnable r = new Runnable(){ 195 | @Override 196 | public void run() { 197 | Bitmap bitmap = getBitmap(); 198 | if (bitmap != null) { 199 | getScreenshotAsURI(bitmap, mQuality); 200 | } 201 | synchronized (this) { this.notify(); } 202 | } 203 | }; 204 | 205 | synchronized (r) { 206 | super.cordova.getActivity().runOnUiThread(r); 207 | try{ 208 | r.wait(); 209 | } catch (InterruptedException e){ 210 | mCallbackContext.error(e.getMessage()); 211 | } 212 | } 213 | } 214 | 215 | @Override 216 | public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { 217 | // starting on ICS, some WebView methods 218 | // can only be called on UI threads 219 | mCallbackContext = callbackContext; 220 | mAction = action; 221 | mArgs = args; 222 | 223 | if (action.equals("saveScreenshot")) { 224 | if(PermissionHelper.hasPermission(this, PERMISSIONS[0])) { 225 | saveScreenshot(); 226 | } else { 227 | PermissionHelper.requestPermissions(this, SAVE_SCREENSHOT_SEC, PERMISSIONS); 228 | } 229 | return true; 230 | } else if (action.equals("getScreenshotAsURI")) { 231 | getScreenshotAsURI(); 232 | return true; 233 | } else if (action.equals("getScreenshotAsURISync")){ 234 | getScreenshotAsURISync(); 235 | return true; 236 | } 237 | callbackContext.error("action not found"); 238 | return false; 239 | } 240 | 241 | public void onRequestPermissionResult(int requestCode, String[] permissions, 242 | int[] grantResults) throws JSONException 243 | { 244 | for(int r:grantResults) 245 | { 246 | if(r == PackageManager.PERMISSION_DENIED) 247 | { 248 | mCallbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR)); 249 | return; 250 | } 251 | } 252 | switch(requestCode) 253 | { 254 | case SAVE_SCREENSHOT_SEC: 255 | saveScreenshot(); 256 | break; 257 | } 258 | } 259 | 260 | 261 | } 262 | --------------------------------------------------------------------------------