summaryrefslogtreecommitdiffstats
path: root/src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java')
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java540
1 files changed, 540 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java b/src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java
new file mode 100644
index 0000000..945ccac
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2013 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 org.cyanogenmod.wallpaperpicker;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.widget.Toast;
+
+import com.android.gallery3d.common.BitmapCropTask;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource.InBitmapProvider;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+import org.cyanogenmod.wallpaperpicker.base.BaseActivity;
+import org.cyanogenmod.wallpaperpicker.util.Thunk;
+import org.cyanogenmod.wallpaperpicker.util.WallpaperUtils;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+public class WallpaperCropActivity extends BaseActivity implements Handler.Callback {
+ private static final String LOGTAG = "CropActivity";
+
+ private static final int REQUEST_CODE_STORAGE_PERMISSION_CHECK = 100;
+
+ protected static final String WALLPAPER_WIDTH_KEY = WallpaperUtils.WALLPAPER_WIDTH_KEY;
+ protected static final String WALLPAPER_HEIGHT_KEY = WallpaperUtils.WALLPAPER_HEIGHT_KEY;
+
+ /**
+ * The maximum bitmap size we allow to be returned through the intent.
+ * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
+ * have some overhead to hit so that we go way below the limit here to make
+ * sure the intent stays below 1MB.We should consider just returning a byte
+ * array instead of a Bitmap instance to avoid overhead.
+ */
+ public static final int MAX_BMAP_IN_INTENT = 750000;
+ public static final float WALLPAPER_SCREENS_SPAN = WallpaperUtils.WALLPAPER_SCREENS_SPAN;
+
+ private static final int MSG_LOAD_IMAGE = 1;
+
+ protected CropView mCropView;
+ protected View mProgressView;
+ protected Uri mUri;
+ protected View mSetWallpaperButton;
+
+ private HandlerThread mLoaderThread;
+ private Handler mLoaderHandler;
+ @Thunk
+ LoadRequest mCurrentLoadRequest;
+ private byte[] mTempStorageForDecoding = new byte[16 * 1024];
+ // A weak-set of reusable bitmaps
+ @Thunk Set<Bitmap> mReusableBitmaps =
+ Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (!hasStoragePermissions()) {
+ requestStoragePermissions();
+ } else {
+ load();
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ if (requestCode == REQUEST_CODE_STORAGE_PERMISSION_CHECK) {
+ for (int i = 0; i < permissions.length; i++ ) {
+ final String permission = permissions[i];
+ final int grantResult = grantResults[i];
+ if (permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) {
+ if (grantResult == PackageManager.PERMISSION_GRANTED) {
+ load();
+ } else {
+ Toast.makeText(this, getString(R.string.storage_permission_denied),
+ Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ }
+ }
+ }
+ }
+
+ private void load() {
+ mLoaderThread = new HandlerThread("wallpaper_loader");
+ mLoaderThread.start();
+ mLoaderHandler = new Handler(mLoaderThread.getLooper(), this);
+
+ init();
+ }
+
+ protected void init() {
+ setContentView(R.layout.wallpaper_cropper);
+
+ mCropView = (CropView) findViewById(R.id.cropView);
+ mProgressView = findViewById(R.id.loading);
+
+ Intent cropIntent = getIntent();
+ final Uri imageUri = cropIntent.getData();
+
+ if (imageUri == null) {
+ Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
+ finish();
+ return;
+ }
+
+ // Action bar
+ // Show the custom action bar view
+ final ActionBar actionBar = getActionBar();
+ actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
+ actionBar.getCustomView().setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ boolean finishActivityWhenDone = true;
+ cropImageAndSetWallpaper(imageUri, null, false, finishActivityWhenDone);
+ }
+ });
+ mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
+
+ // Load image in background
+ final BitmapRegionTileSource.UriBitmapSource bitmapSource =
+ new BitmapRegionTileSource.UriBitmapSource(getContext(), imageUri);
+ mSetWallpaperButton.setEnabled(false);
+ Runnable onLoad = new Runnable() {
+ public void run() {
+ if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
+ Toast.makeText(getContext(), R.string.wallpaper_load_fail,
+ Toast.LENGTH_LONG).show();
+ finish();
+ } else {
+ mSetWallpaperButton.setEnabled(true);
+ }
+ }
+ };
+ setCropViewTileSource(bitmapSource, true, false, null, onLoad);
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mCropView != null) {
+ mCropView.destroy();
+ }
+ if (mLoaderThread != null) {
+ mLoaderThread.quit();
+ }
+ super.onDestroy();
+ }
+
+ /**
+ * This is called on {@link #mLoaderThread}
+ */
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_LOAD_IMAGE) {
+ final LoadRequest req = (LoadRequest) msg.obj;
+ try {
+ req.src.loadInBackground(new InBitmapProvider() {
+
+ @Override
+ public Bitmap forPixelCount(int count) {
+ Bitmap bitmapToReuse = null;
+ // Find the smallest bitmap that satisfies the pixel count limit
+ synchronized (mReusableBitmaps) {
+ int currentBitmapSize = Integer.MAX_VALUE;
+ for (Bitmap b : mReusableBitmaps) {
+ int bitmapSize = b.getWidth() * b.getHeight();
+ if ((bitmapSize >= count) && (bitmapSize < currentBitmapSize)) {
+ bitmapToReuse = b;
+ currentBitmapSize = bitmapSize;
+ }
+ }
+
+ if (bitmapToReuse != null) {
+ mReusableBitmaps.remove(bitmapToReuse);
+ }
+ }
+ return bitmapToReuse;
+ }
+ });
+ } catch (SecurityException securityException) {
+ if (isActivityDestroyed()) {
+ // Temporarily granted permissions are revoked when the activity
+ // finishes, potentially resulting in a SecurityException here.
+ // Even though {@link #isDestroyed} might also return true in different
+ // situations where the configuration changes, we are fine with
+ // catching these cases here as well.
+ return true;
+ } else {
+ // otherwise it had a different cause and we throw it further
+ throw securityException;
+ }
+ }
+
+ req.result = new BitmapRegionTileSource(getContext(), req.src, mTempStorageForDecoding);
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (req == mCurrentLoadRequest) {
+ onLoadRequestComplete(req,
+ req.src.getLoadingState() == BitmapSource.State.LOADED);
+ } else {
+ addReusableBitmap(req.result);
+ }
+ }
+ });
+ return true;
+ }
+ return false;
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ protected boolean isActivityDestroyed() {
+ return Utilities.ATLEAST_JB_MR1 && isDestroyed();
+ }
+
+ @Thunk void addReusableBitmap(TileSource src) {
+ synchronized (mReusableBitmaps) {
+ if (Utilities.ATLEAST_KITKAT && src instanceof BitmapRegionTileSource) {
+ Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
+ if (preview != null && preview.isMutable()) {
+ mReusableBitmaps.add(preview);
+ }
+ }
+ }
+ }
+
+ protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+ mCurrentLoadRequest = null;
+ if (success) {
+ TileSource oldSrc = mCropView.getTileSource();
+ mCropView.setTileSource(req.result, null);
+ mCropView.setTouchEnabled(req.touchEnabled);
+ if (req.moveToLeft) {
+ mCropView.moveToLeft();
+ }
+ if (req.scaleProvider != null) {
+ mCropView.setScale(req.scaleProvider.getScale(req.result));
+ }
+
+ // Free last image
+ if (oldSrc != null) {
+ // Call yield instead of recycle, as we only want to free GL resource.
+ // We can still reuse the bitmap for decoding any other image.
+ oldSrc.getPreview().yield();
+ }
+ addReusableBitmap(oldSrc);
+ }
+ if (req.postExecute != null) {
+ req.postExecute.run();
+ }
+ mProgressView.setVisibility(View.GONE);
+ }
+
+ public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled,
+ boolean moveToLeft, CropViewScaleProvider scaleProvider, Runnable postExecute) {
+ final LoadRequest req = new LoadRequest();
+ req.moveToLeft = moveToLeft;
+ req.src = bitmapSource;
+ req.touchEnabled = touchEnabled;
+ req.postExecute = postExecute;
+ req.scaleProvider = scaleProvider;
+ mCurrentLoadRequest = req;
+
+ // Remove any pending requests
+ mLoaderHandler.removeMessages(MSG_LOAD_IMAGE);
+ Message.obtain(mLoaderHandler, MSG_LOAD_IMAGE, req).sendToTarget();
+
+ // We don't want to show the spinner every time we load an image, because that would be
+ // annoying; instead, only start showing the spinner if loading the image has taken
+ // longer than 1 sec (ie 1000 ms)
+ mProgressView.postDelayed(new Runnable() {
+ public void run() {
+ if (mCurrentLoadRequest == req) {
+ mProgressView.setVisibility(View.VISIBLE);
+ }
+ }
+ }, 1000);
+ }
+
+
+ protected void setWallpaper(Uri uri, boolean isLockScreenPicker,
+ final boolean finishActivityWhenDone) {
+ int rotation = BitmapUtils.getRotationFromExif(getContext(), uri);
+ BitmapCropTask cropTask = new BitmapCropTask(
+ getContext(), uri, null, rotation, 0, 0, true, isLockScreenPicker, false, null);
+ final Point bounds = cropTask.getImageBounds();
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ updateWallpaperDimensions(bounds.x, bounds.y);
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ cropTask.setOnEndRunnable(onEndCrop);
+ cropTask.setNoCrop(true);
+ cropTask.execute();
+ }
+
+ protected void cropImageAndSetWallpaper(Resources res, int resId, boolean isLockScreenPicker,
+ final boolean finishActivityWhenDone) {
+ // crop this image and scale it down to the default wallpaper size for
+ // this device
+ int rotation = BitmapUtils.getRotationFromExif(res, resId);
+ Point inSize = mCropView.getSourceDimensions();
+ Point outSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
+ getWindowManager());
+ RectF crop = Utils.getMaxCropRect(
+ inSize.x, inSize.y, outSize.x, outSize.y, false);
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ // Passing 0, 0 will cause launcher to revert to using the
+ // default wallpaper size
+ updateWallpaperDimensions(0, 0);
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ BitmapCropTask cropTask = new BitmapCropTask(getContext(), res, resId,
+ crop, rotation, outSize.x, outSize.y, true, isLockScreenPicker, false, onEndCrop);
+ cropTask.execute();
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ protected void cropImageAndSetWallpaper(Uri uri,
+ BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler,
+ boolean isLockScreenPicker, final boolean finishActivityWhenDone) {
+ boolean centerCrop = getResources().getBoolean(R.bool.center_crop);
+ // Get the crop
+ boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+
+ Display d = getWindowManager().getDefaultDisplay();
+
+ Point displaySize = new Point();
+ d.getSize(displaySize);
+ boolean isPortrait = displaySize.x < displaySize.y;
+
+ Point defaultWallpaperSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
+ getWindowManager());
+ // Get the crop
+ RectF cropRect = mCropView.getCrop();
+
+ Point inSize = mCropView.getSourceDimensions();
+
+ int cropRotation = mCropView.getImageRotation();
+ float cropScale = mCropView.getWidth() / (float) cropRect.width();
+
+
+ Matrix rotateMatrix = new Matrix();
+ rotateMatrix.setRotate(cropRotation);
+ float[] rotatedInSize = new float[] { inSize.x, inSize.y };
+ rotateMatrix.mapPoints(rotatedInSize);
+ rotatedInSize[0] = Math.abs(rotatedInSize[0]);
+ rotatedInSize[1] = Math.abs(rotatedInSize[1]);
+
+
+ // due to rounding errors in the cropview renderer the edges can be slightly offset
+ // therefore we ensure that the boundaries are sanely defined
+ cropRect.left = Math.max(0, cropRect.left);
+ cropRect.right = Math.min(rotatedInSize[0], cropRect.right);
+ cropRect.top = Math.max(0, cropRect.top);
+ cropRect.bottom = Math.min(rotatedInSize[1], cropRect.bottom);
+
+ // ADJUST CROP WIDTH
+ // Extend the crop all the way to the right, for parallax
+ // (or all the way to the left, in RTL)
+ float extraSpace;
+ if (centerCrop) {
+ extraSpace = 2f * Math.min(rotatedInSize[0] - cropRect.right, cropRect.left);
+ } else {
+ extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
+ }
+ // Cap the amount of extra width
+ float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width();
+ extraSpace = Math.min(extraSpace, maxExtraSpace);
+
+ if (centerCrop) {
+ cropRect.left -= extraSpace / 2f;
+ cropRect.right += extraSpace / 2f;
+ } else {
+ if (ltr) {
+ cropRect.right += extraSpace;
+ } else {
+ cropRect.left -= extraSpace;
+ }
+ }
+
+ // ADJUST CROP HEIGHT
+ if (isPortrait) {
+ cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale;
+ } else { // LANDSCAPE
+ float extraPortraitHeight =
+ defaultWallpaperSize.y / cropScale - cropRect.height();
+ float expandHeight =
+ Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
+ extraPortraitHeight / 2);
+ cropRect.top -= expandHeight;
+ cropRect.bottom += expandHeight;
+ }
+ final int outWidth = (int) Math.round(cropRect.width() * cropScale);
+ final int outHeight = (int) Math.round(cropRect.height() * cropScale);
+
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ updateWallpaperDimensions(outWidth, outHeight);
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ BitmapCropTask cropTask = new BitmapCropTask(getContext(), uri, cropRect, cropRotation,
+ outWidth, outHeight, true, isLockScreenPicker, false, onEndCrop);
+ if (onBitmapCroppedHandler != null) {
+ cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
+ }
+ cropTask.execute();
+ }
+
+ protected void cropImageAndSetWallpaper(String path, String packageName,
+ boolean isLockScreenPicker, final boolean finishActivityWhenDone) {
+ // crop this image and scale it down to the default wallpaper size for
+ // this device
+ Point inSize = mCropView.getSourceDimensions();
+ Point outSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
+ getWindowManager());
+ RectF cropRect = Utils.getMaxCropRect(
+ inSize.x, inSize.y, outSize.x, outSize.y, false);
+
+ final Resources res;
+ try {
+ res = getPackageManager().getResourcesForApplication(packageName);
+ if (res == null) {
+ return;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ BitmapCropTask cropTask = new BitmapCropTask(getContext(), res, path, cropRect,
+ 0, outSize.x, outSize.y, true, isLockScreenPicker, false, onEndCrop);
+ if (cropTask != null) {
+ cropTask.execute();
+ }
+ }
+
+ protected void updateWallpaperDimensions(int width, int height) {
+ String spKey = PickerFiles.WALLPAPER_CROP_PREFERENCES_KEY;
+ SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
+ SharedPreferences.Editor editor = sp.edit();
+ if (width != 0 && height != 0) {
+ editor.putInt(WALLPAPER_WIDTH_KEY, width);
+ editor.putInt(WALLPAPER_HEIGHT_KEY, height);
+ } else {
+ editor.remove(WALLPAPER_WIDTH_KEY);
+ editor.remove(WALLPAPER_HEIGHT_KEY);
+ }
+ editor.commit();
+ WallpaperUtils.suggestWallpaperDimension(getResources(),
+ sp, getWindowManager(), WallpaperManager.getInstance(getContext()), true);
+ }
+
+ private boolean hasStoragePermissions() {
+ return checkCallingOrSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) ==
+ PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void requestStoragePermissions() {
+ requestPermissions(new String[] {android.Manifest.permission.READ_EXTERNAL_STORAGE},
+ REQUEST_CODE_STORAGE_PERMISSION_CHECK);
+ }
+
+ static class LoadRequest {
+ BitmapSource src;
+ boolean touchEnabled;
+ boolean moveToLeft;
+ Runnable postExecute;
+ CropViewScaleProvider scaleProvider;
+
+ TileSource result;
+ }
+
+ interface CropViewScaleProvider {
+ float getScale(TileSource src);
+ }
+}