com.jafme.mobile.activity.CropImageActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.jafme.mobile.activity.CropImageActivity.java

Source

/*
 * 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);
        }
    }

}