Java tutorial
/* * 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 com.jafme.mobile.activity; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.net.Uri; import android.opengl.GLES10; import android.os.Bundle; import android.os.Handler; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; import android.util.Base64; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Toast; import com.google.android.gms.analytics.HitBuilders; import com.google.android.gms.analytics.Tracker; import com.google.gson.JsonObject; import com.jafme.mobile.JafmeApp; import com.jafme.mobile.R; import com.jafme.mobile.api.Api; import com.jafme.mobile.api.ApiRest; import com.jafme.mobile.model.api.Photo; import com.jafme.mobile.model.api.Vacancy; import com.jafme.mobile.utils.analytics.Analytics; import com.jafme.mobile.utils.Utils; import com.jafme.mobile.utils.crop.CropImageView; import com.jafme.mobile.utils.crop.CropUtil; import com.jafme.mobile.utils.crop.HighlightView; import com.jafme.mobile.utils.crop.ImageViewTouchBase; import com.jafme.mobile.utils.crop.MonitoredActivity; import com.jafme.mobile.utils.crop.RotateBitmap; import com.jafme.mobile.utils.network.Network; import com.jafme.mobile.utils.toolbar.ToolbarUtils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import retrofit.RetrofitError; import retrofit.client.Response; public class CropImageActivity extends MonitoredActivity { // original private static final int SIZE_DEFAULT = 2048; private static final int SIZE_LIMIT = 4096; private final Handler handler = new Handler(); private int aspectX; private int aspectY; private int maxX; private int maxY; private int exifRotation; private Uri sourceUri; private boolean isSaving; private int sampleSize; private RotateBitmap rotateBitmap; private CropImageView imageView; private HighlightView cropView; // my public static final int TYPE_APPLICANT_PHOTO = 1; public static final int TYPE_USER_AVATAR = 2; public static final int TYPE_VACANCY_PHOTO = 3; public static final String RESULT_PHOTO = CropImageActivity.class.getName() + ".RESULT_PHOTO"; public static final String RESULT_TYPE = CropImageActivity.class.getName() + ".RESULT_TYPE"; public static final String RESULT_ERROR = CropImageActivity.class.getName() + ".RESULT_ERROR"; public static final String RESULT_VACANCY_ID = CropImageActivity.class.getName() + ".RESULT_VACANCY_ID"; public static final String ARG_VACANCY_ID = CropImageActivity.class.getName() + ".ARG_VACANCY_ID"; public static final String ARG_IS_PRIMARY = CropImageActivity.class.getName() + ".ARG_IS_PRIMARY"; public static final String EXT_TYPE = CropImageActivity.class.getName() + ".EXT_TYPE"; public static final String EXT_ARGS = CropImageActivity.class.getName() + ".EXT_ARGS"; public static final String EXT_REMOVE_ORIGINAL = CropImageActivity.class.getName() + ".EXT_REMOVE_ORIGINAL"; public static final String EXT_MAX_X = CropImageActivity.class.getName() + ".EXT_MAX_X"; public static final String EXT_MAX_Y = CropImageActivity.class.getName() + ".EXT_MAX_Y"; public static final String EXT_NEED_UPLOAD = CropImageActivity.class.getName() + ".EXT_NEED_UPLOAD"; private static final String LOG_TAG = CropImageActivity.class.getSimpleName(); private int type; private Bundle args; private boolean needUpload; private Toolbar toolbar; private View layoutPhoto; private boolean removeOriginal; private ToolbarUtils toolbarUtils; public static Intent createCropIntent(Context context, Uri source, int maxWidth, int maxHeight, boolean removeOriginal, int type, boolean needUpload, Bundle args) { final Intent cropIntent = new Intent(context, CropImageActivity.class); cropIntent.setData(source); cropIntent.putExtra(EXT_MAX_X, maxWidth); cropIntent.putExtra(EXT_MAX_Y, maxHeight); cropIntent.putExtra(EXT_REMOVE_ORIGINAL, removeOriginal); cropIntent.putExtra(EXT_TYPE, type); cropIntent.putExtra(EXT_NEED_UPLOAD, needUpload); cropIntent.putExtra(EXT_ARGS, args); return cropIntent; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_crop_image); Utils.setupRobotoFont(findViewById(R.id.layout_activity_crop_image)); Utils.setupSystemBarsTint(this, R.color.actionbar_bg, R.color.actionbar_bg); setResult(RESULT_CANCELED); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayShowHomeEnabled(true); actionBar.setDisplayShowTitleEnabled(true); actionBar.setHomeButtonEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setTitle(R.string.crop_photo); } toolbarUtils = new ToolbarUtils(this, toolbar, R.color.actionbar_bg, R.color.actionbar_text, R.color.color_primary, R.drawable.ic_action_back); toolbar.setVisibility(View.GONE); layoutPhoto = findViewById(R.id.layout_photo); assert layoutPhoto != null; layoutPhoto.setVisibility(View.GONE); // ================ imageView = (CropImageView) findViewById(R.id.crop_image); assert imageView != null; imageView.context = this; imageView.setRecycler(new ImageViewTouchBase.Recycler() { @Override public void recycle(Bitmap b) { b.recycle(); System.gc(); } }); Bundle extras = getIntent().getExtras(); if (extras != null) { sourceUri = getIntent().getData(); removeOriginal = extras.getBoolean(EXT_REMOVE_ORIGINAL); maxX = extras.getInt(EXT_MAX_X); maxY = extras.getInt(EXT_MAX_Y); type = extras.getInt(EXT_TYPE); needUpload = extras.getBoolean(EXT_NEED_UPLOAD); args = extras.getBundle(EXT_ARGS); switch (type) { case TYPE_USER_AVATAR: aspectX = 1; aspectY = 1; break; case TYPE_APPLICANT_PHOTO: aspectX = 0; aspectY = 0; break; case TYPE_VACANCY_PHOTO: aspectX = 0; aspectY = 0; break; } } if (sourceUri != null) { exifRotation = CropUtil.getExifRotation(CropUtil.getFromMediaUri(this, sourceUri)); InputStream is = null; try { sampleSize = calculateBitmapSampleSize(sourceUri); is = getContentResolver().openInputStream(sourceUri); BitmapFactory.Options option = new BitmapFactory.Options(); option.inSampleSize = sampleSize; rotateBitmap = new RotateBitmap(BitmapFactory.decodeStream(is, null, option), exifRotation); } catch (IOException e) { Log.e(LOG_TAG, "Error reading image: " + e.getMessage(), e); setResultException(e); } catch (OutOfMemoryError e) { Log.e(LOG_TAG, "OOM reading image: " + e.getMessage(), e); setResultException(e); } finally { CropUtil.closeSilently(is); } } else { finish(); return; } if (rotateBitmap == null) { finish(); return; } layoutPhoto.post(new Runnable() { @Override public void run() { startCrop(); } }); Analytics.setScreenName(this, "User-Crop_photo"); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_crop_image, menu); toolbarUtils.setupMenuItems(menu); toolbarUtils.setupMenuIconsTint(ContextCompat.getColor(this, R.color.color_primary)); return true; } @Override public boolean onOptionsItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { case R.id.menuitem_apply: done(); return true; default: return super.onOptionsItemSelected(menuItem); } } @Override public boolean onSupportNavigateUp() { finish(); return true; } private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException { InputStream is = null; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; try { is = getContentResolver().openInputStream(bitmapUri); BitmapFactory.decodeStream(is, null, options); // Just get image size } finally { CropUtil.closeSilently(is); } int maxSize = getMaxImageSize(); int sampleSize = 1; while (options.outHeight / sampleSize > maxSize || options.outWidth / sampleSize > maxSize) { sampleSize = sampleSize << 1; } return sampleSize; } private int getMaxImageSize() { int textureLimit = getMaxTextureSize(); return textureLimit == 0 ? SIZE_DEFAULT : Math.min(textureLimit, SIZE_LIMIT); } private int getMaxTextureSize() { // The OpenGL texture size is the maximum size that can be drawn in an ImageView int[] maxSize = new int[1]; GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0); return maxSize[0]; } private void startCrop() { if (isFinishing()) return; imageView.setImageRotateBitmapResetBase(rotateBitmap, true); CropUtil.startBackgroundJob(this, getString(R.string.preparing) + '\u2026', null, new Runnable() { public void run() { final CountDownLatch latch = new CountDownLatch(1); handler.post(new Runnable() { public void run() { if (imageView.getScale() == 1f) imageView.center(true, true); showCropper(); toolbar.setVisibility(View.VISIBLE); layoutPhoto.setVisibility(View.VISIBLE); latch.countDown(); } }); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, handler); } private void done() { if (Network.isNetworkDisconnected(this)) { Toast.makeText(this, R.string.no_network_connection, Toast.LENGTH_SHORT).show(); return; } if (cropView == null || isSaving) return; isSaving = true; Bitmap croppedImage; Rect r = cropView.getScaledCropRect(sampleSize); int width = r.width(); int height = r.height(); int outWidth = width; int outHeight = height; if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) { float ratio = (float) width / (float) height; if ((float) maxX / (float) maxY > ratio) { outHeight = maxY; outWidth = (int) ((float) maxY * ratio + .5f); } else { outWidth = maxX; outHeight = (int) ((float) maxX / ratio + .5f); } } try { croppedImage = decodeRegionCrop(r, outWidth, outHeight); } catch (IllegalArgumentException e) { setResultException(e); finish(); return; } if (croppedImage != null) { imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true); imageView.center(true, true); imageView.highlightViews.clear(); } saveImage(croppedImage); } private void saveImage(Bitmap croppedImage) { if (croppedImage != null) { final Bitmap b = croppedImage; CropUtil.startBackgroundJob(this, getString(needUpload ? R.string.uploading : R.string.saving) + '\u2026', null, new Runnable() { public void run() { uploadImage(b); } }, handler); } else { finish(); } } private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) { // Release memory now clearImageView(); InputStream is = null; Bitmap croppedImage = null; try { is = getContentResolver().openInputStream(sourceUri); BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false); final int width = decoder.getWidth(); final int height = decoder.getHeight(); if (exifRotation != 0) { // Adjust crop area to account for image rotation Matrix matrix = new Matrix(); matrix.setRotate(-exifRotation); RectF adjusted = new RectF(); matrix.mapRect(adjusted, new RectF(rect)); // Adjust to account for origin at 0,0 adjusted.offset(adjusted.left < 0 ? width : 0, adjusted.top < 0 ? height : 0); rect = new Rect((int) adjusted.left, (int) adjusted.top, (int) adjusted.right, (int) adjusted.bottom); } try { croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options()); // ? if (exifRotation != 0) { final Matrix matrix = new Matrix(); matrix.setRotate(exifRotation); croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true); exifRotation = 0; } int croppedWidth = croppedImage.getWidth(); int croppedHeight = croppedImage.getHeight(); if (croppedWidth > outWidth || croppedHeight > outHeight) { Matrix matrix = new Matrix(); matrix.postScale((float) outWidth / croppedWidth, (float) outHeight / croppedHeight); croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedWidth, croppedHeight, matrix, true); } } catch (IllegalArgumentException e) { // Rethrow with some extra information throw new IllegalArgumentException("Rectangle " + rect + " is outside of the image (" + width + "," + height + "," + exifRotation + ")", e); } } catch (IOException e) { Log.e(LOG_TAG, "Error cropping image: " + e.getMessage(), e); finish(); } catch (OutOfMemoryError e) { Log.e(LOG_TAG, "OOM cropping image: " + e.getMessage(), e); setResultException(e); } finally { CropUtil.closeSilently(is); } return croppedImage; } private void clearImageView() { imageView.clear(); if (rotateBitmap != null) { rotateBitmap.recycle(); } System.gc(); } private void uploadImage(Bitmap croppedBitmap) { if (!needUpload) { final File file = new File(getCacheDir(), "wizard_avatar"); final Uri fileUri = Uri.fromFile(file); OutputStream fileOutputStream = null; try { fileOutputStream = getContentResolver().openOutputStream(fileUri); if (fileOutputStream == null) throw new IOException("Can't open output stream"); if (croppedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream)) { fileOutputStream.flush(); final Photo photo = new Photo(-1, fileUri.toString()); setResultPhoto(photo, -1); } } catch (IOException e) { setResultException(e); } finally { CropUtil.closeSilently(fileOutputStream); } } else { long startTime = System.currentTimeMillis(); ByteArrayOutputStream baos = null; try { // ?? JPG baos baos = new ByteArrayOutputStream(); if (!croppedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, baos)) { throw new IOException("Can't compress photo"); } baos.flush(); // encod base64 final byte[] encodedJpgImage = Base64.encode(baos.toByteArray(), Base64.DEFAULT); final String base64Photo = new String(encodedJpgImage, "UTF-8"); /* try { FileWriter fileWriter = new FileWriter(new File(getExternalFilesDir(null), "photo.base64")); fileWriter.write(base64Photo); fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } */ // ? ? final JsonObject jsonRequest = new JsonObject(); jsonRequest.addProperty("image_string", base64Photo); jsonRequest.addProperty("image_extension", "jpg"); final ApiRest api = Api.getInstance(this); long vacancyId = 0; Response response; Photo photo; switch (type) { case TYPE_APPLICANT_PHOTO: response = api.uploadApplicantPhoto(jsonRequest); photo = Network.responseToObject(response, Photo.class, false); break; case TYPE_USER_AVATAR: response = api.uploadUserAvatar(jsonRequest); if (response == null) throw new IOException("No response received"); final int statusCode = response.getStatus(); if (statusCode < 200 || statusCode >= 300) throw new IOException(response.getReason()); // HTTP 204 No content - ? GET /user/avatar response = api.getUserAvatar(); @SuppressWarnings("unchecked") Map<String, String> paths = Network.responseToObject(response, HashMap.class, false); photo = new Photo(paths); break; case TYPE_VACANCY_PHOTO: vacancyId = args != null ? args.getLong(ARG_VACANCY_ID, -1) : -1; boolean isPrimary = args != null && args.getBoolean(ARG_IS_PRIMARY); if (vacancyId == -1) { // ? ? ? JsonObject jsonRequest2 = new JsonObject(); jsonRequest2.addProperty("position", getString(R.string.new_vacancy)); response = api.createEmployerVacancy(jsonRequest2); Vacancy newVacancy = Network.responseToObject(response, Vacancy.class, false); vacancyId = newVacancy.id; } jsonRequest.addProperty("is_primary", isPrimary); response = api.uploadVacancyPhoto(vacancyId, jsonRequest); photo = Network.responseToObject(response, Photo.class, false); break; default: throw new RuntimeException("Invalid photo type specified: " + type); } setResultPhoto(photo, vacancyId); long uploadTime = System.currentTimeMillis() - startTime; final Tracker tracker = ((JafmeApp) getApplication()).getDefaultTracker(); tracker.send(new HitBuilders.TimingBuilder().setCategory("UX").setValue(uploadTime) .setVariable("Photo upload").setLabel("Type: " + type).build()); } catch (IOException | RetrofitError e) { e.printStackTrace(); setResultException(e); } finally { CropUtil.closeSilently(baos); } } if (removeOriginal && "file".equals(sourceUri.getScheme())) { File file = new File(sourceUri.getPath()); if (!file.delete()) { file.deleteOnExit(); } } // ? bitmap ? final Bitmap b = croppedBitmap; handler.post(new Runnable() { public void run() { imageView.clear(); b.recycle(); } }); finish(); } @Override protected void onDestroy() { super.onDestroy(); if (rotateBitmap != null) { rotateBitmap.recycle(); } } @Override public boolean onSearchRequested() { return false; } public boolean isSaving() { return isSaving; } private void setResultPhoto(Photo photo, long vacancyId) { Intent resultIntent = new Intent(); resultIntent.putExtra(RESULT_PHOTO, photo); resultIntent.putExtra(RESULT_TYPE, type); resultIntent.putExtra(RESULT_VACANCY_ID, vacancyId); setResult(RESULT_OK, resultIntent); } private void setResultException(Throwable throwable) { Intent resultIntent = new Intent(); resultIntent.putExtra(RESULT_ERROR, throwable.getMessage()); resultIntent.putExtra(RESULT_TYPE, type); setResult(RESULT_CANCELED, resultIntent); } private void showCropper() { if (rotateBitmap == null) return; HighlightView hv = new HighlightView(imageView); final int width = rotateBitmap.getWidth(); final int height = rotateBitmap.getHeight(); Rect imageRect = new Rect(0, 0, width, height); // Make the default size about 4/5 of the width or height int cropWidth = Math.min(width, height) * 4 / 5; int cropHeight = cropWidth; if (aspectX != 0 && aspectY != 0) { if (aspectX > aspectY) { cropHeight = cropWidth * aspectY / aspectX; } else { cropWidth = cropHeight * aspectX / aspectY; } } int x = (width - cropWidth) / 2; int y = (height - cropHeight) / 2; // FIXME - ? ? RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight); hv.setup(imageView.getUnrotatedMatrix(), imageRect, cropRect, aspectX != 0 && aspectY != 0); imageView.add(hv); if (imageView.highlightViews.size() == 1) { cropView = imageView.highlightViews.get(0); cropView.setFocus(true); } } }