Java tutorial
/* * Twidere - Twitter client for Android * * Copyright (C) 2012 Mariotaku Lee <mariotaku.lee@gmail.com> * * 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.mariotaku.twidere.activity; import static org.mariotaku.twidere.util.Utils.copyStream; import static org.mariotaku.twidere.util.Utils.getBestCacheDir; import static org.mariotaku.twidere.util.Utils.getImageLoaderHttpClient; import static org.mariotaku.twidere.util.Utils.getRedirectedHttpResponse; import static org.mariotaku.twidere.util.Utils.parseString; import static org.mariotaku.twidere.util.Utils.showErrorToast; import it.sephiroth.android.library.imagezoom.ImageViewTouch; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.R; import org.mariotaku.twidere.util.BitmapDecodeHelper; import twitter4j.TwitterException; import twitter4j.http.HttpClientWrapper; import twitter4j.http.HttpResponse; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; public class ImageViewerActivity extends FragmentActivity implements Constants, OnClickListener, LoaderCallbacks<ImageViewerActivity.ImageLoader.Result> { private ImageViewTouch mImageView; private View mProgress; private ImageButton mRefreshStopSaveButton; private boolean mImageLoaded; private File mImageFile; @Override public void onClick(final View view) { final Uri uri = getIntent().getData(); switch (view.getId()) { case R.id.close: { onBackPressed(); break; } case R.id.refresh_stop_save: { final LoaderManager lm = getSupportLoaderManager(); if (!mImageLoaded && !lm.hasRunningLoaders()) { loadImage(false); } else if (!mImageLoaded && lm.hasRunningLoaders()) { stopLoading(); } else if (mImageLoaded) { saveImage(); } break; } case R.id.share: { if (uri == null) { break; } final Intent intent = new Intent(Intent.ACTION_SEND); if (mImageFile != null && mImageFile.exists()) { intent.setType("image/*"); intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(mImageFile)); } else { intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, uri.toString()); } startActivity(Intent.createChooser(intent, getString(R.string.share))); break; } case R.id.open_in_browser: { if (uri == null) { break; } final String scheme = uri.getScheme(); if ("http".equals(scheme) || "https".equals(scheme)) { final Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.addCategory(Intent.CATEGORY_BROWSABLE); try { startActivity(intent); } catch (final ActivityNotFoundException e) { // Ignore. } } break; } } } @Override public void onContentChanged() { super.onContentChanged(); mImageView = (ImageViewTouch) findViewById(R.id.image_viewer); mRefreshStopSaveButton = (ImageButton) findViewById(R.id.refresh_stop_save); mProgress = findViewById(R.id.progress); } @Override public Loader<ImageLoader.Result> onCreateLoader(final int id, final Bundle args) { mProgress.setVisibility(View.VISIBLE); mRefreshStopSaveButton.setImageResource(R.drawable.ic_menu_stop); final Uri uri = args != null ? (Uri) args.getParcelable(INTENT_KEY_URI) : null; return new ImageLoader(this, uri); } @Override public void onLoaderReset(final Loader<ImageLoader.Result> loader) { } @Override public void onLoadFinished(final Loader<ImageLoader.Result> loader, final ImageLoader.Result data) { if (data != null && data.bitmap != null) { mImageView.setImageBitmap(data.bitmap); mImageFile = data.file; mImageLoaded = true; mRefreshStopSaveButton.setImageResource(R.drawable.ic_menu_save); } else { mImageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.broken_image)); mImageFile = null; mImageLoaded = false; mRefreshStopSaveButton.setImageResource(R.drawable.ic_menu_refresh); if (data != null) { showErrorToast(this, null, data.exception, true); } } mProgress.setVisibility(View.GONE); } @Override protected void onCreate(final Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.image_viewer); loadImage(true); } @Override protected void onDestroy() { super.onDestroy(); stopLoading(); } @Override protected void onNewIntent(final Intent intent) { super.onNewIntent(intent); setIntent(intent); loadImage(false); } private void loadImage(final boolean init) { getSupportLoaderManager().destroyLoader(0); final Uri uri = getIntent().getData(); if (uri == null) { finish(); return; } mImageView.setImageBitmap(null); final Bundle args = new Bundle(); args.putParcelable(INTENT_KEY_URI, uri); if (init) { getSupportLoaderManager().initLoader(0, args, this); } else { getSupportLoaderManager().restartLoader(0, args, this); } } private void saveImage() { if (mImageFile != null && mImageFile.exists()) { final Uri uri = getIntent().getData(); if (uri == null) return; final String file_name = uri.getLastPathSegment(); final BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeFile(mImageFile.getPath(), o); final String mime_type = o.outMimeType; String file_name_with_suffix = null; if (file_name.matches("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|PNG|JPG|JPEG|GIF|BMP)$")) { file_name_with_suffix = file_name; } else { if (mime_type == null) return; if (mime_type.startsWith("image/") && !"image/*".equals(mime_type)) { file_name_with_suffix = file_name + "." + mime_type.substring(5); } } final Intent intent = new Intent(INTENT_ACTION_SAVE_FILE); intent.setPackage(getPackageName()); intent.putExtra(INTENT_KEY_FILE_SOURCE, mImageFile.getPath()); intent.putExtra(INTENT_KEY_FILENAME, file_name_with_suffix); startActivity(intent); } } private void stopLoading() { getSupportLoaderManager().destroyLoader(0); if (!mImageLoaded) { mRefreshStopSaveButton.setImageResource(R.drawable.ic_menu_refresh); mImageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.refresh_image)); mProgress.setVisibility(View.GONE); } } public static class ImageLoader extends AsyncTaskLoader<ImageLoader.Result> { private static final String CACHE_DIR_NAME = "cached_images"; private final Uri mUri; private final Context mContext; private final HttpClientWrapper mClient; private File mCacheDir; public ImageLoader(final Context context, final Uri uri) { super(context); mContext = context; mUri = uri; mClient = getImageLoaderHttpClient(context); init(); } @Override public Result loadInBackground() { if (mUri == null) return new Result(null, null, null); final String scheme = mUri.getScheme(); if ("http".equals(scheme) || "https".equals(scheme)) { final String url = parseString(mUri.toString()); if (url == null) return new Result(null, null, null); if (mCacheDir == null || !mCacheDir.exists()) { init(); } final File cache_file = new File(mCacheDir, getURLFilename(url)); // from SD cache final Bitmap cached_bitmap = decodeFile(cache_file); if (cached_bitmap != null) return new Result(cached_bitmap, cache_file, null); // from web try { final HttpResponse resp = getRedirectedHttpResponse(mClient, url); if (resp == null) return null; final InputStream is = resp.asStream(); final OutputStream os = new FileOutputStream(cache_file); copyStream(is, os); os.close(); final Bitmap bitmap = decodeFile(cache_file); if (bitmap == null) { // The file is corrupted, so we remove it from // cache. if (cache_file.isFile()) { cache_file.delete(); } } return new Result(bitmap, cache_file, null); } catch (final FileNotFoundException e) { init(); } catch (final IOException e) { return new Result(null, null, e); } catch (final TwitterException e) { return new Result(null, null, e); } } else if ("file".equals(scheme)) { final File file = new File(mUri.getPath()); return new Result(decodeFile(file), file, null); } return new Result(null, null, null); } @Override protected void onStartLoading() { forceLoad(); } private Bitmap decodeFile(final File f) { if (f == null) return null; final int max_texture_size = getMaximumTextureSize(); final BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeFile(f.getPath(), o); if (o.outHeight <= 0) return null; final BitmapFactory.Options o1 = new BitmapFactory.Options(); final double size = Math.max(o.outWidth, o.outHeight); o1.inSampleSize = size > max_texture_size ? (int) Math.round(size / max_texture_size) + 1 : 1; Bitmap bitmap = null; while (bitmap == null) { try { final BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = o1.inSampleSize; bitmap = BitmapDecodeHelper.decode(f.getPath(), o2); } catch (final OutOfMemoryError e) { o1.inSampleSize++; continue; } if (bitmap == null) { break; } return bitmap; } return null; } private String getURLFilename(final String url) { if (url == null) return null; return url.replaceFirst("https?:\\/\\/", "").replaceAll("[^a-zA-Z0-9]", "_"); } private void init() { /* Find the dir to save cached images. */ mCacheDir = getBestCacheDir(mContext, CACHE_DIR_NAME); if (mCacheDir != null && !mCacheDir.exists()) { mCacheDir.mkdirs(); } } public static int getMaximumTextureSize() { final EGL10 egl = (EGL10) EGLContext.getEGL(); final EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); // Initialise final int[] version = new int[2]; egl.eglInitialize(display, version); // Query total number of configurations final int[] totalConfigurations = new int[1]; egl.eglGetConfigs(display, null, 0, totalConfigurations); // Query actual list configurations final EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]]; egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations); final int[] textureSize = new int[1]; int maximumTextureSize = 0; // Iterate through all the configurations to located the maximum // texture size for (int i = 0; i < totalConfigurations[0]; i++) { // Only need to check for width since opengl textures are always // squared egl.eglGetConfigAttrib(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize); // Keep track of the maximum texture size if (maximumTextureSize < textureSize[0]) { maximumTextureSize = textureSize[0]; } } // Release egl.eglTerminate(display); return maximumTextureSize; } public static class Result { public final Bitmap bitmap; public final File file; public final Exception exception; public Result(final Bitmap bitmap, final File file, final Exception exception) { this.bitmap = bitmap; this.file = file; this.exception = exception; } } } }