/* * Kontalk Android client * Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org> * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kontalk.ui.view; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.regex.Pattern; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import org.kontalk.Log; import org.kontalk.R; import org.kontalk.message.CompositeMessage; import org.kontalk.message.ImageComponent; import org.kontalk.ui.ComposeMessage; import org.kontalk.util.MediaStorage; /** * Message component for {@link ImageComponent}. * @author Daniele Ricci */ public class ImageContentView extends FrameLayout implements MessageContentView<ImageComponent> { static final String TAG = ComposeMessage.TAG; private ImageComponent mComponent; private ImageView mContent; private TextView mPlaceholder; public ImageContentView(Context context) { super(context); } public ImageContentView(Context context, AttributeSet attrs) { super(context, attrs); } public ImageContentView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onFinishInflate() { super.onFinishInflate(); mContent = (ImageView) findViewById(R.id.content); mPlaceholder = (TextView) findViewById(R.id.placeholder); } @Override public void bind(long messageId, ImageComponent component, Pattern highlight) { mComponent = component; Bitmap bitmap = getBitmap(); showBitmap(bitmap); } /** This method might be called from a thread other than the main thread. */ void showBitmap(Bitmap bitmap) { // this method might be called from another thread final ImageComponent component = mComponent; if (component == null) return; if (bitmap != null) { mContent.setImageBitmap(bitmap); mPlaceholder.setVisibility(GONE); mContent.setVisibility(VISIBLE); } else { String placeholder = CompositeMessage.getSampleTextContent(component.getContent().getMime()); mPlaceholder.setText(placeholder); TextContentView.setTextStyle(mPlaceholder); mContent.setVisibility(GONE); mPlaceholder.setVisibility(VISIBLE); } } private BitmapFactory.Options bitmapOptions() { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; return options; } Bitmap loadPreview(File previewFile) throws IOException { InputStream in = new FileInputStream(previewFile); BitmapFactory.Options options = bitmapOptions(); Bitmap bitmap = BitmapFactory.decodeStream(in, null, options); in.close(); return bitmap; } private Bitmap getBitmap() { /* * local_uri is used for referencing the original media. * preview_uri is used to load the media thumbnail. * If preview_uri is null or cannot be found, a thumbnail is * generated on the fly from local_uri - if possible. */ File previewFile = mComponent.getPreviewFile(); Uri localUri = mComponent.getLocalUri(); try { // preview path if (previewFile != null) { // load from file - we know it's a file uri return loadPreview(previewFile); } } catch (Exception e) { Log.w(TAG, "unable to load thumbnail, generating one"); /* * unable to load preview - generate thumbnail * Of course a thumbnail can be generated only if the image has * already been downloaded. */ if (localUri != null) { new GenerateThumbnailTask(getContext(), localUri, previewFile, new ThumbnailListener() { @Override public void onThumbnailGenerated(File previewFile) { try { Bitmap bitmap = loadPreview(previewFile); showBitmap(bitmap); } catch (IOException e) { // since at this point anything can happen, just ignore any errors Log.w(TAG, "unable to load generated thumbnail", e); } } }).execute(); } } return null; } @Override public void unbind() { clear(); } @Override public ImageComponent getComponent() { return mComponent; } /** Image is always on top. */ @Override public int getPriority() { return 1; } private void clear() { mComponent = null; mContent.setImageBitmap(null); } public static ImageContentView create(LayoutInflater inflater, ViewGroup parent) { return (ImageContentView) inflater.inflate(R.layout.message_content_image, parent, false); } interface ThumbnailListener { void onThumbnailGenerated(File previewFile); } final static class GenerateThumbnailTask extends AsyncTask<Void, Void, Boolean> { private final Uri mLocalUri; private final File mPreviewFile; private final WeakReference<Context> mContext; private final ThumbnailListener mListener; GenerateThumbnailTask(Context context, Uri localUri, File previewFile, ThumbnailListener listener) { mContext = new WeakReference<>(context); mLocalUri = localUri; mPreviewFile = previewFile; mListener = listener; } @Override protected Boolean doInBackground(Void... params) { Context context = mContext.get(); if (context != null) { try { MediaStorage.cacheThumbnail(context, mLocalUri, mPreviewFile, false); return true; } catch (Exception e) { Log.e(TAG, "unable to generate thumbnail", e); } } return false; } @Override protected void onPostExecute(Boolean result) { if (result != null && result) { mListener.onThumbnailGenerated(mPreviewFile); } } } }