diff --git a/SignaturePad-Example/build.gradle b/SignaturePad-Example/build.gradle index eaeb4af..bc8125f 100644 --- a/SignaturePad-Example/build.gradle +++ b/SignaturePad-Example/build.gradle @@ -17,6 +17,11 @@ android { minifyEnabled false } } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/signature-pad/build.gradle b/signature-pad/build.gradle index e688be1..73cdb80 100644 --- a/signature-pad/build.gradle +++ b/signature-pad/build.gradle @@ -18,6 +18,11 @@ android { dataBinding { enabled = true } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } bintray { diff --git a/signature-pad/src/main/java/com/github/gcacace/signaturepad/views/SignaturePad.java b/signature-pad/src/main/java/com/github/gcacace/signaturepad/views/SignaturePad.java index 91474e3..1e020ee 100644 --- a/signature-pad/src/main/java/com/github/gcacace/signaturepad/views/SignaturePad.java +++ b/signature-pad/src/main/java/com/github/gcacace/signaturepad/views/SignaturePad.java @@ -4,6 +4,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; @@ -12,6 +13,7 @@ import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; @@ -25,10 +27,21 @@ import com.github.gcacace.signaturepad.view.ViewCompat; import com.github.gcacace.signaturepad.view.ViewTreeObserverCompat; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executors; public class SignaturePad extends View { + private static final String TAG = "SignaturePad"; + + private static final String KEY_SIGNATURE_BITMAP_URL = "signatureBitmapUrl"; + private static final String TEMP_FILE_PREFIX = "signature-pad"; + private static final String TEMP_FILE_EXT = ".png"; + //View state private List mPoints; private boolean mIsEmpty; @@ -67,6 +80,7 @@ public class SignaturePad extends View { private Paint mPaint = new Paint(); private Bitmap mSignatureBitmap = null; private Canvas mSignatureBitmapCanvas = null; + private final String signatureStateFilePath; public SignaturePad(Context context, AttributeSet attrs) { super(context, attrs); @@ -104,16 +118,107 @@ public boolean onDoubleTap(MotionEvent e) { return onDoubleClick(); } }); + + + signatureStateFilePath = safeCreateTempFilePath(); + } + + /** + * Tries to create temp file and get its path. Exceptions suppressed. + * + * @return Returns absolute path or null. + */ + private String safeCreateTempFilePath() { + String temporaryFilePath; + + try { + temporaryFilePath = File.createTempFile( + TEMP_FILE_PREFIX, + TEMP_FILE_EXT, + getContext().getFilesDir() + ).getAbsolutePath(); + } catch (IOException exception) { + Log.e(TAG, "Failed to create temp file"); + temporaryFilePath = null; + } + + Log.d(TAG, String.format("Will use [%s] to store signature bitmap when needed", temporaryFilePath)); + + return temporaryFilePath; + } + + private void updateBundleWithSignatureStateFilePath(Bundle bundle) { + if (signatureStateFilePath != null) { + bundle.putString(KEY_SIGNATURE_BITMAP_URL, signatureStateFilePath); + } else { + Log.e(TAG, "Skipped bundle update as no temp file to work with"); + } + } + + private void storeBitmapToSignatureStateFile() { + try { + Executors.newSingleThreadExecutor().submit(() -> { + Log.d(TAG, "Will save bitmap to path " + signatureStateFilePath); + + if (signatureStateFilePath != null) { + try (FileOutputStream fileOutputStream = new FileOutputStream(signatureStateFilePath)) { + if (mBitmapSavedState.compress(Bitmap.CompressFormat.PNG, 80, fileOutputStream)) { + Log.d(TAG, "Succeeded to compress bitmap to output stream"); + } else { + Log.e(TAG, "Failed to compress bitmap to output stream"); + } + } catch (FileNotFoundException fileNotFoundException) { + Log.e(TAG, "Failed to write bitmap to output stream. File not found."); + } catch (IOException ioException) { + Log.e(TAG, "Failed to write bitmap to output stream. IO error."); + } + } else { + Log.e(TAG, "Skipped bitmap file save as no temp file to work with"); + } + }).get(); + } catch (Exception exception) { + Log.e(TAG, "Failed to wait for future completion with " + exception.getMessage()); + } + } + + private void readBitmapFromSignatureStateFile(Bundle bundle) { + String path = bundle.getString(KEY_SIGNATURE_BITMAP_URL); + + if (path != null) { + Executors.newSingleThreadExecutor().submit(() -> { + Log.d(TAG, String.format("Will un-bundle bitmap from [%s]", path)); + + mBitmapSavedState = BitmapFactory.decodeFile(path); + + if (mBitmapSavedState == null) { + Log.d(TAG, "Failed to decode bitmap from path " + path); + } else { + this.setSignatureBitmap(mBitmapSavedState); + + Log.d(TAG, String.format("Decoded bitmap is %d bytes", mBitmapSavedState.getByteCount())); + } + deleteTempFilePath(path); + }); + } + } + + private void deleteTempFilePath(String path) { + File file = new File(path); + Log.d(TAG, String.format("Was temp file delete successful? %b", file.delete())); } @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable("superState", super.onSaveInstanceState()); + if (this.mHasEditState == null || this.mHasEditState) { this.mBitmapSavedState = this.getTransparentSignatureBitmap(); } - bundle.putParcelable("signatureBitmap", this.mBitmapSavedState); + + storeBitmapToSignatureStateFile(); + updateBundleWithSignatureStateFilePath(bundle); + return bundle; } @@ -121,8 +226,9 @@ protected Parcelable onSaveInstanceState() { protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; - this.setSignatureBitmap((Bitmap) bundle.getParcelable("signatureBitmap")); - this.mBitmapSavedState = bundle.getParcelable("signatureBitmap"); + + readBitmapFromSignatureStateFile(bundle); + state = bundle.getParcelable("superState"); } this.mHasEditState = false; @@ -251,6 +357,21 @@ protected void onDraw(Canvas canvas) { } } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + recycleBitmapSafely(mBitmapSavedState); + recycleBitmapSafely(mSignatureBitmap); + mBitmapSavedState = null; + mSignatureBitmap = null; + } + + private void recycleBitmapSafely(Bitmap bitmap) { + if (bitmap != null && !bitmap.isRecycled()) { + bitmap.recycle(); + } + } + public void setOnSignedListener(OnSignedListener listener) { mOnSignedListener = listener; }