summaryrefslogtreecommitdiffstats
path: root/src/org/cyanogenmod
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/cyanogenmod')
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/AlphaDisableableButton.java50
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/CheckableFrameLayout.java63
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/CropView.java321
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/DrawableTileSource.java102
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/LiveWallpaperListAdapter.java203
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/Partner.java109
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/PickerFiles.java30
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/SavedWallpaperImages.java227
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/StylusEventHelper.java82
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/ThirdPartyWallpaperPickerListAdapter.java137
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/Utilities.java549
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java540
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/WallpaperPickerActivity.java1410
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/base/BaseActivity.java21
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/util/Constants.java24
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/util/Thunk.java43
-rw-r--r--src/org/cyanogenmod/wallpaperpicker/util/WallpaperUtils.java125
17 files changed, 4036 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/wallpaperpicker/AlphaDisableableButton.java b/src/org/cyanogenmod/wallpaperpicker/AlphaDisableableButton.java
new file mode 100644
index 0000000..9a3d6c3
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/AlphaDisableableButton.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package org.cyanogenmod.wallpaperpicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * A Button which becomes translucent when it is disabled
+ */
+public class AlphaDisableableButton extends Button {
+ public static float DISABLED_ALPHA_VALUE = 0.4f;
+ public AlphaDisableableButton(Context context) {
+ this(context, null);
+ }
+
+ public AlphaDisableableButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AlphaDisableableButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if(enabled) {
+ setAlpha(1.0f);
+ } else {
+ setAlpha(DISABLED_ALPHA_VALUE);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpaperpicker/CheckableFrameLayout.java b/src/org/cyanogenmod/wallpaperpicker/CheckableFrameLayout.java
new file mode 100644
index 0000000..8f1bc3a
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/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 org.cyanogenmod.wallpaperpicker;
+
+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/src/org/cyanogenmod/wallpaperpicker/CropView.java b/src/org/cyanogenmod/wallpaperpicker/CropView.java
new file mode 100644
index 0000000..118efd3
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/CropView.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpaperpicker;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+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] = (float) Math.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] = (float) Math.ceil(adjustment[dim]);
+ }
+
+ mInverseRotateMatrix.mapPoints(adjustment);
+ mCenterX += adjustment[0];
+ mCenterY += adjustment[1];
+ updateCenter();
+ }
+ }
+
+ mLastX = x;
+ mLastY = y;
+ return true;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpaperpicker/DrawableTileSource.java b/src/org/cyanogenmod/wallpaperpicker/DrawableTileSource.java
new file mode 100644
index 0000000..5752047
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/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 org.cyanogenmod.wallpaperpicker;
+
+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/src/org/cyanogenmod/wallpaperpicker/LiveWallpaperListAdapter.java b/src/org/cyanogenmod/wallpaperpicker/LiveWallpaperListAdapter.java
new file mode 100644
index 0000000..f72564f
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/LiveWallpaperListAdapter.java
@@ -0,0 +1,203 @@
+/*
+ * 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 org.cyanogenmod.wallpaperpicker;
+
+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.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import org.cyanogenmod.wallpaperpicker.util.Thunk;
+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;
+
+ @Thunk
+ 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;
+ }
+
+ 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 {
+ @Thunk Drawable mThumbnail;
+ @Thunk 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.startActivityForResultSafely(preview,
+ WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+ }
+ }
+
+ 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/src/org/cyanogenmod/wallpaperpicker/Partner.java b/src/org/cyanogenmod/wallpaperpicker/Partner.java
new file mode 100644
index 0000000..0e0d7d0
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/Partner.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpaperpicker;
+
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.Pair;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utilities to discover and interact with partner customizations. There can
+ * only be one set of customizations on a device, and it must be bundled with
+ * the system.
+ */
+public class Partner {
+
+ static final String TAG = "Launcher.Partner";
+
+ /** Marker action used to discover partner */
+ private static final String
+ ACTION_PARTNER_CUSTOMIZATION = "com.android.launcher3.action.PARTNER_CUSTOMIZATION";
+
+ public static final String RES_FOLDER = "partner_folder";
+ public static final String RES_WALLPAPERS = "partner_wallpapers";
+
+ public static final String RES_DEFAULT_WALLPAPER_HIDDEN = "default_wallpapper_hidden";
+ public static final String RES_SYSTEM_WALLPAPER_DIR = "system_wallpaper_directory";
+
+ private static boolean sSearched = false;
+ private static List<Partner> sPartners;
+
+ static {
+ sPartners = new ArrayList<Partner>();
+ }
+
+ /**
+ * Find and return first partner details, or {@code null} if none exists.
+ */
+ public static synchronized Partner get(PackageManager pm) {
+ getAllPartners(pm);
+ return sPartners.size() > 0 ? sPartners.get(0) : null;
+ }
+
+ /**
+ * Find and return all partner details, or {@code null} if none exists.
+ */
+ public static synchronized List<Partner> getAllPartners(PackageManager pm) {
+ if (!sSearched) {
+ List<Pair<String, Resources>> apkInfos =
+ Utilities.findSystemApks(ACTION_PARTNER_CUSTOMIZATION, pm);
+ for (Pair<String, Resources> apkInfo : apkInfos) {
+ sPartners.add(new Partner(apkInfo.first, apkInfo.second));
+ }
+ sSearched = true;
+ }
+ return sPartners;
+ }
+
+ private final String mPackageName;
+ private final Resources mResources;
+
+ private Partner(String packageName, Resources res) {
+ mPackageName = packageName;
+ mResources = res;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public Resources getResources() {
+ return mResources;
+ }
+
+ public boolean hasFolder() {
+ int folder = getResources().getIdentifier(Partner.RES_FOLDER,
+ "xml", getPackageName());
+ return folder != 0;
+ }
+
+ public boolean hideDefaultWallpaper() {
+ int resId = getResources().getIdentifier(RES_DEFAULT_WALLPAPER_HIDDEN, "bool",
+ getPackageName());
+ return resId != 0 && getResources().getBoolean(resId);
+ }
+
+ public File getWallpaperDirectory() {
+ int resId = getResources().getIdentifier(RES_SYSTEM_WALLPAPER_DIR, "string",
+ getPackageName());
+ return (resId != 0) ? new File(getResources().getString(resId)) : null;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpaperpicker/PickerFiles.java b/src/org/cyanogenmod/wallpaperpicker/PickerFiles.java
new file mode 100644
index 0000000..eb3485e
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/PickerFiles.java
@@ -0,0 +1,30 @@
+package org.cyanogenmod.wallpaperpicker;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Central list of files the Launcher writes to the application data directory.
+ *
+ * To add a new Launcher file, create a String constant referring to the filename, and add it to
+ * ALL_FILES, as shown below.
+ */
+public class PickerFiles {
+
+ private static final String XML = ".xml";
+
+ public static final String DEFAULT_WALLPAPER_THUMBNAIL = "default_thumb2.jpg";
+ public static final String DEFAULT_WALLPAPER_THUMBNAIL_OLD = "default_thumb.jpg";
+ public static final String WALLPAPER_CROP_PREFERENCES_KEY =
+ "org.cyanogenmod.wallpaperpicker.WallpaperCropActivity";
+
+ public static final String WALLPAPER_IMAGES_DB = "saved_wallpaper_images.db";
+
+ public static final List<String> ALL_FILES = Collections.unmodifiableList(Arrays.asList(
+ DEFAULT_WALLPAPER_THUMBNAIL,
+ DEFAULT_WALLPAPER_THUMBNAIL_OLD,
+ WALLPAPER_CROP_PREFERENCES_KEY + XML,
+ WALLPAPER_IMAGES_DB));
+
+}
diff --git a/src/org/cyanogenmod/wallpaperpicker/SavedWallpaperImages.java b/src/org/cyanogenmod/wallpaperpicker/SavedWallpaperImages.java
new file mode 100644
index 0000000..c3e4bb4
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/SavedWallpaperImages.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpaperpicker;
+
+import android.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 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 = "SavedWallpaperImages";
+ private ImageDb mDb;
+ private boolean mIsLockScreenWallpaper;
+ ArrayList<SavedWallpaperTile> mImages;
+ Context mContext;
+ LayoutInflater mLayoutInflater;
+
+ public static class SavedWallpaperTile extends WallpaperPickerActivity.FileWallpaperInfo {
+ private int mDbId;
+ public SavedWallpaperTile(int dbId, File target, Drawable thumb,
+ boolean isLockScreenWallpaper) {
+ super(target, thumb, isLockScreenWallpaper);
+ mDbId = dbId;
+ }
+
+ @Override
+ public void onDelete(WallpaperPickerActivity a) {
+ a.getSavedImages().deleteImage(mDbId);
+ }
+ }
+
+ public SavedWallpaperImages(Context context, boolean isLockScreenWallpaper) {
+ // We used to store the saved images in the cache directory, but that meant they'd get
+ // deleted sometimes-- move them to the data directory
+ ImageDb.moveFromCacheDirectoryIfNecessary(context);
+ mDb = new ImageDb(context);
+ mContext = context;
+ mLayoutInflater = LayoutInflater.from(context);
+ mIsLockScreenWallpaper = isLockScreenWallpaper;
+ }
+
+ 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,
+ ImageDb.COLUMN_IMAGE_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 File(mContext.getFilesDir(), result.getString(2)),
+ new BitmapDrawable(thumb), mIsLockScreenWallpaper));
+ }
+ }
+ 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, convertView, parent, thumbDrawable);
+ }
+
+ 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 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, context.getDatabasePath(PickerFiles.WALLPAPER_IMAGES_DB).getPath(),
+ null, DB_VERSION);
+ // Store the context for later use
+ mContext = context;
+ }
+
+ public static void moveFromCacheDirectoryIfNecessary(Context context) {
+ // We used to store the saved images in the cache directory, but that meant they'd get
+ // deleted sometimes-- move them to the data directory
+ File oldSavedImagesFile = new File(context.getCacheDir(),
+ PickerFiles.WALLPAPER_IMAGES_DB);
+ File savedImagesFile = context.getDatabasePath(PickerFiles.WALLPAPER_IMAGES_DB);
+ if (oldSavedImagesFile.exists()) {
+ oldSavedImagesFile.renameTo(savedImagesFile);
+ }
+ }
+ @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/src/org/cyanogenmod/wallpaperpicker/StylusEventHelper.java b/src/org/cyanogenmod/wallpaperpicker/StylusEventHelper.java
new file mode 100644
index 0000000..d6d9649
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/StylusEventHelper.java
@@ -0,0 +1,82 @@
+package org.cyanogenmod.wallpaperpicker;
+
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+/**
+ * Helper for identifying when a stylus touches a view while the primary stylus button is pressed.
+ * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}. On a
+ * stylus button press this performs the view's {@link View#performLongClick()} method, if the view
+ * is long clickable.
+ */
+public class StylusEventHelper {
+ private boolean mIsButtonPressed;
+ private View mView;
+
+ public StylusEventHelper(View view) {
+ mView = view;
+ }
+
+ /**
+ * Call this in onTouchEvent method of a view to identify a stylus button press and perform a
+ * long click (if the view is long clickable).
+ *
+ * @param event The event to check for a stylus button press.
+ * @return Whether a stylus event occurred and was handled.
+ */
+ public boolean checkAndPerformStylusEvent(MotionEvent event) {
+ final float slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
+
+ if (!mView.isLongClickable()) {
+ // We don't do anything unless the view is long clickable.
+ return false;
+ }
+
+ final boolean stylusButtonPressed = isStylusButtonPressed(event);
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mIsButtonPressed = false;
+ if (stylusButtonPressed && mView.performLongClick()) {
+ mIsButtonPressed = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (Utilities.pointInView(mView, event.getX(), event.getY(), slop)) {
+ if (!mIsButtonPressed && stylusButtonPressed && mView.performLongClick()) {
+ mIsButtonPressed = true;
+ return true;
+ } else if (mIsButtonPressed && !stylusButtonPressed) {
+ mIsButtonPressed = false;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mIsButtonPressed = false;
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Whether a stylus button press is occurring.
+ */
+ public boolean inStylusButtonPressed() {
+ return mIsButtonPressed;
+ }
+
+ /**
+ * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button
+ * pressed.
+ *
+ * @param event The event to check.
+ * @return Whether a stylus button press occurred.
+ */
+ private static boolean isStylusButtonPressed(MotionEvent event) {
+ return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
+ && ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY)
+ == MotionEvent.BUTTON_SECONDARY);
+ }
+} \ No newline at end of file
diff --git a/src/org/cyanogenmod/wallpaperpicker/ThirdPartyWallpaperPickerListAdapter.java b/src/org/cyanogenmod/wallpaperpicker/ThirdPartyWallpaperPickerListAdapter.java
new file mode 100644
index 0000000..25465aa
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/ThirdPartyWallpaperPickerListAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * 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 org.cyanogenmod.wallpaperpicker;
+
+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.ListAdapter;
+import android.widget.TextView;
+
+import org.cyanogenmod.wallpaperpicker.util.Thunk;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements ListAdapter {
+ 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 {
+ @Thunk
+ 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.from(context);
+ 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;
+ }
+
+ 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/src/org/cyanogenmod/wallpaperpicker/Utilities.java b/src/org/cyanogenmod/wallpaperpicker/Utilities.java
new file mode 100644
index 0000000..21a22be
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/Utilities.java
@@ -0,0 +1,549 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpaperpicker;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.Toast;
+import org.cyanogenmod.wallpaperpicker.util.WallpaperUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Various utilities shared amongst the Launcher's classes.
+ */
+public final class Utilities {
+
+ private static final String TAG = "WallpaperPicker.Utilities";
+
+ private static final float WALLPAPER_SCREENS_SPAN = 2f;
+
+ private static final Rect sOldBounds = new Rect();
+ private static final Canvas sCanvas = new Canvas();
+
+ private static final Pattern sTrimPattern =
+ Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
+
+ static {
+ sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+ }
+ static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
+ static int sColorIndex = 0;
+
+ private static final int[] sLoc0 = new int[2];
+ private static final int[] sLoc1 = new int[2];
+
+ // TODO: use Build.VERSION_CODES when available
+ public static final boolean ATLEAST_MARSHMALLOW = Build.VERSION.SDK_INT >= 23;
+
+ public static final boolean ATLEAST_LOLLIPOP_MR1 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
+
+ public static final boolean ATLEAST_LOLLIPOP =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+
+ public static final boolean ATLEAST_KITKAT =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+ public static final boolean ATLEAST_JB_MR1 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+ public static final boolean ATLEAST_JB_MR2 =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
+
+ // To turn on these properties, type
+ // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
+ private static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
+ private static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
+
+ public static boolean isPropertyEnabled(String propertyName) {
+ return Log.isLoggable(propertyName, Log.VERBOSE);
+ }
+
+ /**
+ * Utility method to determine whether the given point, in local coordinates,
+ * is inside the view, where the area of the view is expanded by the slop factor.
+ * This method is called while processing touch-move events to determine if the event
+ * is still within the view.
+ */
+ public static boolean pointInView(View v, float localX, float localY, float slop) {
+ return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
+ localY < (v.getHeight() + slop);
+ }
+
+ public static void scaleRect(Rect r, float scale) {
+ if (scale != 1.0f) {
+ r.left = (int) (r.left * scale + 0.5f);
+ r.top = (int) (r.top * scale + 0.5f);
+ r.right = (int) (r.right * scale + 0.5f);
+ r.bottom = (int) (r.bottom * scale + 0.5f);
+ }
+ }
+
+ public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) {
+ v0.getLocationInWindow(sLoc0);
+ v1.getLocationInWindow(sLoc1);
+
+ sLoc0[0] += (v0.getMeasuredWidth() * v0.getScaleX()) / 2;
+ sLoc0[1] += (v0.getMeasuredHeight() * v0.getScaleY()) / 2;
+ sLoc1[0] += (v1.getMeasuredWidth() * v1.getScaleX()) / 2;
+ sLoc1[1] += (v1.getMeasuredHeight() * v1.getScaleY()) / 2;
+
+ if (delta == null) {
+ delta = new int[2];
+ }
+
+ delta[0] = sLoc1[0] - sLoc0[0];
+ delta[1] = sLoc1[1] - sLoc0[1];
+
+ return delta;
+ }
+
+ public static void scaleRectAboutCenter(Rect r, float scale) {
+ int cx = r.centerX();
+ int cy = r.centerY();
+ r.offset(-cx, -cy);
+ Utilities.scaleRect(r, scale);
+ r.offset(cx, cy);
+ }
+
+ static boolean isSystemApp(Context context, Intent intent) {
+ PackageManager pm = context.getPackageManager();
+ ComponentName cn = intent.getComponent();
+ String packageName = null;
+ if (cn == null) {
+ ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if ((info != null) && (info.activityInfo != null)) {
+ packageName = info.activityInfo.packageName;
+ }
+ } else {
+ packageName = cn.getPackageName();
+ }
+ if (packageName != null) {
+ try {
+ PackageInfo info = pm.getPackageInfo(packageName, 0);
+ return (info != null) && (info.applicationInfo != null) &&
+ ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
+ * @param bitmap The bitmap to scan
+ * @param samples The approximate max number of samples to use.
+ */
+ static int findDominantColorByHue(Bitmap bitmap, int samples) {
+ final int height = bitmap.getHeight();
+ final int width = bitmap.getWidth();
+ int sampleStride = (int) Math.sqrt((height * width) / samples);
+ if (sampleStride < 1) {
+ sampleStride = 1;
+ }
+
+ // This is an out-param, for getting the hsv values for an rgb
+ float[] hsv = new float[3];
+
+ // First get the best hue, by creating a histogram over 360 hue buckets,
+ // where each pixel contributes a score weighted by saturation, value, and alpha.
+ float[] hueScoreHistogram = new float[360];
+ float highScore = -1;
+ int bestHue = -1;
+
+ for (int y = 0; y < height; y += sampleStride) {
+ for (int x = 0; x < width; x += sampleStride) {
+ int argb = bitmap.getPixel(x, y);
+ int alpha = 0xFF & (argb >> 24);
+ if (alpha < 0x80) {
+ // Drop mostly-transparent pixels.
+ continue;
+ }
+ // Remove the alpha channel.
+ int rgb = argb | 0xFF000000;
+ Color.colorToHSV(rgb, hsv);
+ // Bucket colors by the 360 integer hues.
+ int hue = (int) hsv[0];
+ if (hue < 0 || hue >= hueScoreHistogram.length) {
+ // Defensively avoid array bounds violations.
+ continue;
+ }
+ float score = hsv[1] * hsv[2];
+ hueScoreHistogram[hue] += score;
+ if (hueScoreHistogram[hue] > highScore) {
+ highScore = hueScoreHistogram[hue];
+ bestHue = hue;
+ }
+ }
+ }
+
+ SparseArray<Float> rgbScores = new SparseArray<Float>();
+ int bestColor = 0xff000000;
+ highScore = -1;
+ // Go back over the RGB colors that match the winning hue,
+ // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
+ // The highest-scoring RGB color wins.
+ for (int y = 0; y < height; y += sampleStride) {
+ for (int x = 0; x < width; x += sampleStride) {
+ int rgb = bitmap.getPixel(x, y) | 0xff000000;
+ Color.colorToHSV(rgb, hsv);
+ int hue = (int) hsv[0];
+ if (hue == bestHue) {
+ float s = hsv[1];
+ float v = hsv[2];
+ int bucket = (int) (s * 100) + (int) (v * 10000);
+ // Score by cumulative saturation * value.
+ float score = s * v;
+ Float oldTotal = rgbScores.get(bucket);
+ float newTotal = oldTotal == null ? score : oldTotal + score;
+ rgbScores.put(bucket, newTotal);
+ if (newTotal > highScore) {
+ highScore = newTotal;
+ // All the colors in the winning bucket are very similar. Last in wins.
+ bestColor = rgb;
+ }
+ }
+ }
+ }
+ return bestColor;
+ }
+
+ /*
+ * Finds a system apk which had a broadcast receiver listening to a particular action.
+ * @param action intent action used to find the apk
+ * @return a pair of apk package name and the resources.
+ */
+ static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
+ final Intent intent = new Intent(action);
+ for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
+ if (info.activityInfo != null &&
+ (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ final String packageName = info.activityInfo.packageName;
+ try {
+ final Resources res = pm.getResourcesForApplication(packageName);
+ return Pair.create(packageName, res);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to find resources for " + packageName);
+ }
+ }
+ }
+ return null;
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static boolean isViewAttachedToWindow(View v) {
+ if (ATLEAST_KITKAT) {
+ return v.isAttachedToWindow();
+ } else {
+ // A proxy call which returns null, if the view is not attached to the window.
+ return v.getKeyDispatcherState() != null;
+ }
+ }
+
+ /*
+ * Finds all system apks which had a broadcast receiver listening to a particular action.
+ * @param action intent action used to find the apk
+ * @return a list of pairs of apk package name and the resources.
+ */
+ static List<Pair<String, Resources>> findSystemApks(String action, PackageManager pm) {
+ final Intent intent = new Intent(action);
+ List<Pair<String, Resources>> systemApks = new ArrayList<Pair<String, Resources>>();
+ for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
+ if (info.activityInfo != null &&
+ (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ final String packageName = info.activityInfo.packageName;
+ try {
+ final Resources res = pm.getResourcesForApplication(packageName);
+ systemApks.add(Pair.create(packageName, res));
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to find resources for " + packageName);
+ }
+ }
+ }
+ return systemApks;
+ }
+
+ /**
+ * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
+ * provided by the same package which is set to be global search activity.
+ * If widgetCategory is not supported, or no such widget is found, returns the first widget
+ * provided by the package.
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public static AppWidgetProviderInfo getSearchWidgetProvider(Context context) {
+ SearchManager searchManager =
+ (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
+ ComponentName searchComponent = searchManager.getGlobalSearchActivity();
+ if (searchComponent == null) return null;
+ String providerPkg = searchComponent.getPackageName();
+
+ AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
+
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
+ if (info.provider.getPackageName().equals(providerPkg)) {
+ if (ATLEAST_JB_MR1) {
+ if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
+ return info;
+ } else if (defaultWidgetForSearchPackage == null) {
+ defaultWidgetForSearchPackage = info;
+ }
+ } else {
+ return info;
+ }
+ }
+ }
+ return defaultWidgetForSearchPackage;
+ }
+
+ /**
+ * Compresses the bitmap to a byte array for serialization.
+ */
+ public static byte[] flattenBitmap(Bitmap bitmap) {
+ // Try go guesstimate how much space the icon will take when serialized
+ // to avoid unnecessary allocations/copies during the write.
+ int size = bitmap.getWidth() * bitmap.getHeight() * 4;
+ ByteArrayOutputStream out = new ByteArrayOutputStream(size);
+ try {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ out.close();
+ return out.toByteArray();
+ } catch (IOException e) {
+ Log.w(TAG, "Could not write bitmap");
+ return null;
+ }
+ }
+
+ /**
+ * Find the first vacant cell, if there is one.
+ *
+ * @param vacant Holds the x and y coordinate of the vacant cell
+ * @param spanX Horizontal cell span.
+ * @param spanY Vertical cell span.
+ *
+ * @return true if a vacant cell was found
+ */
+ public static boolean findVacantCell(int[] vacant, int spanX, int spanY,
+ int xCount, int yCount, boolean[][] occupied) {
+
+ for (int y = 0; (y + spanY) <= yCount; y++) {
+ for (int x = 0; (x + spanX) <= xCount; x++) {
+ boolean available = !occupied[x][y];
+ out: for (int i = x; i < x + spanX; i++) {
+ for (int j = y; j < y + spanY; j++) {
+ available = available && !occupied[i][j];
+ if (!available) break out;
+ }
+ }
+
+ if (available) {
+ vacant[0] = x;
+ vacant[1] = y;
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Trims the string, removing all whitespace at the beginning and end of the string.
+ * Non-breaking whitespaces are also removed.
+ */
+ public static String trim(CharSequence s) {
+ if (s == null) {
+ return null;
+ }
+
+ // Just strip any sequence of whitespace or java space characters from the beginning and end
+ Matcher m = sTrimPattern.matcher(s);
+ return m.replaceAll("$1");
+ }
+
+ /**
+ * Calculates the height of a given string at a specific text size.
+ */
+ public static float calculateTextHeight(float textSizePx) {
+ Paint p = new Paint();
+ p.setTextSize(textSizePx);
+ Paint.FontMetrics fm = p.getFontMetrics();
+ return -fm.top + fm.bottom;
+ }
+
+ /**
+ * Convenience println with multiple args.
+ */
+ public static void println(String key, Object... args) {
+ StringBuilder b = new StringBuilder();
+ b.append(key);
+ b.append(": ");
+ boolean isFirstArgument = true;
+ for (Object arg : args) {
+ if (isFirstArgument) {
+ isFirstArgument = false;
+ } else {
+ b.append(", ");
+ }
+ b.append(arg);
+ }
+ System.out.println(b.toString());
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public static boolean isRtl(Resources res) {
+ return ATLEAST_JB_MR1 &&
+ (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+ }
+
+ public static float dpiFromPx(int size, DisplayMetrics metrics){
+ return (size / metrics.density);
+ }
+ public static int pxFromDp(float size, DisplayMetrics metrics) {
+ return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ size, metrics));
+ }
+ public static int pxFromSp(float size, DisplayMetrics metrics) {
+ return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+ size, metrics));
+ }
+
+ public static boolean isPackageInstalled(Context context, String pkg) {
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ PackageInfo pi = packageManager.getPackageInfo(pkg, 0);
+ return pi.applicationInfo.enabled;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ public static void startActivityForResultSafely(
+ Activity activity, Intent intent, int requestCode) {
+ try {
+ activity.startActivityForResult(intent, requestCode);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ } catch (SecurityException e) {
+ Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Wallpaper picker does not have the permission to launch " + intent +
+ ". Make sure to create a MAIN intent-filter for the corresponding activity " +
+ "or use the exported attribute for this activity.", e);
+ }
+ }
+
+ public static Bitmap getThemeWallpaper(Context context, String path, String pkgName,
+ boolean thumb) {
+ InputStream is = null;
+ try {
+ Resources res = context.getPackageManager().getResourcesForApplication(pkgName);
+ if (res == null) {
+ return null;
+ }
+
+ AssetManager am = res.getAssets();
+ String[] wallpapers = am.list(path);
+ if (wallpapers == null || wallpapers.length == 0) {
+ return null;
+ }
+ is = am.open(path + File.separator + wallpapers[0]);
+
+ BitmapFactory.Options bounds = new BitmapFactory.Options();
+ bounds.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, bounds);
+ if ((bounds.outWidth == -1) || (bounds.outHeight == -1))
+ return null;
+
+ int originalSize = (bounds.outHeight > bounds.outWidth) ? bounds.outHeight
+ : bounds.outWidth;
+ Point outSize;
+
+ if (thumb) {
+ outSize = getDefaultThumbnailSize(context.getResources());
+ } else {
+ outSize = WallpaperUtils.getDefaultWallpaperSize(res,
+ ((Activity) context).getWindowManager());
+ }
+ int thumbSampleSize = (outSize.y > outSize.x) ? outSize.y : outSize.x;
+
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inSampleSize = originalSize / thumbSampleSize;
+ return BitmapFactory.decodeStream(is, null, opts);
+ } catch (IOException e) {
+ return null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ } catch (OutOfMemoryError e) {
+ return null;
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public static Point getDefaultThumbnailSize(Resources res) {
+ return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
+ res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
+
+ }
+}
diff --git a/src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java b/src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java
new file mode 100644
index 0000000..945ccac
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/WallpaperCropActivity.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpaperpicker;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.widget.Toast;
+
+import com.android.gallery3d.common.BitmapCropTask;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource.InBitmapProvider;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+import org.cyanogenmod.wallpaperpicker.base.BaseActivity;
+import org.cyanogenmod.wallpaperpicker.util.Thunk;
+import org.cyanogenmod.wallpaperpicker.util.WallpaperUtils;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+public class WallpaperCropActivity extends BaseActivity implements Handler.Callback {
+ private static final String LOGTAG = "CropActivity";
+
+ private static final int REQUEST_CODE_STORAGE_PERMISSION_CHECK = 100;
+
+ protected static final String WALLPAPER_WIDTH_KEY = WallpaperUtils.WALLPAPER_WIDTH_KEY;
+ protected static final String WALLPAPER_HEIGHT_KEY = WallpaperUtils.WALLPAPER_HEIGHT_KEY;
+
+ /**
+ * The maximum bitmap size we allow to be returned through the intent.
+ * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
+ * have some overhead to hit so that we go way below the limit here to make
+ * sure the intent stays below 1MB.We should consider just returning a byte
+ * array instead of a Bitmap instance to avoid overhead.
+ */
+ public static final int MAX_BMAP_IN_INTENT = 750000;
+ public static final float WALLPAPER_SCREENS_SPAN = WallpaperUtils.WALLPAPER_SCREENS_SPAN;
+
+ private static final int MSG_LOAD_IMAGE = 1;
+
+ protected CropView mCropView;
+ protected View mProgressView;
+ protected Uri mUri;
+ protected View mSetWallpaperButton;
+
+ private HandlerThread mLoaderThread;
+ private Handler mLoaderHandler;
+ @Thunk
+ LoadRequest mCurrentLoadRequest;
+ private byte[] mTempStorageForDecoding = new byte[16 * 1024];
+ // A weak-set of reusable bitmaps
+ @Thunk Set<Bitmap> mReusableBitmaps =
+ Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (!hasStoragePermissions()) {
+ requestStoragePermissions();
+ } else {
+ load();
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ if (requestCode == REQUEST_CODE_STORAGE_PERMISSION_CHECK) {
+ for (int i = 0; i < permissions.length; i++ ) {
+ final String permission = permissions[i];
+ final int grantResult = grantResults[i];
+ if (permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) {
+ if (grantResult == PackageManager.PERMISSION_GRANTED) {
+ load();
+ } else {
+ Toast.makeText(this, getString(R.string.storage_permission_denied),
+ Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ }
+ }
+ }
+ }
+
+ private void load() {
+ mLoaderThread = new HandlerThread("wallpaper_loader");
+ mLoaderThread.start();
+ mLoaderHandler = new Handler(mLoaderThread.getLooper(), this);
+
+ init();
+ }
+
+ protected void init() {
+ setContentView(R.layout.wallpaper_cropper);
+
+ mCropView = (CropView) findViewById(R.id.cropView);
+ mProgressView = findViewById(R.id.loading);
+
+ Intent cropIntent = getIntent();
+ final Uri imageUri = cropIntent.getData();
+
+ if (imageUri == null) {
+ Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
+ finish();
+ return;
+ }
+
+ // Action bar
+ // Show the custom action bar view
+ final ActionBar actionBar = getActionBar();
+ actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
+ actionBar.getCustomView().setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ boolean finishActivityWhenDone = true;
+ cropImageAndSetWallpaper(imageUri, null, false, finishActivityWhenDone);
+ }
+ });
+ mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
+
+ // Load image in background
+ final BitmapRegionTileSource.UriBitmapSource bitmapSource =
+ new BitmapRegionTileSource.UriBitmapSource(getContext(), imageUri);
+ mSetWallpaperButton.setEnabled(false);
+ Runnable onLoad = new Runnable() {
+ public void run() {
+ if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
+ Toast.makeText(getContext(), R.string.wallpaper_load_fail,
+ Toast.LENGTH_LONG).show();
+ finish();
+ } else {
+ mSetWallpaperButton.setEnabled(true);
+ }
+ }
+ };
+ setCropViewTileSource(bitmapSource, true, false, null, onLoad);
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mCropView != null) {
+ mCropView.destroy();
+ }
+ if (mLoaderThread != null) {
+ mLoaderThread.quit();
+ }
+ super.onDestroy();
+ }
+
+ /**
+ * This is called on {@link #mLoaderThread}
+ */
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_LOAD_IMAGE) {
+ final LoadRequest req = (LoadRequest) msg.obj;
+ try {
+ req.src.loadInBackground(new InBitmapProvider() {
+
+ @Override
+ public Bitmap forPixelCount(int count) {
+ Bitmap bitmapToReuse = null;
+ // Find the smallest bitmap that satisfies the pixel count limit
+ synchronized (mReusableBitmaps) {
+ int currentBitmapSize = Integer.MAX_VALUE;
+ for (Bitmap b : mReusableBitmaps) {
+ int bitmapSize = b.getWidth() * b.getHeight();
+ if ((bitmapSize >= count) && (bitmapSize < currentBitmapSize)) {
+ bitmapToReuse = b;
+ currentBitmapSize = bitmapSize;
+ }
+ }
+
+ if (bitmapToReuse != null) {
+ mReusableBitmaps.remove(bitmapToReuse);
+ }
+ }
+ return bitmapToReuse;
+ }
+ });
+ } catch (SecurityException securityException) {
+ if (isActivityDestroyed()) {
+ // Temporarily granted permissions are revoked when the activity
+ // finishes, potentially resulting in a SecurityException here.
+ // Even though {@link #isDestroyed} might also return true in different
+ // situations where the configuration changes, we are fine with
+ // catching these cases here as well.
+ return true;
+ } else {
+ // otherwise it had a different cause and we throw it further
+ throw securityException;
+ }
+ }
+
+ req.result = new BitmapRegionTileSource(getContext(), req.src, mTempStorageForDecoding);
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (req == mCurrentLoadRequest) {
+ onLoadRequestComplete(req,
+ req.src.getLoadingState() == BitmapSource.State.LOADED);
+ } else {
+ addReusableBitmap(req.result);
+ }
+ }
+ });
+ return true;
+ }
+ return false;
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ protected boolean isActivityDestroyed() {
+ return Utilities.ATLEAST_JB_MR1 && isDestroyed();
+ }
+
+ @Thunk void addReusableBitmap(TileSource src) {
+ synchronized (mReusableBitmaps) {
+ if (Utilities.ATLEAST_KITKAT && src instanceof BitmapRegionTileSource) {
+ Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
+ if (preview != null && preview.isMutable()) {
+ mReusableBitmaps.add(preview);
+ }
+ }
+ }
+ }
+
+ protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+ mCurrentLoadRequest = null;
+ if (success) {
+ TileSource oldSrc = mCropView.getTileSource();
+ mCropView.setTileSource(req.result, null);
+ mCropView.setTouchEnabled(req.touchEnabled);
+ if (req.moveToLeft) {
+ mCropView.moveToLeft();
+ }
+ if (req.scaleProvider != null) {
+ mCropView.setScale(req.scaleProvider.getScale(req.result));
+ }
+
+ // Free last image
+ if (oldSrc != null) {
+ // Call yield instead of recycle, as we only want to free GL resource.
+ // We can still reuse the bitmap for decoding any other image.
+ oldSrc.getPreview().yield();
+ }
+ addReusableBitmap(oldSrc);
+ }
+ if (req.postExecute != null) {
+ req.postExecute.run();
+ }
+ mProgressView.setVisibility(View.GONE);
+ }
+
+ public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled,
+ boolean moveToLeft, CropViewScaleProvider scaleProvider, Runnable postExecute) {
+ final LoadRequest req = new LoadRequest();
+ req.moveToLeft = moveToLeft;
+ req.src = bitmapSource;
+ req.touchEnabled = touchEnabled;
+ req.postExecute = postExecute;
+ req.scaleProvider = scaleProvider;
+ mCurrentLoadRequest = req;
+
+ // Remove any pending requests
+ mLoaderHandler.removeMessages(MSG_LOAD_IMAGE);
+ Message.obtain(mLoaderHandler, MSG_LOAD_IMAGE, req).sendToTarget();
+
+ // We don't want to show the spinner every time we load an image, because that would be
+ // annoying; instead, only start showing the spinner if loading the image has taken
+ // longer than 1 sec (ie 1000 ms)
+ mProgressView.postDelayed(new Runnable() {
+ public void run() {
+ if (mCurrentLoadRequest == req) {
+ mProgressView.setVisibility(View.VISIBLE);
+ }
+ }
+ }, 1000);
+ }
+
+
+ protected void setWallpaper(Uri uri, boolean isLockScreenPicker,
+ final boolean finishActivityWhenDone) {
+ int rotation = BitmapUtils.getRotationFromExif(getContext(), uri);
+ BitmapCropTask cropTask = new BitmapCropTask(
+ getContext(), uri, null, rotation, 0, 0, true, isLockScreenPicker, false, null);
+ final Point bounds = cropTask.getImageBounds();
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ updateWallpaperDimensions(bounds.x, bounds.y);
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ cropTask.setOnEndRunnable(onEndCrop);
+ cropTask.setNoCrop(true);
+ cropTask.execute();
+ }
+
+ protected void cropImageAndSetWallpaper(Resources res, int resId, boolean isLockScreenPicker,
+ final boolean finishActivityWhenDone) {
+ // crop this image and scale it down to the default wallpaper size for
+ // this device
+ int rotation = BitmapUtils.getRotationFromExif(res, resId);
+ Point inSize = mCropView.getSourceDimensions();
+ Point outSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
+ getWindowManager());
+ RectF crop = Utils.getMaxCropRect(
+ inSize.x, inSize.y, outSize.x, outSize.y, false);
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ // Passing 0, 0 will cause launcher to revert to using the
+ // default wallpaper size
+ updateWallpaperDimensions(0, 0);
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ BitmapCropTask cropTask = new BitmapCropTask(getContext(), res, resId,
+ crop, rotation, outSize.x, outSize.y, true, isLockScreenPicker, false, onEndCrop);
+ cropTask.execute();
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ protected void cropImageAndSetWallpaper(Uri uri,
+ BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler,
+ boolean isLockScreenPicker, final boolean finishActivityWhenDone) {
+ boolean centerCrop = getResources().getBoolean(R.bool.center_crop);
+ // Get the crop
+ boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+
+ Display d = getWindowManager().getDefaultDisplay();
+
+ Point displaySize = new Point();
+ d.getSize(displaySize);
+ boolean isPortrait = displaySize.x < displaySize.y;
+
+ Point defaultWallpaperSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
+ getWindowManager());
+ // Get the crop
+ RectF cropRect = mCropView.getCrop();
+
+ Point inSize = mCropView.getSourceDimensions();
+
+ int cropRotation = mCropView.getImageRotation();
+ float cropScale = mCropView.getWidth() / (float) cropRect.width();
+
+
+ Matrix rotateMatrix = new Matrix();
+ rotateMatrix.setRotate(cropRotation);
+ float[] rotatedInSize = new float[] { inSize.x, inSize.y };
+ rotateMatrix.mapPoints(rotatedInSize);
+ rotatedInSize[0] = Math.abs(rotatedInSize[0]);
+ rotatedInSize[1] = Math.abs(rotatedInSize[1]);
+
+
+ // due to rounding errors in the cropview renderer the edges can be slightly offset
+ // therefore we ensure that the boundaries are sanely defined
+ cropRect.left = Math.max(0, cropRect.left);
+ cropRect.right = Math.min(rotatedInSize[0], cropRect.right);
+ cropRect.top = Math.max(0, cropRect.top);
+ cropRect.bottom = Math.min(rotatedInSize[1], cropRect.bottom);
+
+ // ADJUST CROP WIDTH
+ // Extend the crop all the way to the right, for parallax
+ // (or all the way to the left, in RTL)
+ float extraSpace;
+ if (centerCrop) {
+ extraSpace = 2f * Math.min(rotatedInSize[0] - cropRect.right, cropRect.left);
+ } else {
+ extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
+ }
+ // Cap the amount of extra width
+ float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width();
+ extraSpace = Math.min(extraSpace, maxExtraSpace);
+
+ if (centerCrop) {
+ cropRect.left -= extraSpace / 2f;
+ cropRect.right += extraSpace / 2f;
+ } else {
+ if (ltr) {
+ cropRect.right += extraSpace;
+ } else {
+ cropRect.left -= extraSpace;
+ }
+ }
+
+ // ADJUST CROP HEIGHT
+ if (isPortrait) {
+ cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale;
+ } else { // LANDSCAPE
+ float extraPortraitHeight =
+ defaultWallpaperSize.y / cropScale - cropRect.height();
+ float expandHeight =
+ Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
+ extraPortraitHeight / 2);
+ cropRect.top -= expandHeight;
+ cropRect.bottom += expandHeight;
+ }
+ final int outWidth = (int) Math.round(cropRect.width() * cropScale);
+ final int outHeight = (int) Math.round(cropRect.height() * cropScale);
+
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ updateWallpaperDimensions(outWidth, outHeight);
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ BitmapCropTask cropTask = new BitmapCropTask(getContext(), uri, cropRect, cropRotation,
+ outWidth, outHeight, true, isLockScreenPicker, false, onEndCrop);
+ if (onBitmapCroppedHandler != null) {
+ cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
+ }
+ cropTask.execute();
+ }
+
+ protected void cropImageAndSetWallpaper(String path, String packageName,
+ boolean isLockScreenPicker, final boolean finishActivityWhenDone) {
+ // crop this image and scale it down to the default wallpaper size for
+ // this device
+ Point inSize = mCropView.getSourceDimensions();
+ Point outSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
+ getWindowManager());
+ RectF cropRect = Utils.getMaxCropRect(
+ inSize.x, inSize.y, outSize.x, outSize.y, false);
+
+ final Resources res;
+ try {
+ res = getPackageManager().getResourcesForApplication(packageName);
+ if (res == null) {
+ return;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ BitmapCropTask cropTask = new BitmapCropTask(getContext(), res, path, cropRect,
+ 0, outSize.x, outSize.y, true, isLockScreenPicker, false, onEndCrop);
+ if (cropTask != null) {
+ cropTask.execute();
+ }
+ }
+
+ protected void updateWallpaperDimensions(int width, int height) {
+ String spKey = PickerFiles.WALLPAPER_CROP_PREFERENCES_KEY;
+ SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
+ SharedPreferences.Editor editor = sp.edit();
+ if (width != 0 && height != 0) {
+ editor.putInt(WALLPAPER_WIDTH_KEY, width);
+ editor.putInt(WALLPAPER_HEIGHT_KEY, height);
+ } else {
+ editor.remove(WALLPAPER_WIDTH_KEY);
+ editor.remove(WALLPAPER_HEIGHT_KEY);
+ }
+ editor.commit();
+ WallpaperUtils.suggestWallpaperDimension(getResources(),
+ sp, getWindowManager(), WallpaperManager.getInstance(getContext()), true);
+ }
+
+ private boolean hasStoragePermissions() {
+ return checkCallingOrSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) ==
+ PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void requestStoragePermissions() {
+ requestPermissions(new String[] {android.Manifest.permission.READ_EXTERNAL_STORAGE},
+ REQUEST_CODE_STORAGE_PERMISSION_CHECK);
+ }
+
+ static class LoadRequest {
+ BitmapSource src;
+ boolean touchEnabled;
+ boolean moveToLeft;
+ Runnable postExecute;
+ CropViewScaleProvider scaleProvider;
+
+ TileSource result;
+ }
+
+ interface CropViewScaleProvider {
+ float getScale(TileSource src);
+ }
+}
diff --git a/src/org/cyanogenmod/wallpaperpicker/WallpaperPickerActivity.java b/src/org/cyanogenmod/wallpaperpicker/WallpaperPickerActivity.java
new file mode 100644
index 0000000..a5c1d03
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/WallpaperPickerActivity.java
@@ -0,0 +1,1410 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpaperpicker;
+
+import android.animation.LayoutTransition;
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.ContentResolver;
+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.Point;
+import android.graphics.PorterDuff;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.Manifest;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+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.MotionEvent;
+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.WindowManager;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ArrayAdapter;
+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.gallery3d.common.BitmapCropTask;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+import cyanogenmod.providers.ThemesContract;
+
+import org.cyanogenmod.wallpaperpicker.util.Thunk;
+import org.cyanogenmod.wallpaperpicker.util.WallpaperUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.cyanogenmod.wallpaperpicker.util.Constants.THEME_LOCKSCREEN_PATH;
+import static org.cyanogenmod.wallpaperpicker.util.Constants.THEME_WALLPAPER_PATH;
+
+public class WallpaperPickerActivity extends WallpaperCropActivity {
+ static final String TAG = "WallpaperPickerActivity";
+
+ public static final int IMAGE_PICK = 5;
+ public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
+ private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
+ private static final String SELECTED_INDEX = "SELECTED_INDEX";
+ private static final int FLAG_POST_DELAY_MILLIS = 200;
+ private static final String ACTION_SET_KEYGUARD_WALLPAPER =
+ "android.intent.action.SET_KEYGUARD_WALLPAPER";
+
+ @Thunk
+ View mSelectedTile;
+ @Thunk boolean mIgnoreNextTap;
+ @Thunk OnClickListener mThumbnailOnClickListener;
+
+ @Thunk LinearLayout mWallpapersView;
+ @Thunk HorizontalScrollView mWallpaperScrollContainer;
+ @Thunk View mWallpaperStrip;
+
+ @Thunk ActionMode.Callback mActionModeCallback;
+ @Thunk ActionMode mActionMode;
+
+ @Thunk View.OnLongClickListener mLongClickListener;
+
+ ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
+ private SavedWallpaperImages mSavedImages;
+ @Thunk int mSelectedIndex = -1;
+
+ private boolean mIsLockScreenPicker;
+
+ public static abstract class WallpaperTileInfo {
+ protected View mView;
+ protected boolean mIsLockScreenWallpaper;
+ public Drawable mThumb;
+
+ 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;
+ public UriWallpaperInfo(Uri uri, boolean isLockScreenWallpaper) {
+ mUri = uri;
+ mIsLockScreenWallpaper = isLockScreenWallpaper;
+ }
+ @Override
+ public void onClick(final WallpaperPickerActivity a) {
+ a.setWallpaperButtonEnabled(false);
+ final BitmapRegionTileSource.UriBitmapSource bitmapSource =
+ new BitmapRegionTileSource.UriBitmapSource(a.getContext(), mUri);
+ a.setCropViewTileSource(bitmapSource, true, false, null, new Runnable() {
+
+ @Override
+ public void run() {
+ if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+ a.selectTile(mView);
+ a.setWallpaperButtonEnabled(true);
+ } else {
+ ViewGroup parent = (ViewGroup) mView.getParent();
+ if (parent != null) {
+ parent.removeView(mView);
+ Toast.makeText(a.getContext(), R.string.image_load_fail,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+ });
+ }
+ @Override
+ public void onSave(final WallpaperPickerActivity a) {
+ boolean finishActivityWhenDone = true;
+ BitmapCropTask.OnBitmapCroppedHandler h = new BitmapCropTask.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, mIsLockScreenWallpaper, finishActivityWhenDone);
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ public static class FileWallpaperInfo extends WallpaperTileInfo {
+ private File mFile;
+
+ public FileWallpaperInfo(File target, Drawable thumb, boolean isLockScreenWallpaper) {
+ mFile = target;
+ mThumb = thumb;
+ mIsLockScreenWallpaper = isLockScreenWallpaper;
+ }
+ @Override
+ public void onClick(final WallpaperPickerActivity a) {
+ a.setWallpaperButtonEnabled(false);
+ final BitmapRegionTileSource.UriBitmapSource bitmapSource =
+ new BitmapRegionTileSource.UriBitmapSource(a.getContext(), Uri.fromFile(mFile));
+ a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
+
+ @Override
+ public void run() {
+ if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+ a.setWallpaperButtonEnabled(true);
+ }
+ }
+ });
+ }
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ a.setWallpaper(Uri.fromFile(mFile), mIsLockScreenWallpaper, true);
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ public static class ResourceWallpaperInfo extends WallpaperTileInfo {
+ private Resources mResources;
+ private int mResId;
+
+ public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb,
+ boolean isLockScreenWallpaper) {
+ mResources = res;
+ mResId = resId;
+ mThumb = thumb;
+ mIsLockScreenWallpaper = isLockScreenWallpaper;
+ }
+ @Override
+ public void onClick(final WallpaperPickerActivity a) {
+ a.setWallpaperButtonEnabled(false);
+ final BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
+ new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId);
+ a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
+
+ @Override
+ public float getScale(TileSource src) {
+ Point wallpaperSize = WallpaperUtils.getDefaultWallpaperSize(
+ a.getResources(), a.getWindowManager());
+ RectF crop = Utils.getMaxCropRect(
+ src.getImageWidth(), src.getImageHeight(),
+ wallpaperSize.x, wallpaperSize.y, false);
+ return wallpaperSize.x / crop.width();
+ }
+ }, new Runnable() {
+
+ @Override
+ public void run() {
+ if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
+ a.setWallpaperButtonEnabled(true);
+ }
+ }
+ });
+ }
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ boolean finishActivityWhenDone = true;
+ a.cropImageAndSetWallpaper(mResources, mResId, mIsLockScreenWallpaper,
+ finishActivityWhenDone);
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static class DefaultWallpaperInfo extends WallpaperTileInfo {
+ public DefaultWallpaperInfo(Drawable thumb) {
+ mThumb = thumb;
+ mIsLockScreenWallpaper = false;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ CropView c = a.getCropView();
+ Drawable defaultWallpaper = WallpaperManager.getInstance(a.getContext())
+ .getBuiltInDrawable(c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
+ if (defaultWallpaper == null) {
+ Log.w(TAG, "Null default wallpaper encountered.");
+ c.setTileSource(null, null);
+ return;
+ }
+
+ LoadRequest req = new LoadRequest();
+ req.moveToLeft = false;
+ req.touchEnabled = false;
+ req.scaleProvider = new CropViewScaleProvider() {
+
+ @Override
+ public float getScale(TileSource src) {
+ return 1f;
+ }
+ };
+ req.result = new DrawableTileSource(a.getContext(),
+ defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE);
+ a.onLoadRequestComplete(req, true);
+ }
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ try {
+ WallpaperManager.getInstance(a.getContext()).clear();
+ a.setResult(Activity.RESULT_OK);
+ } 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 false;
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ public static class NoWallpaperInfo extends WallpaperTileInfo {
+ public NoWallpaperInfo(Drawable thumb, boolean isLockScreenWallpaper) {
+ mThumb = thumb;
+ mIsLockScreenWallpaper = isLockScreenWallpaper;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ LoadRequest req = new LoadRequest();
+ req.moveToLeft = false;
+ req.touchEnabled = false;
+ req.scaleProvider = new CropViewScaleProvider() {
+
+ @Override
+ public float getScale(TileSource src) {
+ return 1f;
+ }
+ };
+ req.result = new DrawableTileSource(a.getContext(),
+ mThumb, DrawableTileSource.MAX_PREVIEW_SIZE);
+ a.onLoadRequestComplete(req, true);
+ }
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ try {
+ if (!mIsLockScreenWallpaper) {
+ BitmapDrawable bd = (BitmapDrawable) mThumb;
+ WallpaperManager.getInstance(a.getContext()).setBitmap(bd.getBitmap());
+ } else {
+ WallpaperManager.getInstance(a.getContext()).clearKeyguardWallpaper();
+ }
+ a.setResult(Activity.RESULT_OK);
+ } 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;
+ }
+ }
+
+ /**
+ * For themes which have regular wallpapers
+ */
+ @TargetApi(Build.VERSION_CODES.M)
+ public static class ThemeWallpaperInfo extends WallpaperTileInfo {
+ String mPackageName;
+ Drawable mThumb;
+ Context mContext;
+
+ public ThemeWallpaperInfo(Context context, String packageName, Drawable thumb,
+ boolean isLockScreenWallpaper) {
+ this.mContext = context;
+ this.mPackageName = packageName;
+ this.mThumb = thumb;
+ mIsLockScreenWallpaper = isLockScreenWallpaper;
+ }
+
+ @Override
+ public void onClick(final WallpaperPickerActivity a) {
+ try {
+ final BitmapRegionTileSource.ThemeBitmapSource source;
+ Resources res = a.getPackageManager().getResourcesForApplication(mPackageName);
+ if (res == null) {
+ return;
+ }
+
+ int rotation = 0;
+ source = new BitmapRegionTileSource.ThemeBitmapSource(res.getAssets(),
+ mIsLockScreenWallpaper ? THEME_LOCKSCREEN_PATH : THEME_WALLPAPER_PATH);
+ a.setCropViewTileSource(source, false, false, new CropViewScaleProvider() {
+
+ @Override
+ public float getScale(TileSource src) {
+ Point wallpaperSize = WallpaperUtils.getDefaultWallpaperSize(
+ a.getResources(), a.getWindowManager());
+ RectF crop = Utils.getMaxCropRect(
+ src.getImageWidth(), src.getImageHeight(),
+ wallpaperSize.x, wallpaperSize.y, false);
+ return wallpaperSize.x / crop.width();
+ }
+ }, new Runnable() {
+
+ @Override
+ public void run() {
+ if (source.getLoadingState() == BitmapSource.State.LOADED) {
+ a.setWallpaperButtonEnabled(true);
+ }
+ }
+ });
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ a.cropImageAndSetWallpaper(
+ mIsLockScreenWallpaper ? THEME_LOCKSCREEN_PATH : THEME_WALLPAPER_PATH,
+ mPackageName,
+ mIsLockScreenWallpaper,
+ true);
+ }
+
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ }
+
+ /**
+ * shows the system wallpaper behind the window and hides the {@link
+ * #mCropView} if visible
+ * @param visible should the system wallpaper be shown
+ */
+ protected void setSystemWallpaperVisiblity(final boolean visible) {
+ // hide our own wallpaper preview if necessary
+ if(!visible) {
+ mCropView.setVisibility(View.VISIBLE);
+ } else {
+ changeWallpaperFlags(visible);
+ }
+ // the change of the flag must be delayed in order to avoid flickering,
+ // a simple post / double post does not suffice here
+ mCropView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if(!visible) {
+ changeWallpaperFlags(visible);
+ } else {
+ mCropView.setVisibility(View.INVISIBLE);
+ }
+ }
+ }, FLAG_POST_DELAY_MILLIS);
+ }
+
+ @Thunk void changeWallpaperFlags(boolean visible) {
+ int desiredWallpaperFlag = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
+ int currentWallpaperFlag = getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ if (desiredWallpaperFlag != currentWallpaperFlag) {
+ getWindow().setFlags(desiredWallpaperFlag,
+ WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
+ }
+ }
+
+ @Override
+ protected void onLoadRequestComplete(LoadRequest req, boolean success) {
+ super.onLoadRequestComplete(req, success);
+ if (success) {
+ setSystemWallpaperVisiblity(false);
+ }
+ }
+
+ // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
+ protected void init() {
+ setContentView(R.layout.wallpaper_picker);
+
+ mIsLockScreenPicker = ACTION_SET_KEYGUARD_WALLPAPER.equals(getIntent().getAction());
+
+ mCropView = (CropView) findViewById(R.id.cropView);
+ mProgressView = findViewById(R.id.loading);
+ mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
+ 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();
+ }
+ }
+ });
+
+ if (mIsLockScreenPicker) {
+ final Bitmap keyguardBmp = WallpaperManager.getInstance(this).getKeyguardBitmap();
+ setCropViewTileSource(new BitmapRegionTileSource.DumbBitmapSource(keyguardBmp), false,
+ true, null, null);
+ } else {
+ mCropView.setVisibility(View.INVISIBLE);
+ }
+
+ 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;
+ }
+ setWallpaperButtonEnabled(true);
+ 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;
+ }
+ };
+
+ mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
+
+ // Populate the built-in wallpapers
+ ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers();
+ mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
+ SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(getContext(), wallpapers);
+ populateWallpapersFromAdapter(mWallpapersView, ia, false);
+
+ // Populate the saved wallpapers
+ mSavedImages = new SavedWallpaperImages(getContext(), mIsLockScreenPicker);
+ mSavedImages.loadThumbnailsAndImageIdList();
+ populateWallpapersFromAdapter(mWallpapersView, mSavedImages, false);
+
+ // theme wallpapers
+ new AsyncTask<Void, Void, ArrayList<ThemeWallpaperInfo>>() {
+ @Override
+ protected ArrayList<ThemeWallpaperInfo> doInBackground(Void... params) {
+ return findThemeWallpapers();
+ }
+
+ @Override
+ protected void onPostExecute(ArrayList<ThemeWallpaperInfo> themeWallpaperInfos) {
+ LinearLayout themeList = (LinearLayout) findViewById(R.id.theme_wallpaper_list);
+ ThemeWallpapersAdapter ta = new ThemeWallpapersAdapter(
+ WallpaperPickerActivity.this, themeWallpaperInfos);
+ populateWallpapersFromAdapter(themeList, ta, false);
+ }
+ }.execute((Void) null);
+
+ // Add a tile for the no wallpaper option
+ LinearLayout themeList = (LinearLayout) findViewById(R.id.theme_wallpaper_list);
+ FrameLayout noWallpaperTile = (FrameLayout) getLayoutInflater().
+ inflate(R.layout.wallpaper_picker_no_wallpaper_item, themeList, false);
+ themeList.addView(noWallpaperTile);
+ NoWallpaperInfo noWallpaperInfo = new NoWallpaperInfo(getDrawable(R.drawable.black),
+ mIsLockScreenPicker);
+ noWallpaperTile.setTag(noWallpaperInfo);
+ noWallpaperInfo.setView(noWallpaperTile);
+ noWallpaperTile.setOnClickListener(mThumbnailOnClickListener);
+
+ if (!mIsLockScreenPicker) {
+ // Populate the live wallpapers
+ final LinearLayout liveWallpapersView =
+ (LinearLayout) findViewById(R.id.live_wallpaper_list);
+ final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(getContext());
+ 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(getContext());
+ 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);
+ 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(lastPhoto);
+ 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);
+
+ // 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 (mSelectedIndex >= 0 && mSelectedIndex < mWallpapersView.getChildCount()) {
+ mThumbnailOnClickListener.onClick(
+ mWallpapersView.getChildAt(mSelectedIndex));
+ setSystemWallpaperVisiblity(false);
+ }
+ 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) {
+ // Ensure that a tile is slelected and loaded.
+ if (mSelectedTile != null && mCropView.getTileSource() != null) {
+ // Prevent user from selecting any new tile.
+ mWallpaperStrip.setVisibility(View.GONE);
+ actionBar.hide();
+
+ WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
+ info.onSave(WallpaperPickerActivity.this);
+ } else {
+ // no tile was selected, so we just finish the activity and go back
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ });
+ mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
+
+ // 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>();
+ boolean selectedTileRemoved = false;
+ 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);
+ if (i == mSelectedIndex) {
+ selectedTileRemoved = true;
+ }
+ }
+ }
+ for (View v : viewsToRemove) {
+ mWallpapersView.removeView(v);
+ }
+ if (selectedTileRemoved) {
+ mSelectedIndex = -1;
+ mSelectedTile = null;
+ setSystemWallpaperVisiblity(true);
+ }
+ 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);
+ }
+ if (mSelectedTile != null) {
+ mSelectedTile.setSelected(true);
+ }
+ mActionMode = null;
+ }
+ };
+ }
+
+ public void setWallpaperButtonEnabled(boolean enabled) {
+ mSetWallpaperButton.setEnabled(enabled);
+ }
+
+ @Thunk void selectTile(View v) {
+ if (mSelectedTile != null) {
+ mSelectedTile.setSelected(false);
+ mSelectedTile = null;
+ }
+ mSelectedTile = v;
+ v.setSelected(true);
+ mSelectedIndex = mWallpapersView.indexOfChild(v);
+ // TODO: Remove this once the accessibility framework and
+ // services have better support for selection state.
+ v.announceForAccessibility(
+ getContext().getString(R.string.announce_selection, v.getContentDescription()));
+ }
+
+ @Thunk void initializeScrollForRtl() {
+ if (Utilities.isRtl(getResources())) {
+ final ViewTreeObserver observer = mWallpaperScrollContainer.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ public void onGlobalLayout() {
+ LinearLayout masterWallpaperList =
+ (LinearLayout) findViewById(R.id.master_wallpaper_list);
+ mWallpaperScrollContainer.scrollTo(masterWallpaperList.getWidth(), 0);
+ mWallpaperScrollContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
+ }
+ }
+
+ protected Bitmap getThumbnailOfLastPhoto() {
+ boolean canReadExternalStorage = getActivity().checkPermission(
+ Manifest.permission.READ_EXTERNAL_STORAGE, Process.myPid(), Process.myUid()) ==
+ PackageManager.PERMISSION_GRANTED;
+
+ if (!canReadExternalStorage) {
+ // MediaStore.Images.Media.EXTERNAL_CONTENT_URI requires
+ // the READ_EXTERNAL_STORAGE permission
+ return null;
+ }
+
+ Cursor cursor = MediaStore.Images.Media.query(getContext().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.moveToNext()) {
+ int id = cursor.getInt(0);
+ thumb = MediaStore.Images.Thumbnails.getThumbnail(getContext().getContentResolver(),
+ id, MediaStore.Images.Thumbnails.MINI_KIND, null);
+ }
+ cursor.close();
+ }
+ return thumb;
+ }
+
+ public void onStop() {
+ super.onStop();
+ mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+ if (mWallpaperStrip != null && mWallpaperStrip.getAlpha() < 1f) {
+ mWallpaperStrip.setAlpha(1f);
+ mWallpaperStrip.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
+ outState.putInt(SELECTED_INDEX, mSelectedIndex);
+ }
+
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES);
+ for (Uri uri : uris) {
+ addTemporaryWallpaperTile(uri, true);
+ }
+ mSelectedIndex = savedInstanceState.getInt(SELECTED_INDEX, -1);
+ }
+
+ @Thunk 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);
+ }
+ }
+
+ @Thunk 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);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Thunk static Point getDefaultThumbnailSize(Resources res) {
+ return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
+ res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
+
+ }
+
+ @Thunk 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, false, true, null);
+ } else if (imageBytes != null) {
+ cropTask = new BitmapCropTask(
+ imageBytes, null, rotation, width, height, false, false, true, null);
+ } else {
+ cropTask = new BitmapCropTask(
+ context, res, resId, null, rotation, width, height, false, 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 = Utils.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, boolean fromRestore) {
+ 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);
+ 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 = getContext();
+ new AsyncTask<Void, Bitmap, Bitmap>() {
+ protected Bitmap doInBackground(Void...args) {
+ try {
+ int rotation = BitmapUtils.getRotationFromExif(context, uri);
+ return createThumbnail(defaultSize, context, uri, null, null, 0, rotation, false);
+ } catch (SecurityException securityException) {
+ if (isActivityDestroyed()) {
+ // Temporarily granted permissions are revoked when the activity
+ // finishes, potentially resulting in a SecurityException here.
+ // Even though {@link #isDestroyed} might also return true in different
+ // situations where the configuration changes, we are fine with
+ // catching these cases here as well.
+ cancel(false);
+ } else {
+ // otherwise it had a different cause and we throw it further
+ throw securityException;
+ }
+ return null;
+ }
+ }
+ protected void onPostExecute(Bitmap thumb) {
+ if (!isCancelled() && thumb != null) {
+ image.setImageBitmap(thumb);
+ Drawable thumbDrawable = image.getDrawable();
+ thumbDrawable.setDither(true);
+ pickedImageThumbnail.setVisibility(View.VISIBLE);
+ } else {
+ Log.e(TAG, "Error loading thumbnail for uri=" + uri);
+ }
+ }
+ }.execute();
+
+ UriWallpaperInfo info = new UriWallpaperInfo(uri, mIsLockScreenPicker);
+ pickedImageThumbnail.setTag(info);
+ info.setView(pickedImageThumbnail);
+ addLongPressHandler(pickedImageThumbnail);
+ updateTileIndices();
+ pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
+ if (!fromRestore) {
+ mThumbnailOnClickListener.onClick(pickedImageThumbnail);
+ }
+ }
+
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == IMAGE_PICK && resultCode == Activity.RESULT_OK) {
+ if (data != null && data.getData() != null) {
+ Uri uri = data.getData();
+ addTemporaryWallpaperTile(uri, false);
+ }
+ } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY
+ && resultCode == Activity.RESULT_OK) {
+ // Something was set on the third-party activity.
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+
+ private void addLongPressHandler(View v) {
+ v.setOnLongClickListener(mLongClickListener);
+
+ // Enable stylus button to also trigger long click.
+ final StylusEventHelper stylusEventHelper = new StylusEventHelper(v);
+ v.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ return stylusEventHelper.checkAndPerformStylusEvent(event);
+ }
+ });
+ }
+
+ private ArrayList<WallpaperTileInfo> findBundledWallpapers() {
+ final PackageManager pm = getContext().getPackageManager();
+ final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24);
+
+ List<Partner> partners = Partner.getAllPartners(pm);
+ boolean hideDefault = false;
+ if (partners != null) {
+ for (Partner partner : partners) {
+ final Resources partnerRes = partner.getResources();
+ final int resId = partnerRes.getIdentifier(Partner.RES_WALLPAPERS, "array",
+ partner.getPackageName());
+ if (resId != 0) {
+ addWallpapers(bundled, partnerRes, partner.getPackageName(), resId);
+ }
+
+ // Add system wallpapers
+ File systemDir = partner.getWallpaperDirectory();
+ if (systemDir != null && systemDir.isDirectory()) {
+ for (File file : systemDir.listFiles()) {
+ if (!file.isFile()) {
+ continue;
+ }
+ String name = file.getName();
+ int dotPos = name.lastIndexOf('.');
+ String extension = "";
+ if (dotPos >= -1) {
+ extension = name.substring(dotPos);
+ name = name.substring(0, dotPos);
+ }
+
+ if (name.endsWith("_small")) {
+ // it is a thumbnail
+ continue;
+ }
+
+ File thumbnail = new File(systemDir, name + "_small" + extension);
+ Bitmap thumb = BitmapFactory.decodeFile(thumbnail.getAbsolutePath());
+ if (thumb != null) {
+ bundled.add(new FileWallpaperInfo(file, new BitmapDrawable(thumb),
+ mIsLockScreenPicker));
+ }
+ }
+ }
+ if (partner.hideDefaultWallpaper()) {
+ hideDefault = true;
+ }
+ }
+ }
+
+ Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
+ if (r != null) {
+ try {
+ Resources wallpaperRes = getContext().getPackageManager()
+ .getResourcesForApplication(r.first);
+ addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+
+ if (!hideDefault && !mIsLockScreenPicker) {
+ // Add an entry for the default wallpaper (stored in system resources)
+ WallpaperTileInfo defaultWallpaperInfo = Utilities.ATLEAST_KITKAT
+ ? getDefaultWallpaper() : getPreKKDefaultWallpaperInfo();
+ if (defaultWallpaperInfo != null) {
+ bundled.add(0, defaultWallpaperInfo);
+ }
+ }
+
+ return bundled;
+ }
+
+ private ArrayList<ThemeWallpaperInfo> findThemeWallpapers() {
+ ArrayList<ThemeWallpaperInfo> themeWallpapers =
+ new ArrayList<ThemeWallpaperInfo>();
+ ContentResolver cr = getContentResolver();
+ String[] projection = {ThemesContract.ThemesColumns.PKG_NAME};
+ String selection = (mIsLockScreenPicker
+ ? ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN
+ : ThemesContract.ThemesColumns.MODIFIES_LAUNCHER) + "=?";
+ String[] selectionArgs = {"1"};
+ String sortOrder = null;
+ Cursor c = cr.query(ThemesContract.ThemesColumns.CONTENT_URI, projection, selection,
+ selectionArgs, sortOrder);
+ if (c != null) {
+ Bitmap bmp;
+ while (c.moveToNext()) {
+ String pkgName = c.getString(
+ c.getColumnIndexOrThrow(ThemesContract.ThemesColumns.PKG_NAME));
+ bmp = Utilities.getThemeWallpaper(this,
+ mIsLockScreenPicker ? THEME_LOCKSCREEN_PATH: THEME_WALLPAPER_PATH,
+ pkgName, true /* thumb*/);
+ if (bmp != null) {
+ themeWallpapers.add(
+ new ThemeWallpaperInfo(this, pkgName,
+ new BitmapDrawable(getResources(), bmp), mIsLockScreenPicker));
+
+ Log.d("", String.format("Loaded bitmap of size %dx%d for %s",
+ bmp.getWidth(), bmp.getHeight(), pkgName));
+ }
+ }
+ c.close();
+ }
+ return themeWallpapers;
+ }
+
+ private boolean writeImageToFileAsJpeg(File f, Bitmap b) {
+ try {
+ f.createNewFile();
+ FileOutputStream thumbFileStream =
+ getContext().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 File getDefaultThumbFile() {
+ return new File(getContext().getFilesDir(), Build.VERSION.SDK_INT
+ + "_" + PickerFiles.DEFAULT_WALLPAPER_THUMBNAIL);
+ }
+
+ private boolean saveDefaultWallpaperThumb(Bitmap b) {
+ // Delete old thumbnails.
+ new File(getContext().getFilesDir(), PickerFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete();
+ new File(getContext().getFilesDir(), PickerFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+
+ for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) {
+ new File(getContext().getFilesDir(), i + "_"
+ + PickerFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+ }
+ return writeImageToFileAsJpeg(getDefaultThumbFile(), b);
+ }
+
+ private ResourceWallpaperInfo getPreKKDefaultWallpaperInfo() {
+ Resources sysRes = Resources.getSystem();
+ int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
+
+ File defaultThumbFile = getDefaultThumbFile();
+ 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 = BitmapUtils.getRotationFromExif(res, resId);
+ thumb = createThumbnail(
+ defaultThumbSize, getContext(), null, null, sysRes, resId, rotation, false);
+ if (thumb != null) {
+ defaultWallpaperExists = saveDefaultWallpaperThumb(thumb);
+ }
+ }
+ if (defaultWallpaperExists) {
+ return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(thumb),
+ mIsLockScreenPicker);
+ }
+ return null;
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private DefaultWallpaperInfo getDefaultWallpaper() {
+ File defaultThumbFile = getDefaultThumbFile();
+ Bitmap thumb = null;
+ boolean defaultWallpaperExists = false;
+ if (defaultThumbFile.exists()) {
+ thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+ defaultWallpaperExists = true;
+ } else {
+ Resources res = getResources();
+ Point defaultThumbSize = getDefaultThumbnailSize(res);
+ Drawable wallpaperDrawable = WallpaperManager.getInstance(getContext()).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 = saveDefaultWallpaperThumb(thumb);
+ }
+ }
+ if (defaultWallpaperExists) {
+ return new DefaultWallpaperInfo(new BitmapDrawable(thumb));
+ }
+ return null;
+ }
+
+ public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
+ final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
+ try {
+ ApplicationInfo info = getContext().getPackageManager()
+ .getApplicationInfo(packageName, 0);
+ return new Pair<>(info, R.array.wallpapers);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ private void addWallpapers(ArrayList<WallpaperTileInfo> known, Resources res,
+ String packageName, int listResId) {
+ 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),
+ mIsLockScreenPicker);
+ known.add(wallpaperInfo);
+ // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
+ }
+ } else {
+ Log.e(TAG, "Couldn't find wallpaper " + extra);
+ }
+ }
+ }
+
+ public CropView getCropView() {
+ return mCropView;
+ }
+
+ public SavedWallpaperImages getSavedImages() {
+ return mSavedImages;
+ }
+
+ private static class SimpleWallpapersAdapter extends ArrayAdapter<WallpaperTileInfo> {
+ private final LayoutInflater mLayoutInflater;
+
+ SimpleWallpapersAdapter(Context context, ArrayList<WallpaperTileInfo> wallpapers) {
+ super(context, R.layout.wallpaper_picker_item, wallpapers);
+ mLayoutInflater = LayoutInflater.from(context);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Drawable thumb = getItem(position).mThumb;
+ if (thumb == null) {
+ Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
+ }
+ return createImageTileView(mLayoutInflater, convertView, parent, thumb);
+ }
+ }
+
+ public static View createImageTileView(LayoutInflater layoutInflater,
+ View convertView, ViewGroup parent, Drawable thumb) {
+ View view;
+
+ if (convertView == null) {
+ view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
+
+ if (thumb != null) {
+ image.setImageDrawable(thumb);
+ thumb.setDither(true);
+ }
+
+ return view;
+ }
+
+ public void startActivityForResultSafely(Intent intent, int requestCode) {
+ Utilities.startActivityForResultSafely(getActivity(), intent, requestCode);
+ }
+
+ private static class ThemeWallpapersAdapter extends BaseAdapter implements ListAdapter {
+ private LayoutInflater mLayoutInflater;
+ private ArrayList<ThemeWallpaperInfo> mWallpapers;
+
+ ThemeWallpapersAdapter(Activity activity, ArrayList<ThemeWallpaperInfo> wallpapers) {
+ mLayoutInflater = activity.getLayoutInflater();
+ mWallpapers = wallpapers;
+ }
+
+ public int getCount() {
+ return mWallpapers.size();
+ }
+
+ public ThemeWallpaperInfo 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, convertView, parent, thumb);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/wallpaperpicker/base/BaseActivity.java b/src/org/cyanogenmod/wallpaperpicker/base/BaseActivity.java
new file mode 100644
index 0000000..670a61a
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/base/BaseActivity.java
@@ -0,0 +1,21 @@
+package org.cyanogenmod.wallpaperpicker.base;
+
+import android.app.Activity;
+import android.content.Context;
+
+/**
+ * A wrapper over {@link Activity} which allows to override some methods.
+ * The base implementation can change from an Activity to a Fragment (or any other custom
+ * implementation), Callers should not assume that the base class extends Context, instead use
+ * either {@link #getContext} or {@link #getActivity}
+ */
+public class BaseActivity extends Activity {
+
+ public Context getContext() {
+ return this;
+ }
+
+ public Activity getActivity() {
+ return this;
+ }
+}
diff --git a/src/org/cyanogenmod/wallpaperpicker/util/Constants.java b/src/org/cyanogenmod/wallpaperpicker/util/Constants.java
new file mode 100644
index 0000000..111fb46
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/util/Constants.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpaperpicker.util;
+
+public class Constants {
+
+ public static final String THEME_WALLPAPER_PATH = "wallpapers";
+ public static final String THEME_LOCKSCREEN_PATH = "lockscreen";
+
+}
diff --git a/src/org/cyanogenmod/wallpaperpicker/util/Thunk.java b/src/org/cyanogenmod/wallpaperpicker/util/Thunk.java
new file mode 100644
index 0000000..6f35514
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/util/Thunk.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpaperpicker.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that the given field or method has package visibility solely to prevent the creation
+ * of a synthetic method. In practice, you should treat this field/method as if it were private.
+ * <p>
+ *
+ * When a private method is called from an inner class, the Java compiler generates a simple
+ * package private shim method that the class generated from the inner class can call. This results
+ * in unnecessary bloat and runtime method call overhead. It also gets us closer to the dex method
+ * count limit.
+ * <p>
+ *
+ * If you'd like to see warnings for these synthetic methods in eclipse, turn on:
+ * Window > Preferences > Java > Compiler > Errors/Warnings > "Access to a non-accessible member
+ * of an enclosing type".
+ * <p>
+ *
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.TYPE})
+public @interface Thunk { } \ No newline at end of file
diff --git a/src/org/cyanogenmod/wallpaperpicker/util/WallpaperUtils.java b/src/org/cyanogenmod/wallpaperpicker/util/WallpaperUtils.java
new file mode 100644
index 0000000..ced3844
--- /dev/null
+++ b/src/org/cyanogenmod/wallpaperpicker/util/WallpaperUtils.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.cyanogenmod.wallpaperpicker.util;
+
+import android.annotation.TargetApi;
+import android.app.WallpaperManager;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Build;
+import android.view.WindowManager;
+
+import org.cyanogenmod.wallpaperpicker.Utilities;
+
+/**
+ * Utility methods for wallpaper management.
+ */
+public final class WallpaperUtils {
+
+ public static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
+ public static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
+ public static final float WALLPAPER_SCREENS_SPAN = 2f;
+
+ public static void suggestWallpaperDimension(Resources res,
+ final SharedPreferences sharedPrefs,
+ WindowManager windowManager,
+ final WallpaperManager wallpaperManager, boolean fallBackToDefaults) {
+ final Point defaultWallpaperSize = WallpaperUtils.getDefaultWallpaperSize(res, windowManager);
+ // If we have saved a wallpaper width/height, use that instead
+
+ int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, -1);
+ int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, -1);
+
+ if (savedWidth == -1 || savedHeight == -1) {
+ if (!fallBackToDefaults) {
+ return;
+ } else {
+ savedWidth = defaultWallpaperSize.x;
+ savedHeight = defaultWallpaperSize.y;
+ }
+ }
+
+ if (savedWidth != wallpaperManager.getDesiredMinimumWidth() ||
+ savedHeight != wallpaperManager.getDesiredMinimumHeight()) {
+ wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
+ }
+ }
+
+ /**
+ * As a ratio of screen height, the total distance we want the parallax effect to span
+ * horizontally
+ */
+ public 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;
+ }
+
+ private static Point sDefaultWallpaperSize;
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public static 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 (Utilities.ATLEAST_JB_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 (res.getConfiguration().smallestScreenWidthDp >= 720) {
+ 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;
+ }
+}