/* * Copyright (C) 2012 University of Washington * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.odk.collect.android.activities; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import org.odk.collect.android.R; import org.odk.collect.android.application.Collect; import org.odk.collect.android.utilities.FileUtils; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Display; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.LinearLayout; import android.widget.RelativeLayout; /** * Modified from the FingerPaint example found in The Android Open Source * Project. * * @author BehrAtherton@gmail.com * */ public class DrawActivity extends Activity { public static final String t = "DrawActivity"; public static final String OPTION = "option"; public static final String OPTION_SIGNATURE = "signature"; public static final String OPTION_ANNOTATE = "annotate"; public static final String OPTION_DRAW = "draw"; public static final String REF_IMAGE = "refImage"; public static final String EXTRA_OUTPUT = android.provider.MediaStore.EXTRA_OUTPUT; public static final String SAVEPOINT_IMAGE = "savepointImage"; // during // restore // incoming options... private String loadOption = null; private File refImage = null; private File output = null; private File savepointImage = null; private Button btnFinished; private Button btnReset; private Button btnCancel; private Paint paint; private Paint pointPaint; private DrawView drawView; private String alertTitleString; private AlertDialog alertDialog; /* * (non-Javadoc) * @see android.app.Activity#onSaveInstanceState(android.os.Bundle) */ @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); try { saveFile(savepointImage); } catch (FileNotFoundException e) { e.printStackTrace(); } if ( savepointImage.exists() ) { outState.putString(SAVEPOINT_IMAGE, savepointImage.getAbsolutePath()); } } /* * (non-Javadoc) * @see android.app.Activity#onCreate(android.os.Bundle) */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); Bundle extras = getIntent().getExtras(); if (extras == null) { loadOption = OPTION_DRAW; refImage = null; savepointImage = new File(Collect.TMPDRAWFILE_PATH); savepointImage.delete(); output = new File(Collect.TMPFILE_PATH); } else { loadOption = extras.getString(OPTION); if (loadOption == null) { loadOption = OPTION_DRAW; } // refImage can also be present if resuming a drawing Uri uri = (Uri) extras.get(REF_IMAGE); if (uri != null) { refImage = new File(uri.getPath()); } String savepoint = extras.getString(SAVEPOINT_IMAGE); if (savepoint != null) { savepointImage = new File(savepoint); if (!savepointImage.exists() && refImage != null && refImage.exists()) { FileUtils.copyFile(refImage, savepointImage); } } else { savepointImage = new File(Collect.TMPDRAWFILE_PATH); savepointImage.delete(); if (refImage != null && refImage.exists()) { FileUtils.copyFile(refImage, savepointImage); } } //sets where the result will be saved to uri = (Uri) extras.get(EXTRA_OUTPUT); if (uri != null) { output = new File(uri.getPath()); } else { output = new File(Collect.TMPFILE_PATH); } } // At this point, we have: // loadOption -- type of activity (draw, signature, annotate) // refImage -- original image to work with // savepointImage -- drawing to use as a starting point (may be copy of // original) // output -- where the output should be written if (OPTION_SIGNATURE.equals(loadOption)) { // set landscape setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); alertTitleString = getString(R.string.quit_application, getString(R.string.sign_button)); } else if (OPTION_ANNOTATE.equals(loadOption)) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); alertTitleString = getString(R.string.quit_application, getString(R.string.markup_image)); } else { alertTitleString = getString(R.string.quit_application, getString(R.string.draw_image)); } setTitle(getString(R.string.app_name) + " > " + getString(R.string.draw_image)); LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); RelativeLayout v = (RelativeLayout) inflater.inflate( R.layout.draw_layout, null); LinearLayout ll = (LinearLayout) v.findViewById(R.id.drawViewLayout); drawView = new DrawView(this, OPTION_SIGNATURE.equals(loadOption), savepointImage); ll.addView(drawView); setContentView(v); paint = new Paint(); paint.setAntiAlias(true); paint.setDither(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeWidth(10); paint.setColor(Color.BLACK); pointPaint = new Paint(); pointPaint.setAntiAlias(true); pointPaint.setDither(true); pointPaint.setStyle(Paint.Style.FILL_AND_STROKE); pointPaint.setStrokeWidth(10); pointPaint.setColor(Color.BLACK); btnFinished = (Button) findViewById(R.id.btnFinishDraw); btnFinished.setOnClickListener(new View.OnClickListener() { /* * (non-Javadoc) * @see android.view.View.OnClickListener#onClick(android.view.View) */ @Override public void onClick(View v) { SaveAndClose(); } }); btnReset = (Button) findViewById(R.id.btnResetDraw); btnReset.setOnClickListener(new View.OnClickListener() { /* * (non-Javadoc) * @see android.view.View.OnClickListener#onClick(android.view.View) */ @Override public void onClick(View v) { Reset(); } }); btnCancel = (Button) findViewById(R.id.btnCancelDraw); btnCancel.setOnClickListener(new View.OnClickListener() { /* * (non-Javadoc) * @see android.view.View.OnClickListener#onClick(android.view.View) */ @Override public void onClick(View v) { CancelAndClose(); } }); } private void SaveAndClose() { try { saveFile(output); setResult(Activity.RESULT_OK); } catch (FileNotFoundException e) { e.printStackTrace(); setResult(Activity.RESULT_CANCELED); } this.finish(); } private void saveFile(File f) throws FileNotFoundException { if ( drawView.getWidth() == 0 || drawView.getHeight() == 0 ) { // apparently on 4.x, the orientation change notification can occur // sometime before the view is rendered. In that case, the view // dimensions will not be known. Log.e(t,"view has zero width or zero height"); } else { FileOutputStream fos; fos = new FileOutputStream(f); Bitmap bitmap = Bitmap.createBitmap(drawView.getWidth(), drawView.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawView.draw(canvas); bitmap.compress(Bitmap.CompressFormat.JPEG, 70, fos); try { if ( fos != null ) { fos.flush(); fos.close(); } } catch ( Exception e) { } } } private void Reset() { savepointImage.delete(); if (!OPTION_SIGNATURE.equals(loadOption) && refImage != null && refImage.exists()) { FileUtils.copyFile(refImage, savepointImage); } drawView.reset(); drawView.invalidate(); } private void CancelAndClose() { setResult(Activity.RESULT_CANCELED); this.finish(); } /* * (non-Javadoc) * @see android.app.Activity#onConfigurationChanged(android.content.res.Configuration) */ @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } /* * (non-Javadoc) * @see android.app.Activity#onKeyDown(int, android.view.KeyEvent) */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: createQuitDrawDialog(); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (event.isAltPressed()) { createQuitDrawDialog(); return true; } break; case KeyEvent.KEYCODE_DPAD_LEFT: if (event.isAltPressed()) { createQuitDrawDialog(); return true; } break; } return super.onKeyDown(keyCode, event); } /** * Create a dialog with options to save and exit, save, or quit without * saving */ private void createQuitDrawDialog() { String[] items = { getString(R.string.keep_changes), getString(R.string.do_not_save) }; alertDialog = new AlertDialog.Builder(this) .setIcon(android.R.drawable.ic_dialog_info) .setTitle(alertTitleString) .setNeutralButton(getString(R.string.do_not_exit), new DialogInterface.OnClickListener() { /* * (non-Javadoc) * @see android.content.DialogInterface.OnClickListener#onClick(android.content.DialogInterface, int) */ @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }) .setItems(items, new DialogInterface.OnClickListener() { /* * (non-Javadoc) * @see android.content.DialogInterface.OnClickListener#onClick(android.content.DialogInterface, int) */ @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: // save and exit SaveAndClose(); break; case 1: // discard changes and exit CancelAndClose(); break; case 2:// do nothing break; } } }).create(); alertDialog.show(); } public class DrawView extends View { private boolean isSignature; private Bitmap mBitmap; private Canvas mCanvas; private Path mCurrentPath; private Paint mBitmapPaint; private File mBackgroundBitmapFile; public DrawView(final Context c) { super(c); isSignature = false; mBitmapPaint = new Paint(Paint.DITHER_FLAG); mCurrentPath = new Path(); setBackgroundColor(0xFFFFFFFF); mBackgroundBitmapFile = new File(Collect.TMPDRAWFILE_PATH); } public DrawView(Context c, boolean isSignature, File f) { this(c); this.isSignature = isSignature; mBackgroundBitmapFile = f; } public void reset() { Display display = ((WindowManager) getContext().getSystemService( Context.WINDOW_SERVICE)).getDefaultDisplay(); int screenWidth = display.getWidth(); int screenHeight = display.getHeight(); resetImage(screenWidth, screenHeight); } public void resetImage(int w, int h) { if (mBackgroundBitmapFile.exists()) { mBitmap = FileUtils.getBitmapScaledToDisplay( mBackgroundBitmapFile, w, h).copy( Bitmap.Config.ARGB_8888, true); // mBitmap = // Bitmap.createScaledBitmap(BitmapFactory.decodeFile(mBackgroundBitmapFile.getPath()), // w, h, true); mCanvas = new Canvas(mBitmap); } else { mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBitmap); mCanvas.drawColor(0xFFFFFFFF); if (isSignature) drawSignLine(); } } /* * (non-Javadoc) * @see android.view.View#onSizeChanged(int, int, int, int) */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); resetImage(w, h); } /* * (non-Javadoc) * @see android.view.View#onDraw(android.graphics.Canvas) */ @Override protected void onDraw(Canvas canvas) { canvas.drawColor(R.drawable.grey); canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); canvas.drawPath(mCurrentPath, paint); } private float mX, mY; private void touch_start(float x, float y) { mCurrentPath.reset(); mCurrentPath.moveTo(x, y); mX = x; mY = y; } public void drawSignLine() { mCanvas.drawLine(0, (int) (mCanvas.getHeight() * .7), mCanvas.getWidth(), (int) (mCanvas.getHeight() * .7), paint); } private void touch_move(float x, float y) { mCurrentPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); mX = x; mY = y; } private void touch_up() { if (mCurrentPath.isEmpty()) { mCanvas.drawPoint(mX, mY, pointPaint); } else { mCurrentPath.lineTo(mX, mY); // commit the path to our offscreen mCanvas.drawPath(mCurrentPath, paint); } // kill this so we don't double draw mCurrentPath.reset(); } /* * (non-Javadoc) * @see android.view.View#onTouchEvent(android.view.MotionEvent) */ @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touch_start(x, y); invalidate(); break; case MotionEvent.ACTION_MOVE: touch_move(x, y); invalidate(); break; case MotionEvent.ACTION_UP: touch_up(); invalidate(); break; } return true; } } }