Java tutorial
/* * This source is part of the * _____ ___ ____ * __ / / _ \/ _ | / __/___ _______ _ * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / * /___/ * repository. * * Copyright (C) 2013 Benoit 'BoD' Lubek (BoD@JRAF.org) * * 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.jraf.android.piclabel.app.form; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.location.Address; import android.location.Geocoder; import android.location.Location; import android.location.LocationManager; import android.media.ExifInterface; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.support.v4.app.FragmentActivity; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.EditText; import android.widget.ImageView; import android.widget.Spinner; import android.widget.Toast; import org.jraf.android.piclabel.Config; import org.jraf.android.piclabel.Constants; import org.jraf.android.piclabel.R; import org.jraf.android.util.async.Task; import org.jraf.android.util.async.TaskFragment; import org.jraf.android.util.bitmap.BitmapUtil; import org.jraf.android.util.io.IoUtil; import org.jraf.android.util.mediascanner.MediaScannerUtil; public class FormActivity extends FragmentActivity { private static final String TAG = Constants.TAG + FormActivity.class.getSimpleName(); private static final int JPEG_QUALITY = 85; private static final String FRAGMENT_RETAINED_STATE = "FRAGMENT_RETAINED_STATE"; private ImageView mImgThumbnail; private EditText mEdtDateTime; private EditText mEdtLocation; private View mBtnSave; private View mBtnShare; private Spinner mSpnFont; private TypefaceAdapter mTypefaceAdapter; private View mConLoading; private FormStateFragment mState; private File mImageFile; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.form); mImgThumbnail = (ImageView) findViewById(R.id.imgThumbnail); mEdtDateTime = (EditText) findViewById(R.id.edtDateTime); mEdtLocation = (EditText) findViewById(R.id.edtLocation); mSpnFont = (Spinner) findViewById(R.id.spnFont); mTypefaceAdapter = new TypefaceAdapter(this); mSpnFont.setAdapter(mTypefaceAdapter); mBtnSave = findViewById(R.id.btnSave); mBtnSave.setOnClickListener(mSaveOnClickListener); mBtnShare = findViewById(R.id.btnShare); mBtnShare.setOnClickListener(mShareOnClickListener); mConLoading = findViewById(R.id.pgbLoading); mImageFile = new File(getIntent().getData().getPath()); restoreState(); } private void restoreState() { mState = (FormStateFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_RETAINED_STATE); if (mState == null) { mState = new FormStateFragment(); getSupportFragmentManager().beginTransaction().add(mState, FRAGMENT_RETAINED_STATE).commit(); retrieveInfoFromImage(); } else { mImgThumbnail.setImageBitmap(mState.thumbnailBitmap); if (mState.thumbnailBitmap != null) { mEdtDateTime.setEnabled(true); mEdtLocation.setEnabled(true); mSpnFont.setEnabled(true); mBtnSave.setEnabled(true); mBtnShare.setEnabled(true); mConLoading.setVisibility(View.GONE); } } } private void retrieveInfoFromImage() { new TaskFragment(new Task<FormActivity>() { private ImageInfo mImageInfo; @Override protected void doInBackground() { mState.thumbnailBitmap = BitmapUtil.createThumbnail(mImageFile, 320, 320); mImageInfo = extractImageInfo(mImageFile); } @Override protected void onPostExecuteOk() { if (getActivity().mState.thumbnailBitmap == null) { Toast.makeText(getActivity(), R.string.form_couldNotDecodeImage, Toast.LENGTH_LONG).show(); getActivity().finish(); return; } Animation anim = AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_out); getActivity().mConLoading.startAnimation(anim); getActivity().mConLoading.setVisibility(View.GONE); getActivity().mEdtDateTime.setText(""); getActivity().mEdtDateTime.append(mImageInfo.dateTime); if (mImageInfo.isLocalDateTime) getActivity().mEdtDateTime.setError(getActivity().getString(R.string.form_useLocalDate)); getActivity().mEdtLocation.setText(""); getActivity().mEdtLocation.append(mImageInfo.location); if (mImageInfo.reverseGeocodeProblem) { getActivity().mEdtLocation .setError(getActivity().getString(R.string.form_cannotReverseGeocode)); } else if (mImageInfo.isLocalLocation) { getActivity().mEdtLocation.setError(getActivity().getString(R.string.form_useLocalLocation)); } getActivity().mImgThumbnail.setImageBitmap(mState.thumbnailBitmap); getActivity().mEdtDateTime.setEnabled(true); getActivity().mEdtLocation.setEnabled(true); getActivity().mSpnFont.setEnabled(true); getActivity().mBtnSave.setEnabled(true); getActivity().mBtnShare.setEnabled(true); } }).execute(getSupportFragmentManager()); } private static class ImageInfo { public String dateTime; public String location; public boolean isLocalDateTime; public boolean isLocalLocation; public boolean reverseGeocodeProblem; } protected ImageInfo extractImageInfo(File file) { ImageInfo res = new ImageInfo(); ExifInterface exifInterface = null; try { exifInterface = new ExifInterface(file.getPath()); } catch (IOException e) { Log.e(TAG, "extractImageInfo Could not read exif", e); } // Date String dateTimeStr = null; if (exifInterface != null) dateTimeStr = exifInterface.getAttribute(ExifInterface.TAG_DATETIME); if (TextUtils.isEmpty(dateTimeStr)) { // No date in exif: use 'local' date res.dateTime = DateUtils.formatDateTime(this, System.currentTimeMillis(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_YEAR); res.isLocalDateTime = true; } else { res.dateTime = parseExifDateTime(dateTimeStr); if (res.dateTime == null) { // Date in exif could not be parsed: use 'local' date DateUtils.formatDateTime(this, System.currentTimeMillis(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_YEAR); res.isLocalDateTime = true; } } // Location float[] latLon = new float[2]; boolean latLonPresent = exifInterface != null && exifInterface.getLatLong(latLon); if (!latLonPresent) { // No location in exif: use 'local' location res.isLocalLocation = true; latLonPresent = getLatestLocalLocation(latLon); if (latLonPresent) res.location = reverseGeocode(latLon[0], latLon[1]); } else { res.location = reverseGeocode(latLon[0], latLon[1]); } if (res.location == null) { res.reverseGeocodeProblem = true; res.location = ""; } return res; } private boolean getLatestLocalLocation(float[] latLon) { LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); List<String> providers = locationManager.getProviders(true); Location location = null; for (int i = providers.size() - 1; i >= 0; i--) { location = locationManager.getLastKnownLocation(providers.get(i)); if (location != null) break; } if (location == null) return false; latLon[0] = (float) location.getLatitude(); latLon[1] = (float) location.getLongitude(); return true; } private String reverseGeocode(float lat, float lon) { Geocoder geocoder = new Geocoder(this, Locale.getDefault()); List<Address> addresses; try { addresses = geocoder.getFromLocation(lat, lon, 1); } catch (Throwable t) { Log.w(TAG, "reverseGeocode Could not reverse geocode", t); return null; } if (addresses == null || addresses.isEmpty()) return null; Address address = addresses.get(0); ArrayList<String> strings = new ArrayList<String>(5); if (address.getMaxAddressLineIndex() > 0) strings.add(address.getAddressLine(0)); if (!TextUtils.isEmpty(address.getLocality())) strings.add(address.getLocality()); if (!TextUtils.isEmpty(address.getCountryName())) strings.add(address.getCountryName()); return TextUtils.join(", ", strings); } private String parseExifDateTime(String s) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy':'MM':'dd' 'HH':'mm':'ss", Locale.US); Date date; try { date = sdf.parse(s); } catch (ParseException e) { Log.e(TAG, "decodeExifDateTime Could not parse " + s, e); return null; } return DateUtils.formatDateTime(this, date.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_YEAR); } /* * Save. */ private final OnClickListener mSaveOnClickListener = new OnClickListener() { @Override public void onClick(View v) { new TaskFragment(new Task<FormActivity>() { @Override protected void doInBackground() throws Exception { Uri uri = processAndSaveImage(); if (uri == null) throw new Exception("Received null processed image"); } @Override protected void onPostExecuteOk() { super.onPostExecuteOk(); getActivity().finish(); } }.toastOk(R.string.form_process_success).toastFail(R.string.form_couldNotProcessImage)) .execute(getSupportFragmentManager()); } }; /* * Share. */ private final OnClickListener mShareOnClickListener = new OnClickListener() { @Override public void onClick(View v) { new TaskFragment(new Task<FormActivity>() { private Uri mUri; @Override protected void doInBackground() throws Exception { mUri = processAndSaveImage(); if (mUri == null) throw new Exception("Received null processed image"); } @Override protected void onPostExecuteOk() { Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("image/jpeg"); shareIntent.putExtra(Intent.EXTRA_STREAM, mUri); shareIntent.putExtra(Intent.EXTRA_SUBJECT, getActivity().getString(R.string.form_share_subject)); shareIntent.putExtra(Intent.EXTRA_TEXT, getActivity().getString(R.string.form_share_text)); shareIntent.putExtra("sms_body", getActivity().getString(R.string.form_share_subject)); getActivity().startActivity( Intent.createChooser(shareIntent, getActivity().getText(R.string.common_shareWith))); getActivity().finish(); } }.toastOk(R.string.form_process_success).toastFail(R.string.form_couldNotProcessImage)) .execute(getSupportFragmentManager()); } }; private Uri processAndSaveImage() throws Exception { BitmapFactory.Options options = new BitmapFactory.Options(); Bitmap bitmap = BitmapUtil.tryDecodeFile(mImageFile, options); if (bitmap == null) { Log.w(TAG, "processImage Could not decode file, returning null"); return null; } // Rotate if necessary int rotation = BitmapUtil.getExifRotation(mImageFile); if (rotation != 0) { if (Config.LOGD) Log.d(TAG, "processImage rotating bitmap"); Matrix matrix = new Matrix(); matrix.postRotate(rotation); Bitmap rotatedBitmap = null; try { rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); bitmap.recycle(); bitmap = rotatedBitmap; } catch (OutOfMemoryError exception) { Log.w(TAG, "processImage Could not rotate bitmap, keeping original orientation", exception); } } // To draw text we need a mutable bitmap try { bitmap = BitmapUtil.asImmutable(bitmap); } catch (Throwable t) { throw new Exception(t); } Canvas canvas = new Canvas(bitmap); // Draw text drawText(canvas); // Save the new bitmap to a file File processedImageFile = createProcessedFile(); try { saveBitmap(bitmap, mImageFile, processedImageFile); bitmap.recycle(); } catch (IOException e) { Log.e(TAG, "processImage Could not save bitmap, returning null", e); return null; } // Scan it return MediaScannerUtil.scanFileNow(this, processedImageFile); } private void drawText(Canvas canvas) { Paint paint = new Paint(); paint.setStyle(Paint.Style.FILL); paint.setAntiAlias(true); paint.setTypeface(Typeface.createFromAsset(getAssets(), "fonts/" + getSelectedFontName())); int textSize = canvas.getHeight() / 35; paint.setTextSize(textSize); int margin = textSize / 5; // Measure date/time String dateTime = mEdtDateTime.getText().toString(); Rect boundsDateTime = new Rect(); paint.getTextBounds(dateTime, 0, dateTime.length(), boundsDateTime); // Measure location String location = mEdtLocation.getText().toString(); Rect boundsLocation = new Rect(); paint.getTextBounds(location, 0, location.length(), boundsLocation); int totalWidth = boundsDateTime.width() + textSize * 2 + boundsLocation.width(); if (totalWidth > canvas.getWidth()) { // Draw on 2 lines // Draw a rectangle paint.setColor(Color.argb(180, 0, 0, 0)); canvas.drawRect(0, 0, canvas.getWidth(), -boundsDateTime.top + boundsDateTime.bottom + -boundsLocation.top + boundsLocation.bottom + margin * 3, paint); // Draw date/time paint.setColor(Color.WHITE); canvas.drawText(dateTime, margin, margin + -boundsDateTime.top, paint); // Draw location canvas.drawText(location, canvas.getWidth() - boundsLocation.right - boundsLocation.left - margin, margin + -boundsDateTime.top + boundsDateTime.bottom + margin + -boundsLocation.top, paint); } else { // Draw on 1 line // Draw a rectangle paint.setColor(Color.argb(180, 0, 0, 0)); canvas.drawRect(0, 0, canvas.getWidth(), margin + Math.max(boundsDateTime.height(), boundsLocation.height()) + margin, paint); // Draw date/time paint.setColor(Color.WHITE); canvas.drawText(dateTime, margin, margin + -boundsDateTime.top, paint); // Draw location canvas.drawText(location, canvas.getWidth() - boundsLocation.right - boundsLocation.left - margin, margin + -boundsLocation.top, paint); } } private File createProcessedFile() { File picturesPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); File albumPath = new File(picturesPath, Constants.ALBUM_NAME); albumPath.mkdirs(); String fileDateName = new SimpleDateFormat("yyyy-MM-dd'_'HH-mm-ss", Locale.US).format(new Date()); File res = new File(albumPath, fileDateName + ".jpg"); return res; } private static void saveBitmap(Bitmap bitmap, File originalFile, File outFile) throws IOException { // Compress to a new file FileOutputStream fileOutputStream = new FileOutputStream(outFile); bitmap.compress(CompressFormat.JPEG, JPEG_QUALITY, fileOutputStream); IoUtil.closeSilently(fileOutputStream); // Copy exif tags from original file BitmapUtil.copyExifTags(originalFile, outFile); } private String getSelectedFontName() { return mTypefaceAdapter.getTypefaceName(mSpnFont.getSelectedItemPosition()); } }