summaryrefslogtreecommitdiffstats
path: root/WallpaperPicker/src/com/android/launcher3
diff options
context:
space:
mode:
authorMichael Jurka <mikejurka@google.com>2013-12-12 15:04:25 +0100
committerDanesh M <daneshm90@gmail.com>2014-06-06 22:54:22 -0700
commitaa05711971753bb31cc01f46d4ab10fcf9f9af3b (patch)
tree9785eb5042e55a5dd26d5407b365356a591168f2 /WallpaperPicker/src/com/android/launcher3
parent184854625a903641da354e5bcdec544f3ebccbcf (diff)
downloadandroid_packages_apps_Trebuchet-aa05711971753bb31cc01f46d4ab10fcf9f9af3b.tar.gz
android_packages_apps_Trebuchet-aa05711971753bb31cc01f46d4ab10fcf9f9af3b.tar.bz2
android_packages_apps_Trebuchet-aa05711971753bb31cc01f46d4ab10fcf9f9af3b.zip
Create separate project for Wallpaper Picker
Change-Id: Id9e855780b9fb68c63eb6e9f6c19bcbce28a6fd5
Diffstat (limited to 'WallpaperPicker/src/com/android/launcher3')
-rw-r--r--WallpaperPicker/src/com/android/launcher3/CheckableFrameLayout.java63
-rw-r--r--WallpaperPicker/src/com/android/launcher3/CropView.java322
-rw-r--r--WallpaperPicker/src/com/android/launcher3/DrawableTileSource.java102
-rw-r--r--WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java204
-rw-r--r--WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java241
-rw-r--r--WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java139
-rw-r--r--WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java836
-rw-r--r--WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java1044
-rw-r--r--WallpaperPicker/src/com/android/launcher3/WallpaperRootView.java39
9 files changed, 2990 insertions, 0 deletions
diff --git a/WallpaperPicker/src/com/android/launcher3/CheckableFrameLayout.java b/WallpaperPicker/src/com/android/launcher3/CheckableFrameLayout.java
new file mode 100644
index 000000000..5b7d82425
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/CheckableFrameLayout.java
@@ -0,0 +1,63 @@
+/*
+ * 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 com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.FrameLayout;
+
+public class CheckableFrameLayout extends FrameLayout implements Checkable {
+ private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
+ boolean mChecked;
+
+ public CheckableFrameLayout(Context context) {
+ super(context);
+ }
+
+ public CheckableFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CheckableFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public boolean isChecked() {
+ return mChecked;
+ }
+
+ public void setChecked(boolean checked) {
+ if (checked != mChecked) {
+ mChecked = checked;
+ refreshDrawableState();
+ }
+ }
+
+ public void toggle() {
+ setChecked(!mChecked);
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (isChecked()) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ }
+ return drawableState;
+ }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/CropView.java b/WallpaperPicker/src/com/android/launcher3/CropView.java
new file mode 100644
index 000000000..578b8eafd
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/CropView.java
@@ -0,0 +1,322 @@
+/*
+ * 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 com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.ViewConfiguration;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import com.android.photos.views.TiledImageRenderer.TileSource;
+import com.android.photos.views.TiledImageView;
+
+public class CropView extends TiledImageView implements OnScaleGestureListener {
+
+ private ScaleGestureDetector mScaleGestureDetector;
+ private long mTouchDownTime;
+ private float mFirstX, mFirstY;
+ private float mLastX, mLastY;
+ private float mCenterX, mCenterY;
+ private float mMinScale;
+ private boolean mTouchEnabled = true;
+ private RectF mTempEdges = new RectF();
+ private float[] mTempPoint = new float[] { 0, 0 };
+ private float[] mTempCoef = new float[] { 0, 0 };
+ private float[] mTempAdjustment = new float[] { 0, 0 };
+ private float[] mTempImageDims = new float[] { 0, 0 };
+ private float[] mTempRendererCenter = new float[] { 0, 0 };
+ TouchCallback mTouchCallback;
+ Matrix mRotateMatrix;
+ Matrix mInverseRotateMatrix;
+
+ public interface TouchCallback {
+ void onTouchDown();
+ void onTap();
+ void onTouchUp();
+ }
+
+ public CropView(Context context) {
+ this(context, null);
+ }
+
+ public CropView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mScaleGestureDetector = new ScaleGestureDetector(context, this);
+ mRotateMatrix = new Matrix();
+ mInverseRotateMatrix = new Matrix();
+ }
+
+ private float[] getImageDims() {
+ final float imageWidth = mRenderer.source.getImageWidth();
+ final float imageHeight = mRenderer.source.getImageHeight();
+ float[] imageDims = mTempImageDims;
+ imageDims[0] = imageWidth;
+ imageDims[1] = imageHeight;
+ mRotateMatrix.mapPoints(imageDims);
+ imageDims[0] = Math.abs(imageDims[0]);
+ imageDims[1] = Math.abs(imageDims[1]);
+ return imageDims;
+ }
+
+ private void getEdgesHelper(RectF edgesOut) {
+ final float width = getWidth();
+ final float height = getHeight();
+ final float[] imageDims = getImageDims();
+ final float imageWidth = imageDims[0];
+ final float imageHeight = imageDims[1];
+
+ float initialCenterX = mRenderer.source.getImageWidth() / 2f;
+ float initialCenterY = mRenderer.source.getImageHeight() / 2f;
+
+ float[] rendererCenter = mTempRendererCenter;
+ rendererCenter[0] = mCenterX - initialCenterX;
+ rendererCenter[1] = mCenterY - initialCenterY;
+ mRotateMatrix.mapPoints(rendererCenter);
+ rendererCenter[0] += imageWidth / 2;
+ rendererCenter[1] += imageHeight / 2;
+
+ final float scale = mRenderer.scale;
+ float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
+ * scale + width / 2f;
+ float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
+ * scale + height / 2f;
+ float leftEdge = centerX - imageWidth / 2f * scale;
+ float rightEdge = centerX + imageWidth / 2f * scale;
+ float topEdge = centerY - imageHeight / 2f * scale;
+ float bottomEdge = centerY + imageHeight / 2f * scale;
+
+ edgesOut.left = leftEdge;
+ edgesOut.right = rightEdge;
+ edgesOut.top = topEdge;
+ edgesOut.bottom = bottomEdge;
+ }
+
+ public int getImageRotation() {
+ return mRenderer.rotation;
+ }
+
+ public RectF getCrop() {
+ final RectF edges = mTempEdges;
+ getEdgesHelper(edges);
+ final float scale = mRenderer.scale;
+
+ float cropLeft = -edges.left / scale;
+ float cropTop = -edges.top / scale;
+ float cropRight = cropLeft + getWidth() / scale;
+ float cropBottom = cropTop + getHeight() / scale;
+
+ return new RectF(cropLeft, cropTop, cropRight, cropBottom);
+ }
+
+ public Point getSourceDimensions() {
+ return new Point(mRenderer.source.getImageWidth(), mRenderer.source.getImageHeight());
+ }
+
+ public void setTileSource(TileSource source, Runnable isReadyCallback) {
+ super.setTileSource(source, isReadyCallback);
+ mCenterX = mRenderer.centerX;
+ mCenterY = mRenderer.centerY;
+ mRotateMatrix.reset();
+ mRotateMatrix.setRotate(mRenderer.rotation);
+ mInverseRotateMatrix.reset();
+ mInverseRotateMatrix.setRotate(-mRenderer.rotation);
+ updateMinScale(getWidth(), getHeight(), source, true);
+ }
+
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ updateMinScale(w, h, mRenderer.source, false);
+ }
+
+ public void setScale(float scale) {
+ synchronized (mLock) {
+ mRenderer.scale = scale;
+ }
+ }
+
+ private void updateMinScale(int w, int h, TileSource source, boolean resetScale) {
+ synchronized (mLock) {
+ if (resetScale) {
+ mRenderer.scale = 1;
+ }
+ if (source != null) {
+ final float[] imageDims = getImageDims();
+ final float imageWidth = imageDims[0];
+ final float imageHeight = imageDims[1];
+ mMinScale = Math.max(w / imageWidth, h / imageHeight);
+ mRenderer.scale =
+ Math.max(mMinScale, resetScale ? Float.MIN_VALUE : mRenderer.scale);
+ }
+ }
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ // Don't need the lock because this will only fire inside of
+ // onTouchEvent
+ mRenderer.scale *= detector.getScaleFactor();
+ mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
+ invalidate();
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ }
+
+ public void moveToLeft() {
+ if (getWidth() == 0 || getHeight() == 0) {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ public void onGlobalLayout() {
+ moveToLeft();
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
+ }
+ final RectF edges = mTempEdges;
+ getEdgesHelper(edges);
+ final float scale = mRenderer.scale;
+ mCenterX += Math.ceil(edges.left / scale);
+ updateCenter();
+ }
+
+ private void updateCenter() {
+ mRenderer.centerX = Math.round(mCenterX);
+ mRenderer.centerY = Math.round(mCenterY);
+ }
+
+ public void setTouchEnabled(boolean enabled) {
+ mTouchEnabled = enabled;
+ }
+
+ public void setTouchCallback(TouchCallback cb) {
+ mTouchCallback = cb;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int action = event.getActionMasked();
+ final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int count = event.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i)
+ continue;
+ sumX += event.getX(i);
+ sumY += event.getY(i);
+ }
+ final int div = pointerUp ? count - 1 : count;
+ float x = sumX / div;
+ float y = sumY / div;
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ mFirstX = x;
+ mFirstY = y;
+ mTouchDownTime = System.currentTimeMillis();
+ if (mTouchCallback != null) {
+ mTouchCallback.onTouchDown();
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ ViewConfiguration config = ViewConfiguration.get(getContext());
+
+ float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y) * (mFirstY - y);
+ float slop = config.getScaledTouchSlop() * config.getScaledTouchSlop();
+ long now = System.currentTimeMillis();
+ if (mTouchCallback != null) {
+ // only do this if it's a small movement
+ if (squaredDist < slop &&
+ now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
+ mTouchCallback.onTap();
+ }
+ mTouchCallback.onTouchUp();
+ }
+ }
+
+ if (!mTouchEnabled) {
+ return true;
+ }
+
+ synchronized (mLock) {
+ mScaleGestureDetector.onTouchEvent(event);
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ float[] point = mTempPoint;
+ point[0] = (mLastX - x) / mRenderer.scale;
+ point[1] = (mLastY - y) / mRenderer.scale;
+ mInverseRotateMatrix.mapPoints(point);
+ mCenterX += point[0];
+ mCenterY += point[1];
+ updateCenter();
+ invalidate();
+ break;
+ }
+ if (mRenderer.source != null) {
+ // Adjust position so that the wallpaper covers the entire area
+ // of the screen
+ final RectF edges = mTempEdges;
+ getEdgesHelper(edges);
+ final float scale = mRenderer.scale;
+
+ float[] coef = mTempCoef;
+ coef[0] = 1;
+ coef[1] = 1;
+ mRotateMatrix.mapPoints(coef);
+ float[] adjustment = mTempAdjustment;
+ mTempAdjustment[0] = 0;
+ mTempAdjustment[1] = 0;
+ if (edges.left > 0) {
+ adjustment[0] = edges.left / scale;
+ } else if (edges.right < getWidth()) {
+ adjustment[0] = (edges.right - getWidth()) / scale;
+ }
+ if (edges.top > 0) {
+ adjustment[1] = FloatMath.ceil(edges.top / scale);
+ } else if (edges.bottom < getHeight()) {
+ adjustment[1] = (edges.bottom - getHeight()) / scale;
+ }
+ for (int dim = 0; dim <= 1; dim++) {
+ if (coef[dim] > 0) adjustment[dim] = FloatMath.ceil(adjustment[dim]);
+ }
+
+ mInverseRotateMatrix.mapPoints(adjustment);
+ mCenterX += adjustment[0];
+ mCenterY += adjustment[1];
+ updateCenter();
+ }
+ }
+
+ mLastX = x;
+ mLastY = y;
+ return true;
+ }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/DrawableTileSource.java b/WallpaperPicker/src/com/android/launcher3/DrawableTileSource.java
new file mode 100644
index 000000000..c1f2eff0f
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/DrawableTileSource.java
@@ -0,0 +1,102 @@
+/*
+ * 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 com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.photos.views.TiledImageRenderer;
+
+public class DrawableTileSource implements TiledImageRenderer.TileSource {
+ private static final int GL_SIZE_LIMIT = 2048;
+ // This must be no larger than half the size of the GL_SIZE_LIMIT
+ // due to decodePreview being allowed to be up to 2x the size of the target
+ public static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;
+
+ private int mTileSize;
+ private int mPreviewSize;
+ private Drawable mDrawable;
+ private BitmapTexture mPreview;
+
+ public DrawableTileSource(Context context, Drawable d, int previewSize) {
+ mTileSize = TiledImageRenderer.suggestedTileSize(context);
+ mDrawable = d;
+ mPreviewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
+ }
+
+ @Override
+ public int getTileSize() {
+ return mTileSize;
+ }
+
+ @Override
+ public int getImageWidth() {
+ return mDrawable.getIntrinsicWidth();
+ }
+
+ @Override
+ public int getImageHeight() {
+ return mDrawable.getIntrinsicHeight();
+ }
+
+ @Override
+ public int getRotation() {
+ return 0;
+ }
+
+ @Override
+ public BasicTexture getPreview() {
+ if (mPreviewSize == 0) {
+ return null;
+ }
+ if (mPreview == null){
+ float width = getImageWidth();
+ float height = getImageHeight();
+ while (width > MAX_PREVIEW_SIZE || height > MAX_PREVIEW_SIZE) {
+ width /= 2;
+ height /= 2;
+ }
+ Bitmap b = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ mDrawable.setBounds(new Rect(0, 0, (int) width, (int) height));
+ mDrawable.draw(c);
+ c.setBitmap(null);
+ mPreview = new BitmapTexture(b);
+ }
+ return mPreview;
+ }
+
+ @Override
+ public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+ int tileSize = getTileSize();
+ if (bitmap == null) {
+ bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
+ }
+ Canvas c = new Canvas(bitmap);
+ Rect bounds = new Rect(0, 0, getImageWidth(), getImageHeight());
+ bounds.offset(-x, -y);
+ mDrawable.setBounds(bounds);
+ mDrawable.draw(c);
+ c.setBitmap(null);
+ return bitmap;
+ }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java b/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java
new file mode 100644
index 000000000..60b253711
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2010 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.android.launcher3;
+
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.service.wallpaper.WallpaperService;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter {
+ private static final String LOG_TAG = "LiveWallpaperListAdapter";
+
+ private final LayoutInflater mInflater;
+ private final PackageManager mPackageManager;
+
+ private List<LiveWallpaperTile> mWallpapers;
+
+ @SuppressWarnings("unchecked")
+ public LiveWallpaperListAdapter(Context context) {
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mPackageManager = context.getPackageManager();
+
+ List<ResolveInfo> list = mPackageManager.queryIntentServices(
+ new Intent(WallpaperService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA);
+
+ mWallpapers = new ArrayList<LiveWallpaperTile>();
+
+ new LiveWallpaperEnumerator(context).execute(list);
+ }
+
+ public int getCount() {
+ if (mWallpapers == null) {
+ return 0;
+ }
+ return mWallpapers.size();
+ }
+
+ public LiveWallpaperTile getItem(int position) {
+ return mWallpapers.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+
+ if (convertView == null) {
+ view = mInflater.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
+
+ LiveWallpaperTile wallpaperInfo = mWallpapers.get(position);
+ wallpaperInfo.setView(view);
+ ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
+ ImageView icon = (ImageView) view.findViewById(R.id.wallpaper_icon);
+ if (wallpaperInfo.mThumbnail != null) {
+ image.setImageDrawable(wallpaperInfo.mThumbnail);
+ icon.setVisibility(View.GONE);
+ } else {
+ icon.setImageDrawable(wallpaperInfo.mInfo.loadIcon(mPackageManager));
+ icon.setVisibility(View.VISIBLE);
+ }
+
+ TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
+ label.setText(wallpaperInfo.mInfo.loadLabel(mPackageManager));
+
+ return view;
+ }
+
+ public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+ private Drawable mThumbnail;
+ private WallpaperInfo mInfo;
+ public LiveWallpaperTile(Drawable thumbnail, WallpaperInfo info, Intent intent) {
+ mThumbnail = thumbnail;
+ mInfo = info;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
+ preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
+ mInfo.getComponent());
+ a.onLiveWallpaperPickerLaunch();
+ a.startActivityForResultSafely(preview, WallpaperPickerActivity.PICK_LIVE_WALLPAPER);
+ }
+ }
+
+ private class LiveWallpaperEnumerator extends
+ AsyncTask<List<ResolveInfo>, LiveWallpaperTile, Void> {
+ private Context mContext;
+ private int mWallpaperPosition;
+
+ public LiveWallpaperEnumerator(Context context) {
+ super();
+ mContext = context;
+ mWallpaperPosition = 0;
+ }
+
+ @Override
+ protected Void doInBackground(List<ResolveInfo>... params) {
+ final PackageManager packageManager = mContext.getPackageManager();
+
+ List<ResolveInfo> list = params[0];
+
+ Collections.sort(list, new Comparator<ResolveInfo>() {
+ final Collator mCollator;
+
+ {
+ mCollator = Collator.getInstance();
+ }
+
+ public int compare(ResolveInfo info1, ResolveInfo info2) {
+ return mCollator.compare(info1.loadLabel(packageManager),
+ info2.loadLabel(packageManager));
+ }
+ });
+
+ for (ResolveInfo resolveInfo : list) {
+ WallpaperInfo info = null;
+ try {
+ info = new WallpaperInfo(mContext, resolveInfo);
+ } catch (XmlPullParserException e) {
+ Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+ continue;
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+ continue;
+ }
+
+
+ Drawable thumb = info.loadThumbnail(packageManager);
+ Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
+ launchIntent.setClassName(info.getPackageName(), info.getServiceName());
+ LiveWallpaperTile wallpaper = new LiveWallpaperTile(thumb, info, launchIntent);
+ publishProgress(wallpaper);
+ }
+ // Send a null object to show loading is finished
+ publishProgress((LiveWallpaperTile) null);
+
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(LiveWallpaperTile...infos) {
+ for (LiveWallpaperTile info : infos) {
+ if (info == null) {
+ LiveWallpaperListAdapter.this.notifyDataSetChanged();
+ break;
+ }
+ if (info.mThumbnail != null) {
+ info.mThumbnail.setDither(true);
+ }
+ if (mWallpaperPosition < mWallpapers.size()) {
+ mWallpapers.set(mWallpaperPosition, info);
+ } else {
+ mWallpapers.add(info);
+ }
+ mWallpaperPosition++;
+ }
+ }
+ }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
new file mode 100644
index 000000000..58add7022
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
@@ -0,0 +1,241 @@
+/*
+ * 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 com.android.launcher3;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+import com.android.photos.BitmapRegionTileSource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+
+public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
+ private static String TAG = "Launcher3.SavedWallpaperImages";
+ private ImageDb mDb;
+ ArrayList<SavedWallpaperTile> mImages;
+ Context mContext;
+ LayoutInflater mLayoutInflater;
+
+ public static class SavedWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+ private int mDbId;
+ private Drawable mThumb;
+ public SavedWallpaperTile(int dbId, Drawable thumb) {
+ mDbId = dbId;
+ mThumb = thumb;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ String imageFilename = a.getSavedImages().getImageFilename(mDbId);
+ File file = new File(a.getFilesDir(), imageFilename);
+ BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
+ new BitmapRegionTileSource.FilePathBitmapSource(file.getAbsolutePath(), 1024);
+ a.setCropViewTileSource(bitmapSource, false, true, null);
+ }
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ boolean finishActivityWhenDone = true;
+ String imageFilename = a.getSavedImages().getImageFilename(mDbId);
+ a.setWallpaper(imageFilename, finishActivityWhenDone);
+ }
+ @Override
+ public void onDelete(WallpaperPickerActivity a) {
+ a.getSavedImages().deleteImage(mDbId);
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ public SavedWallpaperImages(Activity context) {
+ mDb = new ImageDb(context);
+ mContext = context;
+ mLayoutInflater = context.getLayoutInflater();
+ }
+
+ public void loadThumbnailsAndImageIdList() {
+ mImages = new ArrayList<SavedWallpaperTile>();
+ SQLiteDatabase db = mDb.getReadableDatabase();
+ Cursor result = db.query(ImageDb.TABLE_NAME,
+ new String[] { ImageDb.COLUMN_ID,
+ ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME }, // cols to return
+ null, // select query
+ null, // args to select query
+ null,
+ null,
+ ImageDb.COLUMN_ID + " DESC",
+ null);
+
+ while (result.moveToNext()) {
+ String filename = result.getString(1);
+ File file = new File(mContext.getFilesDir(), filename);
+
+ Bitmap thumb = BitmapFactory.decodeFile(file.getAbsolutePath());
+ if (thumb != null) {
+ mImages.add(new SavedWallpaperTile(result.getInt(0), new BitmapDrawable(thumb)));
+ }
+ }
+ result.close();
+ }
+
+ public int getCount() {
+ return mImages.size();
+ }
+
+ public SavedWallpaperTile getItem(int position) {
+ return mImages.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Drawable thumbDrawable = mImages.get(position).mThumb;
+ if (thumbDrawable == null) {
+ Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
+ }
+ return WallpaperPickerActivity.createImageTileView(
+ mLayoutInflater, position, convertView, parent, thumbDrawable);
+ }
+
+ public String getImageFilename(int id) {
+ Pair<String, String> filenames = getImageFilenames(id);
+ if (filenames != null) {
+ return filenames.second;
+ }
+ return null;
+ }
+
+ private Pair<String, String> getImageFilenames(int id) {
+ SQLiteDatabase db = mDb.getReadableDatabase();
+ Cursor result = db.query(ImageDb.TABLE_NAME,
+ new String[] { ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
+ ImageDb.COLUMN_IMAGE_FILENAME }, // cols to return
+ ImageDb.COLUMN_ID + " = ?", // select query
+ new String[] { Integer.toString(id) }, // args to select query
+ null,
+ null,
+ null,
+ null);
+ if (result.getCount() > 0) {
+ result.moveToFirst();
+ String thumbFilename = result.getString(0);
+ String imageFilename = result.getString(1);
+ result.close();
+ return new Pair<String, String>(thumbFilename, imageFilename);
+ } else {
+ return null;
+ }
+ }
+
+ public void deleteImage(int id) {
+ Pair<String, String> filenames = getImageFilenames(id);
+ File imageFile = new File(mContext.getFilesDir(), filenames.first);
+ imageFile.delete();
+ File thumbFile = new File(mContext.getFilesDir(), filenames.second);
+ thumbFile.delete();
+ SQLiteDatabase db = mDb.getWritableDatabase();
+ db.delete(ImageDb.TABLE_NAME,
+ ImageDb.COLUMN_ID + " = ?", // SELECT query
+ new String[] {
+ Integer.toString(id) // args to SELECT query
+ });
+ }
+
+ public void writeImage(Bitmap thumbnail, byte[] imageBytes) {
+ try {
+ File imageFile = File.createTempFile("wallpaper", "", mContext.getFilesDir());
+ FileOutputStream imageFileStream =
+ mContext.openFileOutput(imageFile.getName(), Context.MODE_PRIVATE);
+ imageFileStream.write(imageBytes);
+ imageFileStream.close();
+
+ File thumbFile = File.createTempFile("wallpaperthumb", "", mContext.getFilesDir());
+ FileOutputStream thumbFileStream =
+ mContext.openFileOutput(thumbFile.getName(), Context.MODE_PRIVATE);
+ thumbnail.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
+ thumbFileStream.close();
+
+ SQLiteDatabase db = mDb.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME, thumbFile.getName());
+ values.put(ImageDb.COLUMN_IMAGE_FILENAME, imageFile.getName());
+ db.insert(ImageDb.TABLE_NAME, null, values);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed writing images to storage " + e);
+ }
+ }
+
+ static class ImageDb extends SQLiteOpenHelper {
+ final static int DB_VERSION = 1;
+ final static String DB_NAME = "saved_wallpaper_images.db";
+ final static String TABLE_NAME = "saved_wallpaper_images";
+ final static String COLUMN_ID = "id";
+ final static String COLUMN_IMAGE_THUMBNAIL_FILENAME = "image_thumbnail";
+ final static String COLUMN_IMAGE_FILENAME = "image";
+
+ Context mContext;
+
+ public ImageDb(Context context) {
+ super(context, new File(context.getCacheDir(), DB_NAME).getPath(), null, DB_VERSION);
+ // Store the context for later use
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase database) {
+ database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
+ COLUMN_ID + " INTEGER NOT NULL, " +
+ COLUMN_IMAGE_THUMBNAIL_FILENAME + " TEXT NOT NULL, " +
+ COLUMN_IMAGE_FILENAME + " TEXT NOT NULL, " +
+ "PRIMARY KEY (" + COLUMN_ID + " ASC) " +
+ ");");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ // Delete all the records; they'll be repopulated as this is a cache
+ db.execSQL("DELETE FROM " + TABLE_NAME);
+ }
+ }
+ }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
new file mode 100644
index 000000000..7a4d48ca9
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2010 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.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements ListAdapter {
+ private static final String LOG_TAG = "LiveWallpaperListAdapter";
+
+ private final LayoutInflater mInflater;
+ private final PackageManager mPackageManager;
+ private final int mIconSize;
+
+ private List<ThirdPartyWallpaperTile> mThirdPartyWallpaperPickers =
+ new ArrayList<ThirdPartyWallpaperTile>();
+
+ public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+ private ResolveInfo mResolveInfo;
+ public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
+ mResolveInfo = resolveInfo;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ final ComponentName itemComponentName = new ComponentName(
+ mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name);
+ Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ launchIntent.setComponent(itemComponentName);
+ a.startActivityForResultSafely(
+ launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+ }
+ }
+
+ public ThirdPartyWallpaperPickerListAdapter(Context context) {
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mPackageManager = context.getPackageManager();
+ mIconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
+ final PackageManager pm = mPackageManager;
+
+ final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ final List<ResolveInfo> apps =
+ pm.queryIntentActivities(pickWallpaperIntent, 0);
+
+ // Get list of image picker intents
+ Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
+ pickImageIntent.setType("image/*");
+ final List<ResolveInfo> imagePickerActivities =
+ pm.queryIntentActivities(pickImageIntent, 0);
+ final ComponentName[] imageActivities = new ComponentName[imagePickerActivities.size()];
+ for (int i = 0; i < imagePickerActivities.size(); i++) {
+ ActivityInfo activityInfo = imagePickerActivities.get(i).activityInfo;
+ imageActivities[i] = new ComponentName(activityInfo.packageName, activityInfo.name);
+ }
+
+ outerLoop:
+ for (ResolveInfo info : apps) {
+ final ComponentName itemComponentName =
+ new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+ final String itemPackageName = itemComponentName.getPackageName();
+ // Exclude anything from our own package, and the old Launcher,
+ // and live wallpaper picker
+ if (itemPackageName.equals(context.getPackageName()) ||
+ itemPackageName.equals("com.android.launcher") ||
+ itemPackageName.equals("com.android.wallpaper.livepicker")) {
+ continue;
+ }
+ // Exclude any package that already responds to the image picker intent
+ for (ResolveInfo imagePickerActivityInfo : imagePickerActivities) {
+ if (itemPackageName.equals(
+ imagePickerActivityInfo.activityInfo.packageName)) {
+ continue outerLoop;
+ }
+ }
+ mThirdPartyWallpaperPickers.add(new ThirdPartyWallpaperTile(info));
+ }
+ }
+
+ public int getCount() {
+ return mThirdPartyWallpaperPickers.size();
+ }
+
+ public ThirdPartyWallpaperTile getItem(int position) {
+ return mThirdPartyWallpaperPickers.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+
+ if (convertView == null) {
+ view = mInflater.inflate(R.layout.wallpaper_picker_third_party_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
+
+ ResolveInfo info = mThirdPartyWallpaperPickers.get(position).mResolveInfo;
+ TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
+ label.setText(info.loadLabel(mPackageManager));
+ Drawable icon = info.loadIcon(mPackageManager);
+ icon.setBounds(new Rect(0, 0, mIconSize, mIconSize));
+ label.setCompoundDrawables(null, icon, null, null);
+ return view;
+ }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
new file mode 100644
index 000000000..b3ef07309
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -0,0 +1,836 @@
+/*
+ * 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 com.android.launcher3;
+
+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.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WallpaperCropActivity extends Activity {
+ private static final String LOGTAG = "Launcher3.CropActivity";
+
+ protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
+ protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
+ private static final int DEFAULT_COMPRESS_QUALITY = 90;
+ /**
+ * 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;
+ private static final float WALLPAPER_SCREENS_SPAN = 2f;
+
+ protected static Point sDefaultWallpaperSize;
+
+ protected CropView mCropView;
+ protected Uri mUri;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ init();
+ if (!enableRotation()) {
+ setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
+ }
+ }
+
+ protected void init() {
+ setContentView(R.layout.wallpaper_cropper);
+
+ mCropView = (CropView) findViewById(R.id.cropView);
+
+ 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, finishActivityWhenDone);
+ }
+ });
+
+ // Load image in background
+ final BitmapRegionTileSource.UriBitmapSource bitmapSource =
+ new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024);
+ Runnable onLoad = new Runnable() {
+ public void run() {
+ if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
+ Toast.makeText(WallpaperCropActivity.this,
+ getString(R.string.wallpaper_load_fail),
+ Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+ };
+ setCropViewTileSource(bitmapSource, true, false, onLoad);
+ }
+
+ public void setCropViewTileSource(
+ final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
+ final boolean moveToLeft, final Runnable postExecute) {
+ final Context context = WallpaperCropActivity.this;
+ final View progressView = findViewById(R.id.loading);
+ final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
+ protected Void doInBackground(Void...args) {
+ if (!isCancelled()) {
+ bitmapSource.loadInBackground();
+ }
+ return null;
+ }
+ protected void onPostExecute(Void arg) {
+ if (!isCancelled()) {
+ progressView.setVisibility(View.INVISIBLE);
+ if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+ mCropView.setTileSource(
+ new BitmapRegionTileSource(context, bitmapSource), null);
+ mCropView.setTouchEnabled(touchEnabled);
+ if (moveToLeft) {
+ mCropView.moveToLeft();
+ }
+ }
+ }
+ if (postExecute != null) {
+ postExecute.run();
+ }
+ }
+ };
+ // 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)
+ progressView.postDelayed(new Runnable() {
+ public void run() {
+ if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) {
+ progressView.setVisibility(View.VISIBLE);
+ }
+ }
+ }, 1000);
+ loadBitmapTask.execute();
+ }
+
+ public boolean enableRotation() {
+ return getResources().getBoolean(R.bool.allow_rotation);
+ }
+
+ public static String getSharedPreferencesKey() {
+ return WallpaperCropActivity.class.getName();
+ }
+
+ // As a ratio of screen height, the total distance we want the parallax effect to span
+ // horizontally
+ private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
+ float aspectRatio = width / (float) height;
+
+ // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
+ // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
+ // We will use these two data points to extrapolate how much the wallpaper parallax effect
+ // to span (ie travel) at any aspect ratio:
+
+ final float ASPECT_RATIO_LANDSCAPE = 16/10f;
+ final float ASPECT_RATIO_PORTRAIT = 10/16f;
+ final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
+ final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
+
+ // To find out the desired width at different aspect ratios, we use the following two
+ // formulas, where the coefficient on x is the aspect ratio (width/height):
+ // (16/10)x + y = 1.5
+ // (10/16)x + y = 1.2
+ // We solve for x and y and end up with a final formula:
+ final float x =
+ (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
+ (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
+ final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
+ return x * aspectRatio + y;
+ }
+
+ static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
+ if (sDefaultWallpaperSize == null) {
+ Point minDims = new Point();
+ Point maxDims = new Point();
+ windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
+
+ int maxDim = Math.max(maxDims.x, maxDims.y);
+ int minDim = Math.max(minDims.x, minDims.y);
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ Point realSize = new Point();
+ windowManager.getDefaultDisplay().getRealSize(realSize);
+ maxDim = Math.max(realSize.x, realSize.y);
+ minDim = Math.min(realSize.x, realSize.y);
+ }
+
+ // We need to ensure that there is enough extra space in the wallpaper
+ // for the intended parallax effects
+ final int defaultWidth, defaultHeight;
+ if (isScreenLarge(res)) {
+ defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
+ defaultHeight = maxDim;
+ } else {
+ defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
+ defaultHeight = maxDim;
+ }
+ sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight);
+ }
+ return sDefaultWallpaperSize;
+ }
+
+ public static int getRotationFromExif(String path) {
+ return getRotationFromExifHelper(path, null, 0, null, null);
+ }
+
+ public static int getRotationFromExif(Context context, Uri uri) {
+ return getRotationFromExifHelper(null, null, 0, context, uri);
+ }
+
+ public static int getRotationFromExif(Resources res, int resId) {
+ return getRotationFromExifHelper(null, res, resId, null, null);
+ }
+
+ private static int getRotationFromExifHelper(
+ String path, Resources res, int resId, Context context, Uri uri) {
+ ExifInterface ei = new ExifInterface();
+ InputStream is = null;
+ BufferedInputStream bis = null;
+ try {
+ if (path != null) {
+ ei.readExif(path);
+ } else if (uri != null) {
+ is = context.getContentResolver().openInputStream(uri);
+ bis = new BufferedInputStream(is);
+ ei.readExif(bis);
+ } else {
+ is = res.openRawResource(resId);
+ bis = new BufferedInputStream(is);
+ ei.readExif(bis);
+ }
+ Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+ if (ori != null) {
+ return ExifInterface.getRotationForOrientationValue(ori.shortValue());
+ }
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Getting exif data failed", e);
+ } finally {
+ Utils.closeSilently(bis);
+ Utils.closeSilently(is);
+ }
+ return 0;
+ }
+
+ protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
+ int rotation = getRotationFromExif(filePath);
+ BitmapCropTask cropTask = new BitmapCropTask(
+ this, filePath, null, rotation, 0, 0, true, 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, final boolean finishActivityWhenDone) {
+ // crop this image and scale it down to the default wallpaper size for
+ // this device
+ int rotation = getRotationFromExif(res, resId);
+ Point inSize = mCropView.getSourceDimensions();
+ Point outSize = getDefaultWallpaperSize(getResources(),
+ getWindowManager());
+ RectF crop = 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(this, res, resId,
+ crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
+ cropTask.execute();
+ }
+
+ private static boolean isScreenLarge(Resources res) {
+ Configuration config = res.getConfiguration();
+ return config.smallestScreenWidthDp >= 720;
+ }
+
+ protected void cropImageAndSetWallpaper(Uri uri,
+ OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
+ // 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 = getDefaultWallpaperSize(getResources(),
+ getWindowManager());
+ // Get the crop
+ RectF cropRect = mCropView.getCrop();
+ int cropRotation = mCropView.getImageRotation();
+ float cropScale = mCropView.getWidth() / (float) cropRect.width();
+
+ Point inSize = mCropView.getSourceDimensions();
+ 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]);
+
+ // 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 = 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 (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(this, uri,
+ cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
+ if (onBitmapCroppedHandler != null) {
+ cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
+ }
+ cropTask.execute();
+ }
+
+ public interface OnBitmapCroppedHandler {
+ public void onBitmapCropped(byte[] imageBytes);
+ }
+
+ protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
+ Uri mInUri = null;
+ Context mContext;
+ String mInFilePath;
+ byte[] mInImageBytes;
+ int mInResId = 0;
+ RectF mCropBounds = null;
+ int mOutWidth, mOutHeight;
+ int mRotation;
+ String mOutputFormat = "jpg"; // for now
+ boolean mSetWallpaper;
+ boolean mSaveCroppedBitmap;
+ Bitmap mCroppedBitmap;
+ Runnable mOnEndRunnable;
+ Resources mResources;
+ OnBitmapCroppedHandler mOnBitmapCroppedHandler;
+ boolean mNoCrop;
+
+ public BitmapCropTask(Context c, String filePath,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mContext = c;
+ mInFilePath = filePath;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ public BitmapCropTask(byte[] imageBytes,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mInImageBytes = imageBytes;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ public BitmapCropTask(Context c, Uri inUri,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mContext = c;
+ mInUri = inUri;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ public BitmapCropTask(Context c, Resources res, int inResId,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mContext = c;
+ mInResId = inResId;
+ mResources = res;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mCropBounds = cropBounds;
+ mRotation = rotation;
+ mOutWidth = outWidth;
+ mOutHeight = outHeight;
+ mSetWallpaper = setWallpaper;
+ mSaveCroppedBitmap = saveCroppedBitmap;
+ mOnEndRunnable = onEndRunnable;
+ }
+
+ public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
+ mOnBitmapCroppedHandler = handler;
+ }
+
+ public void setNoCrop(boolean value) {
+ mNoCrop = value;
+ }
+
+ public void setOnEndRunnable(Runnable onEndRunnable) {
+ mOnEndRunnable = onEndRunnable;
+ }
+
+ // Helper to setup input stream
+ private InputStream regenerateInputStream() {
+ if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
+ Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
+ "image byte array given");
+ } else {
+ try {
+ if (mInUri != null) {
+ return new BufferedInputStream(
+ mContext.getContentResolver().openInputStream(mInUri));
+ } else if (mInFilePath != null) {
+ return mContext.openFileInput(mInFilePath);
+ } else if (mInImageBytes != null) {
+ return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
+ } else {
+ return new BufferedInputStream(mResources.openRawResource(mInResId));
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
+ }
+ }
+ return null;
+ }
+
+ public Point getImageBounds() {
+ InputStream is = regenerateInputStream();
+ if (is != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, options);
+ Utils.closeSilently(is);
+ if (options.outWidth != 0 && options.outHeight != 0) {
+ return new Point(options.outWidth, options.outHeight);
+ }
+ }
+ return null;
+ }
+
+ public void setCropBounds(RectF cropBounds) {
+ mCropBounds = cropBounds;
+ }
+
+ public Bitmap getCroppedBitmap() {
+ return mCroppedBitmap;
+ }
+ public boolean cropBitmap() {
+ boolean failure = false;
+
+
+ WallpaperManager wallpaperManager = null;
+ if (mSetWallpaper) {
+ wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
+ }
+
+
+ if (mSetWallpaper && mNoCrop) {
+ try {
+ InputStream is = regenerateInputStream();
+ if (is != null) {
+ wallpaperManager.setStream(is);
+ Utils.closeSilently(is);
+ }
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+ failure = true;
+ }
+ return !failure;
+ } else {
+ // Find crop bounds (scaled to original image size)
+ Rect roundedTrueCrop = new Rect();
+ Matrix rotateMatrix = new Matrix();
+ Matrix inverseRotateMatrix = new Matrix();
+
+ Point bounds = getImageBounds();
+ if (mRotation > 0) {
+ rotateMatrix.setRotate(mRotation);
+ inverseRotateMatrix.setRotate(-mRotation);
+
+ mCropBounds.roundOut(roundedTrueCrop);
+ mCropBounds = new RectF(roundedTrueCrop);
+
+ if (bounds == null) {
+ Log.w(LOGTAG, "cannot get bounds for image");
+ failure = true;
+ return false;
+ }
+
+ float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+ rotateMatrix.mapPoints(rotatedBounds);
+ rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+ rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+ mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
+ inverseRotateMatrix.mapRect(mCropBounds);
+ mCropBounds.offset(bounds.x/2, bounds.y/2);
+
+ }
+
+ mCropBounds.roundOut(roundedTrueCrop);
+
+ if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
+ Log.w(LOGTAG, "crop has bad values for full size image");
+ failure = true;
+ return false;
+ }
+
+ // See how much we're reducing the size of the image
+ int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
+ roundedTrueCrop.height() / mOutHeight));
+ // Attempt to open a region decoder
+ BitmapRegionDecoder decoder = null;
+ InputStream is = null;
+ try {
+ is = regenerateInputStream();
+ if (is == null) {
+ Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
+ failure = true;
+ return false;
+ }
+ decoder = BitmapRegionDecoder.newInstance(is, false);
+ Utils.closeSilently(is);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+ } finally {
+ Utils.closeSilently(is);
+ is = null;
+ }
+
+ Bitmap crop = null;
+ if (decoder != null) {
+ // Do region decoding to get crop bitmap
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ crop = decoder.decodeRegion(roundedTrueCrop, options);
+ decoder.recycle();
+ }
+
+ if (crop == null) {
+ // BitmapRegionDecoder has failed, try to crop in-memory
+ is = regenerateInputStream();
+ Bitmap fullSize = null;
+ if (is != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ fullSize = BitmapFactory.decodeStream(is, null, options);
+ Utils.closeSilently(is);
+ }
+ if (fullSize != null) {
+ // Find out the true sample size that was used by the decoder
+ scaleDownSampleSize = bounds.x / fullSize.getWidth();
+ mCropBounds.left /= scaleDownSampleSize;
+ mCropBounds.top /= scaleDownSampleSize;
+ mCropBounds.bottom /= scaleDownSampleSize;
+ mCropBounds.right /= scaleDownSampleSize;
+ mCropBounds.roundOut(roundedTrueCrop);
+
+ // Adjust values to account for issues related to rounding
+ if (roundedTrueCrop.width() > fullSize.getWidth()) {
+ // Adjust the width
+ roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
+ }
+ if (roundedTrueCrop.right > fullSize.getWidth()) {
+ // Adjust the left value
+ int adjustment = roundedTrueCrop.left -
+ Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width());
+ roundedTrueCrop.left -= adjustment;
+ roundedTrueCrop.right -= adjustment;
+ }
+ if (roundedTrueCrop.height() > fullSize.getHeight()) {
+ // Adjust the height
+ roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
+ }
+ if (roundedTrueCrop.bottom > fullSize.getHeight()) {
+ // Adjust the top value
+ int adjustment = roundedTrueCrop.top -
+ Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height());
+ roundedTrueCrop.top -= adjustment;
+ roundedTrueCrop.bottom -= adjustment;
+ }
+
+ crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
+ roundedTrueCrop.top, roundedTrueCrop.width(),
+ roundedTrueCrop.height());
+ }
+ }
+
+ if (crop == null) {
+ Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
+ failure = true;
+ return false;
+ }
+ if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
+ float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
+ rotateMatrix.mapPoints(dimsAfter);
+ dimsAfter[0] = Math.abs(dimsAfter[0]);
+ dimsAfter[1] = Math.abs(dimsAfter[1]);
+
+ if (!(mOutWidth > 0 && mOutHeight > 0)) {
+ mOutWidth = Math.round(dimsAfter[0]);
+ mOutHeight = Math.round(dimsAfter[1]);
+ }
+
+ RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
+ RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
+
+ Matrix m = new Matrix();
+ if (mRotation == 0) {
+ m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+ } else {
+ Matrix m1 = new Matrix();
+ m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
+ Matrix m2 = new Matrix();
+ m2.setRotate(mRotation);
+ Matrix m3 = new Matrix();
+ m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
+ Matrix m4 = new Matrix();
+ m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+
+ Matrix c1 = new Matrix();
+ c1.setConcat(m2, m1);
+ Matrix c2 = new Matrix();
+ c2.setConcat(m4, m3);
+ m.setConcat(c2, c1);
+ }
+
+ Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
+ (int) returnRect.height(), Bitmap.Config.ARGB_8888);
+ if (tmp != null) {
+ Canvas c = new Canvas(tmp);
+ Paint p = new Paint();
+ p.setFilterBitmap(true);
+ c.drawBitmap(crop, m, p);
+ crop = tmp;
+ }
+ }
+
+ if (mSaveCroppedBitmap) {
+ mCroppedBitmap = crop;
+ }
+
+ // Get output compression format
+ CompressFormat cf =
+ convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
+
+ // Compress to byte array
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+ if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
+ // If we need to set to the wallpaper, set it
+ if (mSetWallpaper && wallpaperManager != null) {
+ try {
+ byte[] outByteArray = tmpOut.toByteArray();
+ wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
+ if (mOnBitmapCroppedHandler != null) {
+ mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
+ }
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+ failure = true;
+ }
+ }
+ } else {
+ Log.w(LOGTAG, "cannot compress bitmap");
+ failure = true;
+ }
+ }
+ return !failure; // True if any of the operations failed
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ return cropBitmap();
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (mOnEndRunnable != null) {
+ mOnEndRunnable.run();
+ }
+ }
+ }
+
+ protected void updateWallpaperDimensions(int width, int height) {
+ String spKey = getSharedPreferencesKey();
+ SharedPreferences sp = 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();
+
+ suggestWallpaperDimension(getResources(),
+ sp, getWindowManager(), WallpaperManager.getInstance(this));
+ }
+
+ static public void suggestWallpaperDimension(Resources res,
+ final SharedPreferences sharedPrefs,
+ WindowManager windowManager,
+ final WallpaperManager wallpaperManager) {
+ final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
+ // If we have saved a wallpaper width/height, use that instead
+ int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
+ int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
+ if (savedWidth != wallpaperManager.getDesiredMinimumWidth() ||
+ savedHeight != wallpaperManager.getDesiredMinimumHeight()) {
+ wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
+ }
+ }
+
+ protected static RectF getMaxCropRect(
+ int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
+ RectF cropRect = new RectF();
+ // Get a crop rect that will fit this
+ if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
+ cropRect.top = 0;
+ cropRect.bottom = inHeight;
+ cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
+ cropRect.right = inWidth - cropRect.left;
+ if (leftAligned) {
+ cropRect.right -= cropRect.left;
+ cropRect.left = 0;
+ }
+ } else {
+ cropRect.left = 0;
+ cropRect.right = inWidth;
+ cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
+ cropRect.bottom = inHeight - cropRect.top;
+ }
+ return cropRect;
+ }
+
+ protected static CompressFormat convertExtensionToCompressFormat(String extension) {
+ return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
+ }
+
+ protected static String getFileExtension(String requestFormat) {
+ String outputFormat = (requestFormat == null)
+ ? "jpg"
+ : requestFormat;
+ outputFormat = outputFormat.toLowerCase();
+ return (outputFormat.equals("png") || outputFormat.equals("gif"))
+ ? "png" // We don't support gif compression.
+ : "jpg";
+ }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
new file mode 100644
index 000000000..465769066
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -0,0 +1,1044 @@
+/*
+ * 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 com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.LayoutTransition;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LevelListDrawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.util.Pair;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.Toast;
+
+import com.android.photos.BitmapRegionTileSource;
+import com.android.launcher3.settings.SettingsProvider;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class WallpaperPickerActivity extends WallpaperCropActivity {
+ static final String TAG = "Launcher.WallpaperPickerActivity";
+
+ public static final int IMAGE_PICK = 5;
+ public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
+ public static final int PICK_LIVE_WALLPAPER = 7;
+ private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
+ private static final String OLD_DEFAULT_WALLPAPER_THUMBNAIL_FILENAME = "default_thumb.jpg";
+ private static final String DEFAULT_WALLPAPER_THUMBNAIL_FILENAME = "default_thumb2.jpg";
+
+ private static final int MENU_WALLPAPER_SCROLL = 0;
+
+ private View mSelectedTile;
+
+ private boolean mIgnoreNextTap;
+ private OnClickListener mThumbnailOnClickListener;
+
+ private LinearLayout mWallpapersView;
+ private View mWallpaperStrip;
+
+ private ActionMode.Callback mActionModeCallback;
+ private ActionMode mActionMode;
+
+ private View.OnLongClickListener mLongClickListener;
+
+ ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
+ private SavedWallpaperImages mSavedImages;
+ private WallpaperInfo mLiveWallpaperInfoOnPickerLaunch;
+
+ public static abstract class WallpaperTileInfo {
+ protected View mView;
+ public void setView(View v) {
+ mView = v;
+ }
+ public void onClick(WallpaperPickerActivity a) {}
+ public void onSave(WallpaperPickerActivity a) {}
+ public void onDelete(WallpaperPickerActivity a) {}
+ public boolean isSelectable() { return false; }
+ public boolean isNamelessWallpaper() { return false; }
+ public void onIndexUpdated(CharSequence label) {
+ if (isNamelessWallpaper()) {
+ mView.setContentDescription(label);
+ }
+ }
+ }
+
+ public static class PickImageInfo extends WallpaperTileInfo {
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ a.startActivityForResultSafely(intent, IMAGE_PICK);
+ }
+ }
+
+ public static class UriWallpaperInfo extends WallpaperTileInfo {
+ private Uri mUri;
+ private boolean mFirstClick = true;
+ private BitmapRegionTileSource.UriBitmapSource mBitmapSource;
+ public UriWallpaperInfo(Uri uri) {
+ mUri = uri;
+ }
+ @Override
+ public void onClick(final WallpaperPickerActivity a) {
+ final Runnable onLoad;
+ if (!mFirstClick) {
+ onLoad = null;
+ } else {
+ mFirstClick = false;
+ onLoad = new Runnable() {
+ public void run() {
+ if (mBitmapSource != null &&
+ mBitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+ mView.setVisibility(View.VISIBLE);
+ a.selectTile(mView);
+ } else {
+ ViewGroup parent = (ViewGroup) mView.getParent();
+ if (parent != null) {
+ parent.removeView(mView);
+ Toast.makeText(a,
+ a.getString(R.string.image_load_fail),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+ };
+ }
+ mBitmapSource = new BitmapRegionTileSource.UriBitmapSource(
+ a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
+ a.setCropViewTileSource(mBitmapSource, true, false, onLoad);
+ }
+ @Override
+ public void onSave(final WallpaperPickerActivity a) {
+ boolean finishActivityWhenDone = true;
+ OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
+ public void onBitmapCropped(byte[] imageBytes) {
+ Point thumbSize = getDefaultThumbnailSize(a.getResources());
+ // rotation is set to 0 since imageBytes has already been correctly rotated
+ Bitmap thumb = createThumbnail(
+ thumbSize, null, null, imageBytes, null, 0, 0, true);
+ a.getSavedImages().writeImage(thumb, imageBytes);
+ }
+ };
+ a.cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone);
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ public static class ResourceWallpaperInfo extends WallpaperTileInfo {
+ private Resources mResources;
+ private int mResId;
+ private Drawable mThumb;
+
+ public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
+ mResources = res;
+ mResId = resId;
+ mThumb = thumb;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
+ new BitmapRegionTileSource.ResourceBitmapSource(
+ mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
+ bitmapSource.loadInBackground();
+ BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource);
+ CropView v = a.getCropView();
+ v.setTileSource(source, null);
+ Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
+ a.getResources(), a.getWindowManager());
+ RectF crop = WallpaperCropActivity.getMaxCropRect(
+ source.getImageWidth(), source.getImageHeight(),
+ wallpaperSize.x, wallpaperSize.y, false);
+ v.setScale(wallpaperSize.x / crop.width());
+ v.setTouchEnabled(false);
+ }
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ boolean finishActivityWhenDone = true;
+ a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ public static class DefaultWallpaperInfo extends WallpaperTileInfo {
+ public Drawable mThumb;
+ public DefaultWallpaperInfo(Drawable thumb) {
+ mThumb = thumb;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ CropView c = a.getCropView();
+
+ Drawable defaultWallpaper = WallpaperManager.getInstance(a).getBuiltInDrawable(
+ c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
+
+ c.setTileSource(
+ new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE), null);
+ c.setScale(1f);
+ c.setTouchEnabled(false);
+ }
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ try {
+ WallpaperManager.getInstance(a).clear();
+ } catch (IOException e) {
+ Log.w("Setting wallpaper to default threw exception", e);
+ }
+ a.finish();
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ public void setWallpaperStripYOffset(float offset) {
+ mWallpaperStrip.setPadding(0, 0, 0, (int) offset);
+ }
+
+ // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
+ protected void init() {
+ setContentView(R.layout.wallpaper_picker);
+
+ mCropView = (CropView) findViewById(R.id.cropView);
+ mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+ mCropView.setTouchCallback(new CropView.TouchCallback() {
+ ViewPropertyAnimator mAnim;
+ @Override
+ public void onTouchDown() {
+ if (mAnim != null) {
+ mAnim.cancel();
+ }
+ if (mWallpaperStrip.getAlpha() == 1f) {
+ mIgnoreNextTap = true;
+ }
+ mAnim = mWallpaperStrip.animate();
+ mAnim.alpha(0f)
+ .setDuration(150)
+ .withEndAction(new Runnable() {
+ public void run() {
+ mWallpaperStrip.setVisibility(View.INVISIBLE);
+ }
+ });
+ mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
+ mAnim.start();
+ }
+ @Override
+ public void onTouchUp() {
+ mIgnoreNextTap = false;
+ }
+ @Override
+ public void onTap() {
+ boolean ignoreTap = mIgnoreNextTap;
+ mIgnoreNextTap = false;
+ if (!ignoreTap) {
+ if (mAnim != null) {
+ mAnim.cancel();
+ }
+ mWallpaperStrip.setVisibility(View.VISIBLE);
+ mAnim = mWallpaperStrip.animate();
+ mAnim.alpha(1f)
+ .setDuration(150)
+ .setInterpolator(new DecelerateInterpolator(0.75f));
+ mAnim.start();
+ }
+ }
+ });
+
+ mThumbnailOnClickListener = new OnClickListener() {
+ public void onClick(View v) {
+ if (mActionMode != null) {
+ // When CAB is up, clicking toggles the item instead
+ if (v.isLongClickable()) {
+ mLongClickListener.onLongClick(v);
+ }
+ return;
+ }
+ WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
+ if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
+ selectTile(v);
+ }
+ info.onClick(WallpaperPickerActivity.this);
+ }
+ };
+ mLongClickListener = new View.OnLongClickListener() {
+ // Called when the user long-clicks on someView
+ public boolean onLongClick(View view) {
+ CheckableFrameLayout c = (CheckableFrameLayout) view;
+ c.toggle();
+
+ if (mActionMode != null) {
+ mActionMode.invalidate();
+ } else {
+ // Start the CAB using the ActionMode.Callback defined below
+ mActionMode = startActionMode(mActionModeCallback);
+ int childCount = mWallpapersView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mWallpapersView.getChildAt(i).setSelected(false);
+ }
+ }
+ return true;
+ }
+ };
+
+ // Populate the built-in wallpapers
+ ArrayList<ResourceWallpaperInfo> wallpapers = findBundledWallpapers();
+ mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
+ BuiltInWallpapersAdapter ia = new BuiltInWallpapersAdapter(this, wallpapers);
+ populateWallpapersFromAdapter(mWallpapersView, ia, false);
+
+ // Populate the saved wallpapers
+ mSavedImages = new SavedWallpaperImages(this);
+ mSavedImages.loadThumbnailsAndImageIdList();
+ populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true);
+
+ // Populate the live wallpapers
+ final LinearLayout liveWallpapersView =
+ (LinearLayout) findViewById(R.id.live_wallpaper_list);
+ final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this);
+ a.registerDataSetObserver(new DataSetObserver() {
+ public void onChanged() {
+ liveWallpapersView.removeAllViews();
+ populateWallpapersFromAdapter(liveWallpapersView, a, false);
+ initializeScrollForRtl();
+ updateTileIndices();
+ }
+ });
+
+ // Populate the third-party wallpaper pickers
+ final LinearLayout thirdPartyWallpapersView =
+ (LinearLayout) findViewById(R.id.third_party_wallpaper_list);
+ final ThirdPartyWallpaperPickerListAdapter ta =
+ new ThirdPartyWallpaperPickerListAdapter(this);
+ populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false);
+
+ // Add a tile for the Gallery
+ LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
+ FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().
+ inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);
+ setWallpaperItemPaddingToZero(pickImageTile);
+ masterWallpaperList.addView(pickImageTile, 0);
+
+ // Make its background the last photo taken on external storage
+ Bitmap lastPhoto = getThumbnailOfLastPhoto();
+ if (lastPhoto != null) {
+ ImageView galleryThumbnailBg =
+ (ImageView) pickImageTile.findViewById(R.id.wallpaper_image);
+ galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto());
+ int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);
+ galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
+
+ }
+
+ PickImageInfo pickImageInfo = new PickImageInfo();
+ pickImageTile.setTag(pickImageInfo);
+ pickImageInfo.setView(pickImageTile);
+ pickImageTile.setOnClickListener(mThumbnailOnClickListener);
+
+ // Add a tile for the default wallpaper
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ DefaultWallpaperInfo defaultWallpaperInfo = getDefaultWallpaper();
+ FrameLayout defaultWallpaperTile = (FrameLayout) createImageTileView(
+ getLayoutInflater(), 0, null, mWallpapersView, defaultWallpaperInfo.mThumb);
+ setWallpaperItemPaddingToZero(defaultWallpaperTile);
+ defaultWallpaperTile.setTag(defaultWallpaperInfo);
+ mWallpapersView.addView(defaultWallpaperTile, 0);
+ defaultWallpaperTile.setOnClickListener(mThumbnailOnClickListener);
+ defaultWallpaperInfo.setView(defaultWallpaperTile);
+ }
+
+ // Select the first item; wait for a layout pass so that we initialize the dimensions of
+ // cropView or the defaultWallpaperView first
+ mCropView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if ((right - left) > 0 && (bottom - top) > 0) {
+ if (mWallpapersView.getChildCount() > 0) {
+ mThumbnailOnClickListener.onClick(mWallpapersView.getChildAt(0));
+ }
+ v.removeOnLayoutChangeListener(this);
+ }
+ }
+ });
+
+ updateTileIndices();
+
+ // Update the scroll for RTL
+ initializeScrollForRtl();
+
+ // Create smooth layout transitions for when items are deleted
+ final LayoutTransition transitioner = new LayoutTransition();
+ transitioner.setDuration(200);
+ transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
+ transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
+ mWallpapersView.setLayoutTransition(transitioner);
+
+ // 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) {
+ if (mSelectedTile != null) {
+ WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
+ info.onSave(WallpaperPickerActivity.this);
+ }
+ }
+ });
+
+ // CAB for deleting items
+ mActionModeCallback = new ActionMode.Callback() {
+ // Called when the action mode is created; startActionMode() was called
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ // Inflate a menu resource providing context menu items
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.cab_delete_wallpapers, menu);
+ return true;
+ }
+
+ private int numCheckedItems() {
+ int childCount = mWallpapersView.getChildCount();
+ int numCheckedItems = 0;
+ for (int i = 0; i < childCount; i++) {
+ CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+ if (c.isChecked()) {
+ numCheckedItems++;
+ }
+ }
+ return numCheckedItems;
+ }
+
+ // Called each time the action mode is shown. Always called after onCreateActionMode,
+ // but may be called multiple times if the mode is invalidated.
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ int numCheckedItems = numCheckedItems();
+ if (numCheckedItems == 0) {
+ mode.finish();
+ return true;
+ } else {
+ mode.setTitle(getResources().getQuantityString(
+ R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems));
+ return true;
+ }
+ }
+
+ // Called when the user selects a contextual menu item
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == R.id.menu_delete) {
+ int childCount = mWallpapersView.getChildCount();
+ ArrayList<View> viewsToRemove = new ArrayList<View>();
+ for (int i = 0; i < childCount; i++) {
+ CheckableFrameLayout c =
+ (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+ if (c.isChecked()) {
+ WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
+ info.onDelete(WallpaperPickerActivity.this);
+ viewsToRemove.add(c);
+ }
+ }
+ for (View v : viewsToRemove) {
+ mWallpapersView.removeView(v);
+ }
+ updateTileIndices();
+ mode.finish(); // Action picked, so close the CAB
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Called when the user exits the action mode
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ int childCount = mWallpapersView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+ c.setChecked(false);
+ }
+ mSelectedTile.setSelected(true);
+ mActionMode = null;
+ }
+ };
+ }
+
+ private void selectTile(View v) {
+ if (mSelectedTile != null) {
+ mSelectedTile.setSelected(false);
+ mSelectedTile = null;
+ }
+ mSelectedTile = v;
+ v.setSelected(true);
+ // TODO: Remove this once the accessibility framework and
+ // services have better support for selection state.
+ v.announceForAccessibility(
+ getString(R.string.announce_selection, v.getContentDescription()));
+ }
+
+ private void initializeScrollForRtl() {
+ final HorizontalScrollView scroll =
+ (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
+
+ if (scroll.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ final ViewTreeObserver observer = scroll.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ public void onGlobalLayout() {
+ LinearLayout masterWallpaperList =
+ (LinearLayout) findViewById(R.id.master_wallpaper_list);
+ scroll.scrollTo(masterWallpaperList.getWidth(), 0);
+ scroll.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
+ }
+ }
+
+ protected Bitmap getThumbnailOfLastPhoto() {
+ Cursor cursor = MediaStore.Images.Media.query(getContentResolver(),
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new String[] { MediaStore.Images.ImageColumns._ID,
+ MediaStore.Images.ImageColumns.DATE_TAKEN},
+ null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1");
+ Bitmap thumb = null;
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ int id = cursor.getInt(0);
+ thumb = MediaStore.Images.Thumbnails.getThumbnail(getContentResolver(),
+ id, MediaStore.Images.Thumbnails.MINI_KIND, null);
+ }
+ cursor.close();
+ }
+ return thumb;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_WALLPAPER_SCROLL, 0,
+ R.string.wallpaper_scroll).setCheckable(true);
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem wallpaperScroll = menu.findItem(MENU_WALLPAPER_SCROLL);
+
+ wallpaperScroll.setChecked(SettingsProvider.getBoolean(this,
+ SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL,
+ R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default));
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle presses on the action bar items
+ switch (item.getItemId()) {
+ case MENU_WALLPAPER_SCROLL:
+ SettingsProvider.get(this).edit()
+ .putBoolean(SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL, !item.isChecked())
+ .commit();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ protected void onStop() {
+ super.onStop();
+ mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+ if (mWallpaperStrip.getAlpha() < 1f) {
+ mWallpaperStrip.setAlpha(1f);
+ mWallpaperStrip.setVisibility(View.VISIBLE);
+ }
+ mCropView.destroy();
+ }
+
+ protected void onSaveInstanceState(Bundle outState) {
+ outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
+ }
+
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES);
+ for (Uri uri : uris) {
+ addTemporaryWallpaperTile(uri);
+ }
+ }
+
+ private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
+ boolean addLongPressHandler) {
+ for (int i = 0; i < adapter.getCount(); i++) {
+ FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
+ parent.addView(thumbnail, i);
+ WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i);
+ thumbnail.setTag(info);
+ info.setView(thumbnail);
+ if (addLongPressHandler) {
+ addLongPressHandler(thumbnail);
+ }
+ thumbnail.setOnClickListener(mThumbnailOnClickListener);
+ }
+ }
+
+ private void updateTileIndices() {
+ LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
+ final int childCount = masterWallpaperList.getChildCount();
+ final Resources res = getResources();
+
+ // Do two passes; the first pass gets the total number of tiles
+ int numTiles = 0;
+ for (int passNum = 0; passNum < 2; passNum++) {
+ int tileIndex = 0;
+ for (int i = 0; i < childCount; i++) {
+ View child = masterWallpaperList.getChildAt(i);
+ LinearLayout subList;
+
+ int subListStart;
+ int subListEnd;
+ if (child.getTag() instanceof WallpaperTileInfo) {
+ subList = masterWallpaperList;
+ subListStart = i;
+ subListEnd = i + 1;
+ } else { // if (child instanceof LinearLayout) {
+ subList = (LinearLayout) child;
+ subListStart = 0;
+ subListEnd = subList.getChildCount();
+ }
+
+ for (int j = subListStart; j < subListEnd; j++) {
+ WallpaperTileInfo info = (WallpaperTileInfo) subList.getChildAt(j).getTag();
+ if (info.isNamelessWallpaper()) {
+ if (passNum == 0) {
+ numTiles++;
+ } else {
+ CharSequence label = res.getString(
+ R.string.wallpaper_accessibility_name, ++tileIndex, numTiles);
+ info.onIndexUpdated(label);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static Point getDefaultThumbnailSize(Resources res) {
+ return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
+ res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
+
+ }
+
+ private static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
+ Resources res, int resId, int rotation, boolean leftAligned) {
+ int width = size.x;
+ int height = size.y;
+
+ BitmapCropTask cropTask;
+ if (uri != null) {
+ cropTask = new BitmapCropTask(
+ context, uri, null, rotation, width, height, false, true, null);
+ } else if (imageBytes != null) {
+ cropTask = new BitmapCropTask(
+ imageBytes, null, rotation, width, height, false, true, null);
+ } else {
+ cropTask = new BitmapCropTask(
+ context, res, resId, null, rotation, width, height, false, true, null);
+ }
+ Point bounds = cropTask.getImageBounds();
+ if (bounds == null || bounds.x == 0 || bounds.y == 0) {
+ return null;
+ }
+
+ Matrix rotateMatrix = new Matrix();
+ rotateMatrix.setRotate(rotation);
+ float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+ rotateMatrix.mapPoints(rotatedBounds);
+ rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+ rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+ RectF cropRect = WallpaperCropActivity.getMaxCropRect(
+ (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
+ cropTask.setCropBounds(cropRect);
+
+ if (cropTask.cropBitmap()) {
+ return cropTask.getCroppedBitmap();
+ } else {
+ return null;
+ }
+ }
+
+ private void addTemporaryWallpaperTile(final Uri uri) {
+ mTempWallpaperTiles.add(uri);
+ // Add a tile for the image picked from Gallery
+ final FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
+ inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
+ pickedImageThumbnail.setVisibility(View.GONE);
+ setWallpaperItemPaddingToZero(pickedImageThumbnail);
+ mWallpapersView.addView(pickedImageThumbnail, 0);
+
+ // Load the thumbnail
+ final ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
+ final Point defaultSize = getDefaultThumbnailSize(this.getResources());
+ final Context context = this;
+ new AsyncTask<Void, Bitmap, Bitmap>() {
+ protected Bitmap doInBackground(Void...args) {
+ int rotation = WallpaperCropActivity.getRotationFromExif(context, uri);
+ return createThumbnail(defaultSize, context, uri, null, null, 0, rotation, false);
+
+ }
+ protected void onPostExecute(Bitmap thumb) {
+ if (thumb != null) {
+ image.setImageBitmap(thumb);
+ Drawable thumbDrawable = image.getDrawable();
+ thumbDrawable.setDither(true);
+ } else {
+ Log.e(TAG, "Error loading thumbnail for uri=" + uri);
+ }
+ }
+ }.execute();
+
+ UriWallpaperInfo info = new UriWallpaperInfo(uri);
+ pickedImageThumbnail.setTag(info);
+ info.setView(pickedImageThumbnail);
+ addLongPressHandler(pickedImageThumbnail);
+ updateTileIndices();
+ pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
+ mThumbnailOnClickListener.onClick(pickedImageThumbnail);
+ }
+
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == IMAGE_PICK && resultCode == RESULT_OK) {
+ if (data != null && data.getData() != null) {
+ Uri uri = data.getData();
+ addTemporaryWallpaperTile(uri);
+ }
+ } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY) {
+ setResult(RESULT_OK);
+ finish();
+ } else if (requestCode == PICK_LIVE_WALLPAPER) {
+ WallpaperManager wm = WallpaperManager.getInstance(this);
+ final WallpaperInfo oldLiveWallpaper = mLiveWallpaperInfoOnPickerLaunch;
+ WallpaperInfo newLiveWallpaper = wm.getWallpaperInfo();
+ // Try to figure out if a live wallpaper was set;
+ if (newLiveWallpaper != null &&
+ (oldLiveWallpaper == null ||
+ !oldLiveWallpaper.getComponent().equals(newLiveWallpaper.getComponent()))) {
+ // Return if a live wallpaper was set
+ setResult(RESULT_OK);
+ finish();
+ }
+ }
+ }
+
+ static void setWallpaperItemPaddingToZero(FrameLayout frameLayout) {
+ frameLayout.setPadding(0, 0, 0, 0);
+ frameLayout.setForeground(new ZeroPaddingDrawable(frameLayout.getForeground()));
+ }
+
+ private void addLongPressHandler(View v) {
+ v.setOnLongClickListener(mLongClickListener);
+ }
+
+ private ArrayList<ResourceWallpaperInfo> findBundledWallpapers() {
+ ArrayList<ResourceWallpaperInfo> bundledWallpapers =
+ new ArrayList<ResourceWallpaperInfo>(24);
+
+ Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
+ if (r != null) {
+ try {
+ Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
+ bundledWallpapers = addWallpapers(wallpaperRes, r.first.packageName, r.second);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+
+ // Add an entry for the default wallpaper (stored in system resources)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ ResourceWallpaperInfo defaultWallpaperInfo = getPreKKDefaultWallpaperInfo();
+ if (defaultWallpaperInfo != null) {
+ bundledWallpapers.add(0, defaultWallpaperInfo);
+ }
+ }
+ return bundledWallpapers;
+ }
+
+ private boolean writeImageToFileAsJpeg(File f, Bitmap b) {
+ try {
+ f.createNewFile();
+ FileOutputStream thumbFileStream =
+ openFileOutput(f.getName(), Context.MODE_PRIVATE);
+ b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
+ thumbFileStream.close();
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, "Error while writing bitmap to file " + e);
+ f.delete();
+ }
+ return false;
+ }
+
+ private ResourceWallpaperInfo getPreKKDefaultWallpaperInfo() {
+ Resources sysRes = Resources.getSystem();
+ int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
+
+ File defaultThumbFile = new File(getFilesDir(), DEFAULT_WALLPAPER_THUMBNAIL_FILENAME);
+ Bitmap thumb = null;
+ boolean defaultWallpaperExists = false;
+ if (defaultThumbFile.exists()) {
+ thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+ defaultWallpaperExists = true;
+ } else {
+ Resources res = getResources();
+ Point defaultThumbSize = getDefaultThumbnailSize(res);
+ int rotation = WallpaperCropActivity.getRotationFromExif(res, resId);
+ thumb = createThumbnail(
+ defaultThumbSize, this, null, null, sysRes, resId, rotation, false);
+ if (thumb != null) {
+ defaultWallpaperExists = writeImageToFileAsJpeg(defaultThumbFile, thumb);
+ }
+ }
+ if (defaultWallpaperExists) {
+ return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(thumb));
+ }
+ return null;
+ }
+
+ private DefaultWallpaperInfo getDefaultWallpaper() {
+ File defaultThumbFile = new File(getFilesDir(), DEFAULT_WALLPAPER_THUMBNAIL_FILENAME);
+ Bitmap thumb = null;
+ boolean defaultWallpaperExists = false;
+ if (defaultThumbFile.exists()) {
+ thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+ defaultWallpaperExists = true;
+ } else {
+ // Delete old thumbnail file, since we had a bug where the thumbnail wasn't being drawn
+ // before
+ new File(getFilesDir(), OLD_DEFAULT_WALLPAPER_THUMBNAIL_FILENAME).delete();
+
+ Resources res = getResources();
+ Point defaultThumbSize = getDefaultThumbnailSize(res);
+ Drawable wallpaperDrawable = WallpaperManager.getInstance(this).getBuiltInDrawable(
+ defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f);
+ if (wallpaperDrawable != null) {
+ thumb = Bitmap.createBitmap(
+ defaultThumbSize.x, defaultThumbSize.y, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(thumb);
+ wallpaperDrawable.setBounds(0, 0, defaultThumbSize.x, defaultThumbSize.y);
+ wallpaperDrawable.draw(c);
+ c.setBitmap(null);
+ }
+ if (thumb != null) {
+ defaultWallpaperExists = writeImageToFileAsJpeg(defaultThumbFile, thumb);
+ }
+ }
+ if (defaultWallpaperExists) {
+ return new DefaultWallpaperInfo(new BitmapDrawable(thumb));
+ }
+ return null;
+ }
+
+ public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
+ // Context.getPackageName() may return the "original" package name,
+ // com.android.launcher3; Resources needs the real package name,
+ // com.android.launcher3. So we ask Resources for what it thinks the
+ // package name should be.
+ final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
+ try {
+ ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
+ return new Pair<ApplicationInfo, Integer>(info, R.array.wallpapers);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ private ArrayList<ResourceWallpaperInfo> addWallpapers(
+ Resources res, String packageName, int listResId) {
+ ArrayList<ResourceWallpaperInfo> bundledWallpapers =
+ new ArrayList<ResourceWallpaperInfo>(24);
+ final String[] extras = res.getStringArray(listResId);
+ for (String extra : extras) {
+ int resId = res.getIdentifier(extra, "drawable", packageName);
+ if (resId != 0) {
+ final int thumbRes = res.getIdentifier(extra + "_small", "drawable", packageName);
+
+ if (thumbRes != 0) {
+ ResourceWallpaperInfo wallpaperInfo =
+ new ResourceWallpaperInfo(res, resId, res.getDrawable(thumbRes));
+ bundledWallpapers.add(wallpaperInfo);
+ // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
+ }
+ } else {
+ Log.e(TAG, "Couldn't find wallpaper " + extra);
+ }
+ }
+ return bundledWallpapers;
+ }
+
+ public CropView getCropView() {
+ return mCropView;
+ }
+
+ public SavedWallpaperImages getSavedImages() {
+ return mSavedImages;
+ }
+
+ public void onLiveWallpaperPickerLaunch() {
+ mLiveWallpaperInfoOnPickerLaunch = WallpaperManager.getInstance(this).getWallpaperInfo();
+ }
+
+ static class ZeroPaddingDrawable extends LevelListDrawable {
+ public ZeroPaddingDrawable(Drawable d) {
+ super();
+ addLevel(0, 0, d);
+ setLevel(0);
+ }
+
+ @Override
+ public boolean getPadding(Rect padding) {
+ padding.set(0, 0, 0, 0);
+ return true;
+ }
+ }
+
+ private static class BuiltInWallpapersAdapter extends BaseAdapter implements ListAdapter {
+ private LayoutInflater mLayoutInflater;
+ private ArrayList<ResourceWallpaperInfo> mWallpapers;
+
+ BuiltInWallpapersAdapter(Activity activity, ArrayList<ResourceWallpaperInfo> wallpapers) {
+ mLayoutInflater = activity.getLayoutInflater();
+ mWallpapers = wallpapers;
+ }
+
+ public int getCount() {
+ return mWallpapers.size();
+ }
+
+ public ResourceWallpaperInfo getItem(int position) {
+ return mWallpapers.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Drawable thumb = mWallpapers.get(position).mThumb;
+ if (thumb == null) {
+ Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
+ }
+ return createImageTileView(mLayoutInflater, position, convertView, parent, thumb);
+ }
+ }
+
+ public static View createImageTileView(LayoutInflater layoutInflater, int position,
+ View convertView, ViewGroup parent, Drawable thumb) {
+ View view;
+
+ if (convertView == null) {
+ view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ setWallpaperItemPaddingToZero((FrameLayout) view);
+
+ ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
+
+ if (thumb != null) {
+ image.setImageDrawable(thumb);
+ thumb.setDither(true);
+ }
+
+ return view;
+ }
+
+ // In Launcher3, we override this with a method that catches exceptions
+ // from starting activities; didn't want to copy and paste code into here
+ public void startActivityForResultSafely(Intent intent, int requestCode) {
+ startActivityForResult(intent, requestCode);
+ }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperRootView.java b/WallpaperPicker/src/com/android/launcher3/WallpaperRootView.java
new file mode 100644
index 000000000..ceaa043a7
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperRootView.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.RelativeLayout;
+
+public class WallpaperRootView extends RelativeLayout {
+ private final WallpaperPickerActivity a;
+ public WallpaperRootView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ a = (WallpaperPickerActivity) context;
+ }
+ public WallpaperRootView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ a = (WallpaperPickerActivity) context;
+ }
+
+ protected boolean fitSystemWindows(Rect insets) {
+ a.setWallpaperStripYOffset(insets.bottom);
+ return true;
+ }
+}