RNViewShotModule.java 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package fr.greweb.reactnativeviewshot;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.net.Uri;
  5. import android.os.AsyncTask;
  6. import android.support.annotation.NonNull;
  7. import android.util.DisplayMetrics;
  8. import android.util.Log;
  9. import com.facebook.react.bridge.GuardedAsyncTask;
  10. import com.facebook.react.bridge.Promise;
  11. import com.facebook.react.bridge.ReactApplicationContext;
  12. import com.facebook.react.bridge.ReactContext;
  13. import com.facebook.react.bridge.ReactContextBaseJavaModule;
  14. import com.facebook.react.bridge.ReactMethod;
  15. import com.facebook.react.bridge.ReadableMap;
  16. import com.facebook.react.uimanager.UIManagerModule;
  17. import java.io.File;
  18. import java.io.FilenameFilter;
  19. import java.io.IOException;
  20. import java.util.Collections;
  21. import java.util.Map;
  22. import fr.greweb.reactnativeviewshot.ViewShot.Formats;
  23. import fr.greweb.reactnativeviewshot.ViewShot.Results;
  24. public class RNViewShotModule extends ReactContextBaseJavaModule {
  25. public static final String RNVIEW_SHOT = "RNViewShot";
  26. private final ReactApplicationContext reactContext;
  27. public RNViewShotModule(ReactApplicationContext reactContext) {
  28. super(reactContext);
  29. this.reactContext = reactContext;
  30. }
  31. @Override
  32. public String getName() {
  33. return RNVIEW_SHOT;
  34. }
  35. @Override
  36. public Map<String, Object> getConstants() {
  37. return Collections.emptyMap();
  38. }
  39. @Override
  40. public void onCatalystInstanceDestroy() {
  41. super.onCatalystInstanceDestroy();
  42. new CleanTask(getReactApplicationContext()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  43. }
  44. @ReactMethod
  45. public void releaseCapture(String uri) {
  46. final String path = Uri.parse(uri).getPath();
  47. if (path == null) return;
  48. File file = new File(path);
  49. if (!file.exists()) return;
  50. File parent = file.getParentFile();
  51. if (parent.equals(reactContext.getExternalCacheDir()) || parent.equals(reactContext.getCacheDir())) {
  52. file.delete();
  53. }
  54. }
  55. @ReactMethod
  56. public void captureRef(int tag, ReadableMap options, Promise promise) {
  57. final ReactApplicationContext context = getReactApplicationContext();
  58. final DisplayMetrics dm = context.getResources().getDisplayMetrics();
  59. final String extension = options.getString("format");
  60. final int imageFormat = "jpg".equals(extension)
  61. ? Formats.JPEG
  62. : "webm".equals(extension)
  63. ? Formats.WEBP
  64. : "raw".equals(extension)
  65. ? Formats.RAW
  66. : Formats.PNG;
  67. final double quality = options.getDouble("quality");
  68. final Integer scaleWidth = options.hasKey("width") ? (int) (dm.density * options.getDouble("width")) : null;
  69. final Integer scaleHeight = options.hasKey("height") ? (int) (dm.density * options.getDouble("height")) : null;
  70. final String resultStreamFormat = options.getString("result");
  71. final Boolean snapshotContentContainer = options.getBoolean("snapshotContentContainer");
  72. try {
  73. File outputFile = null;
  74. if (Results.TEMP_FILE.equals(resultStreamFormat)) {
  75. outputFile = createTempFile(getReactApplicationContext(), extension);
  76. }
  77. final Activity activity = getCurrentActivity();
  78. final UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class);
  79. uiManager.addUIBlock(new ViewShot(
  80. tag, extension, imageFormat, quality,
  81. scaleWidth, scaleHeight, outputFile, resultStreamFormat,
  82. snapshotContentContainer, reactContext, activity, promise)
  83. );
  84. } catch (final Throwable ex) {
  85. Log.e(RNVIEW_SHOT, "Failed to snapshot view tag " + tag, ex);
  86. promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag " + tag);
  87. }
  88. }
  89. @ReactMethod
  90. public void captureScreen(ReadableMap options, Promise promise) {
  91. captureRef(-1, options, promise);
  92. }
  93. private static final String TEMP_FILE_PREFIX = "ReactNative-snapshot-image";
  94. /**
  95. * Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
  96. * image files. This is run when the catalyst instance is being destroyed (i.e. app is shutting
  97. * down) and when the module is instantiated, to handle the case where the app crashed.
  98. */
  99. private static class CleanTask extends GuardedAsyncTask<Void, Void> implements FilenameFilter {
  100. private final File cacheDir;
  101. private final File externalCacheDir;
  102. private CleanTask(ReactContext context) {
  103. super(context);
  104. cacheDir = context.getCacheDir();
  105. externalCacheDir = context.getExternalCacheDir();
  106. }
  107. @Override
  108. protected void doInBackgroundGuarded(Void... params) {
  109. if (null != cacheDir) {
  110. cleanDirectory(cacheDir);
  111. }
  112. if (externalCacheDir != null) {
  113. cleanDirectory(externalCacheDir);
  114. }
  115. }
  116. @Override
  117. public final boolean accept(File dir, String filename) {
  118. return filename.startsWith(TEMP_FILE_PREFIX);
  119. }
  120. private void cleanDirectory(@NonNull final File directory) {
  121. final File[] toDelete = directory.listFiles(this);
  122. if (toDelete != null) {
  123. for (File file : toDelete) {
  124. if (file.delete()) {
  125. Log.d(RNVIEW_SHOT, "deleted file: " + file.getAbsolutePath());
  126. }
  127. }
  128. }
  129. }
  130. }
  131. /**
  132. * Create a temporary file in the cache directory on either internal or external storage,
  133. * whichever is available and has more free space.
  134. */
  135. @NonNull
  136. private File createTempFile(@NonNull final Context context, @NonNull final String ext) throws IOException {
  137. final File externalCacheDir = context.getExternalCacheDir();
  138. final File internalCacheDir = context.getCacheDir();
  139. final File cacheDir;
  140. if (externalCacheDir == null && internalCacheDir == null) {
  141. throw new IOException("No cache directory available");
  142. }
  143. if (externalCacheDir == null) {
  144. cacheDir = internalCacheDir;
  145. } else if (internalCacheDir == null) {
  146. cacheDir = externalCacheDir;
  147. } else {
  148. cacheDir = externalCacheDir.getFreeSpace() > internalCacheDir.getFreeSpace() ?
  149. externalCacheDir : internalCacheDir;
  150. }
  151. final String suffix = "." + ext;
  152. return File.createTempFile(TEMP_FILE_PREFIX, suffix, cacheDir);
  153. }
  154. }