Refactoring wallpaper picker activity

  > Moving different tiles to individual classes
  > Moving some utility methods to corresponding tile classes
  > No functionality change

Change-Id: I493cf309f4e3d817a9300be004c475d208f8dadb
diff --git a/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java b/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java
deleted file mode 100644
index b53fce1..0000000
--- a/WallpaperPicker/src/com/android/launcher3/LiveWallpaperListAdapter.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.app.WallpaperInfo;
-import android.app.WallpaperManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.service.wallpaper.WallpaperService;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import com.android.launcher3.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/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
index 64b0ac4..9124e41 100644
--- a/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
+++ b/WallpaperPicker/src/com/android/launcher3/SavedWallpaperImages.java
@@ -26,29 +26,24 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
+
+import com.android.launcher3.wallpapertileinfo.FileWallpaperInfo;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.List;
 
+public class SavedWallpaperImages {
 
-public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
     private static String TAG = "Launcher3.SavedWallpaperImages";
-    private ImageDb mDb;
-    ArrayList<SavedWallpaperTile> mImages;
-    Context mContext;
-    LayoutInflater mLayoutInflater;
 
-    public static class SavedWallpaperTile extends WallpaperPickerActivity.FileWallpaperInfo {
+    public static class SavedWallpaperInfo extends FileWallpaperInfo {
+
         private int mDbId;
-        public SavedWallpaperTile(int dbId, File target, Drawable thumb) {
+
+        public SavedWallpaperInfo(int dbId, File target, Drawable thumb) {
             super(target, thumb);
             mDbId = dbId;
         }
@@ -59,19 +54,22 @@
         }
     }
 
+    private final ImageDb mDb;
+    private final Context mContext;
+
     public SavedWallpaperImages(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
         ImageDb.moveFromCacheDirectoryIfNecessary(context);
         mDb = new ImageDb(context);
         mContext = context;
-        mLayoutInflater = LayoutInflater.from(context);
     }
 
-    public void loadThumbnailsAndImageIdList() {
-        mImages = new ArrayList<SavedWallpaperTile>();
+    public List<SavedWallpaperInfo> loadThumbnailsAndImageIdList() {
+        List<SavedWallpaperInfo> result = new ArrayList<SavedWallpaperInfo>();
+
         SQLiteDatabase db = mDb.getReadableDatabase();
-        Cursor result = db.query(ImageDb.TABLE_NAME,
+        Cursor c = db.query(ImageDb.TABLE_NAME,
                 new String[] { ImageDb.COLUMN_ID,
                     ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
                     ImageDb.COLUMN_IMAGE_FILENAME}, // cols to return
@@ -82,43 +80,24 @@
                 ImageDb.COLUMN_ID + " DESC",
                 null);
 
-        while (result.moveToNext()) {
-            String filename = result.getString(1);
+        while (c.moveToNext()) {
+            String filename = c.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)));
+                result.add(new SavedWallpaperInfo(c.getInt(0),
+                        new File(mContext.getFilesDir(), c.getString(2)),
+                        new BitmapDrawable(mContext.getResources(), thumb)));
             }
         }
-        result.close();
+        c.close();
+        return result;
     }
 
-    public int getCount() {
-        return mImages.size();
-    }
+    public void deleteImage(int id) {
+        SQLiteDatabase db = mDb.getWritableDatabase();
 
-    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
@@ -128,24 +107,12 @@
                 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;
+        if (result.moveToFirst()) {
+            new File(mContext.getFilesDir(), result.getString(0)).delete();
+            new File(mContext.getFilesDir(), result.getString(1)).delete();
         }
-    }
+        result.close();
 
-    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[] {
@@ -177,20 +144,16 @@
         }
     }
 
-    static class ImageDb extends SQLiteOpenHelper {
+    private 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(LauncherFiles.WALLPAPER_IMAGES_DB).getPath(),
                     null, DB_VERSION);
-            // Store the context for later use
-            mContext = context;
         }
 
         public static void moveFromCacheDirectoryIfNecessary(Context context) {
diff --git a/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
deleted file mode 100644
index f46da53..0000000
--- a/WallpaperPicker/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import com.android.launcher3.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/WallpaperPicker/src/com/android/launcher3/ToggleOnTapCallback.java b/WallpaperPicker/src/com/android/launcher3/ToggleOnTapCallback.java
new file mode 100644
index 0000000..2bc48ee
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/ToggleOnTapCallback.java
@@ -0,0 +1,67 @@
+package com.android.launcher3;
+
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.launcher3.util.Thunk;
+
+/**
+ * Callback that toggles the visibility of the target view when crop view is tapped.
+ */
+public class ToggleOnTapCallback implements CropView.TouchCallback {
+
+    @Thunk final View mViewtoToggle;
+
+    private ViewPropertyAnimator mAnim;
+    private boolean mIgnoreNextTap;
+
+    public ToggleOnTapCallback(View viewtoHide) {
+        mViewtoToggle = viewtoHide;
+    }
+
+    @Override
+    public void onTouchDown() {
+        if (mAnim != null) {
+            mAnim.cancel();
+        }
+        if (mViewtoToggle.getAlpha() == 1f) {
+            mIgnoreNextTap = true;
+        }
+
+        mAnim = mViewtoToggle.animate();
+        mAnim.alpha(0f)
+            .setDuration(150)
+            .withEndAction(new Runnable() {
+                public void run() {
+                    mViewtoToggle.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();
+            }
+            mViewtoToggle.setVisibility(View.VISIBLE);
+            mAnim = mViewtoToggle.animate();
+            mAnim.alpha(1f)
+                 .setDuration(150)
+                 .setInterpolator(new DecelerateInterpolator(0.75f));
+            mAnim.start();
+        }
+    }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index eb47380..890d1ff 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -19,6 +19,7 @@
 import android.annotation.TargetApi;
 import android.app.ActionBar;
 import android.app.Activity;
+import android.app.WallpaperManager;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -26,6 +27,7 @@
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -116,8 +118,7 @@
                 new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
-                        boolean finishActivityWhenDone = true;
-                        cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
+                        cropImageAndSetWallpaper(imageUri, null);
                     }
                 });
         mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
@@ -215,7 +216,7 @@
     }
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    protected boolean isActivityDestroyed() {
+    public boolean isActivityDestroyed() {
         return Utilities.ATLEAST_JB_MR1 && isDestroyed();
     }
 
@@ -257,6 +258,7 @@
         mProgressView.setVisibility(View.GONE);
     }
 
+    @TargetApi(Build.VERSION_CODES.KITKAT)
     public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled,
             boolean moveToLeft, CropViewScaleProvider scaleProvider, Runnable postExecute) {
         final LoadRequest req = new LoadRequest();
@@ -266,6 +268,21 @@
         req.postExecute = postExecute;
         req.scaleProvider = scaleProvider;
         mCurrentLoadRequest = req;
+        if (bitmapSource == null) {
+            // Load the default wallpaper
+            Drawable defaultWallpaper = WallpaperManager.getInstance(this)
+                    .getBuiltInDrawable(mCropView.getWidth(), mCropView.getHeight(),
+                            false, 0.5f, 0.5f);
+            if (defaultWallpaper == null) {
+                Log.w(LOGTAG, "Null default wallpaper encountered.");
+                mCropView.setTileSource(null, null);
+                return;
+            }
+            req.result = new DrawableTileSource(this,
+                    defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE);
+            onLoadRequestComplete(req, true);
+            return;
+        }
 
         // Remove any pending requests
         mLoaderHandler.removeMessages(MSG_LOAD_IMAGE);
@@ -288,27 +305,17 @@
         return getResources().getBoolean(R.bool.allow_rotation);
     }
 
-    protected void setWallpaper(Uri uri, final boolean finishActivityWhenDone) {
+    public void setWallpaper(Uri uri) {
         int rotation = BitmapUtils.getRotationFromExif(getContext(), uri);
         BitmapCropTask cropTask = new BitmapCropTask(
                 getContext(), uri, null, rotation, 0, 0, true, false, null);
-        final Point bounds = cropTask.getImageBounds();
-        Runnable onEndCrop = new Runnable() {
-            public void run() {
-                WallpaperUtils.saveWallpaperDimensions(bounds.x, bounds.y, WallpaperCropActivity.this);
-                if (finishActivityWhenDone) {
-                    setResult(Activity.RESULT_OK);
-                    finish();
-                }
-            }
-        };
+        CropAndFinishRunnable onEndCrop = new CropAndFinishRunnable(cropTask.getImageBounds());
         cropTask.setOnEndRunnable(onEndCrop);
         cropTask.setNoCrop(true);
         cropTask.execute();
     }
 
-    protected void cropImageAndSetWallpaper(
-            Resources res, int resId, final boolean finishActivityWhenDone) {
+    public void cropImageAndSetWallpaper(Resources res, int resId) {
         // crop this image and scale it down to the default wallpaper size for
         // this device
         int rotation = BitmapUtils.getRotationFromExif(res, resId, this);
@@ -317,25 +324,17 @@
                 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
-                WallpaperUtils.saveWallpaperDimensions(0, 0, WallpaperCropActivity.this);
-                if (finishActivityWhenDone) {
-                    setResult(Activity.RESULT_OK);
-                    finish();
-                }
-            }
-        };
+        // Passing 0, 0 will cause launcher to revert to using the
+        // default wallpaper size
+        CropAndFinishRunnable onEndCrop = new CropAndFinishRunnable(new Point(0, 0));
         BitmapCropTask cropTask = new BitmapCropTask(getContext(), res, resId,
                 crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
         cropTask.execute();
     }
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    protected void cropImageAndSetWallpaper(Uri uri,
-            BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
+    public void cropImageAndSetWallpaper(Uri uri,
+            BitmapCropTask.OnBitmapCroppedHandler onBitmapCroppedHandler) {
         boolean centerCrop = getResources().getBoolean(R.bool.center_crop);
         // Get the crop
         boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
@@ -408,18 +407,11 @@
             cropRect.top -= expandHeight;
             cropRect.bottom += expandHeight;
         }
+
         final int outWidth = (int) Math.round(cropRect.width() * cropScale);
         final int outHeight = (int) Math.round(cropRect.height() * cropScale);
+        CropAndFinishRunnable onEndCrop = new CropAndFinishRunnable(new Point(outWidth, outHeight));
 
-        Runnable onEndCrop = new Runnable() {
-            public void run() {
-                WallpaperUtils.saveWallpaperDimensions(outWidth, outHeight, WallpaperCropActivity.this);
-                if (finishActivityWhenDone) {
-                    setResult(Activity.RESULT_OK);
-                    finish();
-                }
-            }
-        };
         BitmapCropTask cropTask = new BitmapCropTask(getContext(), uri,
                 cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
         if (onBitmapCroppedHandler != null) {
@@ -428,6 +420,22 @@
         cropTask.execute();
     }
 
+    private class CropAndFinishRunnable implements Runnable {
+        private final Point mBounds;
+
+        public CropAndFinishRunnable(Point bounds) {
+            mBounds = bounds;
+        }
+
+        @Override
+        public void run() {
+            WallpaperUtils.saveWallpaperDimensions(mBounds.x, mBounds.y,
+                    WallpaperCropActivity.this);
+            setResult(Activity.RESULT_OK);
+            finish();
+        }
+    }
+
     static class LoadRequest {
         BitmapSource src;
         boolean touchEnabled;
@@ -438,7 +446,7 @@
         TileSource result;
     }
 
-    interface CropViewScaleProvider {
+    public interface CropViewScaleProvider {
         float getScale(TileSource src);
     }
 }
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index f37ec2d..f44c88e 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -16,38 +16,21 @@
 
 package com.android.launcher3;
 
-import android.Manifest;
 import android.animation.LayoutTransition;
-import android.annotation.TargetApi;
+import android.annotation.SuppressLint;
 import android.app.ActionBar;
 import android.app.Activity;
-import android.app.WallpaperManager;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.DataSetObserver;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.RectF;
 import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
 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;
@@ -55,36 +38,28 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLayoutChangeListener;
+import android.view.View.OnLongClickListener;
 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.Toast;
-
-import com.android.gallery3d.common.BitmapCropTask;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.WallpaperUtils;
-import com.android.photos.BitmapRegionTileSource;
-import com.android.photos.BitmapRegionTileSource.BitmapSource;
-import com.android.photos.views.TiledImageRenderer.TileSource;
-
+import com.android.launcher3.wallpapertileinfo.DefaultWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.FileWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.LiveWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.PickImageInfo;
+import com.android.launcher3.wallpapertileinfo.ResourceWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.ThirdPartyWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.UriWallpaperInfo;
+import com.android.launcher3.wallpapertileinfo.WallpaperTileInfo;
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.util.ArrayList;
+import java.util.List;
 
-public class WallpaperPickerActivity extends WallpaperCropActivity {
+public class WallpaperPickerActivity extends WallpaperCropActivity
+        implements OnClickListener, OnLongClickListener, ActionMode.Callback {
     static final String TAG = "Launcher.WallpaperPickerActivity";
 
     public static final int IMAGE_PICK = 5;
@@ -94,241 +69,18 @@
     private static final int FLAG_POST_DELAY_MILLIS = 200;
 
     @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;
-
-    public static abstract class WallpaperTileInfo {
-        protected View mView;
-        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) {
-            mUri = uri;
-        }
-        @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, 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) {
-            mFile = target;
-            mThumb = thumb;
-        }
-        @Override
-        public void onClick(final WallpaperPickerActivity a) {
-            a.setWallpaperButtonEnabled(false);
-            final BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.FilePathBitmapSource(mFile.getAbsolutePath());
-            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), 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) {
-            mResources = res;
-            mResId = resId;
-            mThumb = thumb;
-        }
-        @Override
-        public void onClick(final WallpaperPickerActivity a) {
-            a.setWallpaperButtonEnabled(false);
-            final BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
-                    new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId, a);
-            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, 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;
-        }
-        @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 true;
-        }
-    }
-
     /**
-     * shows the system wallpaper behind the window and hides the {@link
-     * #mCropView} if visible
+     * 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) {
@@ -370,7 +122,9 @@
         }
     }
 
-    // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
+    /**
+     * called by onCreate; this is sub-classed to overwrite WallpaperCropActivity
+     */
     protected void init() {
         setContentView(R.layout.wallpaper_picker);
 
@@ -380,137 +134,37 @@
         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();
-                }
-            }
-        });
+        mCropView.setTouchCallback(new ToggleOnTapCallback(mWallpaperStrip));
 
-        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 saved wallpapers
+        mSavedImages = new SavedWallpaperImages(getContext());
+        populateWallpapers(mWallpapersView, mSavedImages.loadThumbnailsAndImageIdList(), true);
 
         // 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);
+        populateWallpapers(mWallpapersView, wallpapers, false);
 
-        // Populate the saved wallpapers
-        mSavedImages = new SavedWallpaperImages(getContext());
-        mSavedImages.loadThumbnailsAndImageIdList();
-        populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true);
+        // Load live wallpapers asynchronously
+        new LiveWallpaperInfo.LoaderTask(this) {
 
-        // 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);
+            @Override
+            protected void onPostExecute(List<LiveWallpaperInfo> result) {
+                populateWallpapers((LinearLayout) findViewById(R.id.live_wallpaper_list),
+                        result, false);
                 initializeScrollForRtl();
                 updateTileIndices();
             }
-        });
+        }.execute();
 
         // 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);
+        populateWallpapers((LinearLayout) findViewById(R.id.third_party_wallpaper_list),
+                ThirdPartyWallpaperInfo.getAll(this), 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);
+        masterWallpaperList.addView(
+                createTileView(masterWallpaperList, new PickImageInfo(), false), 0);
 
         // Select the first item; wait for a layout pass so that we initialize the dimensions of
         // cropView or the defaultWallpaperView first
@@ -520,8 +174,7 @@
                     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));
+                        onClick(mWallpapersView.getChildAt(mSelectedIndex));
                         setSystemWallpaperVisiblity(false);
                     }
                     v.removeOnLayoutChangeListener(this);
@@ -565,102 +218,54 @@
                     }
                 });
         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;
+    /**
+     * Called when a wallpaper tile is clicked
+     */
+    @Override
+    public void onClick(View v) {
+        if (mActionMode != null) {
+            // When CAB is up, clicking toggles the item instead
+            if (v.isLongClickable()) {
+                onLongClick(v);
             }
+            return;
+        }
+        setWallpaperButtonEnabled(true);
+        WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
+        if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
+            selectTile(v);
+        }
+        info.onClick(this);
+    }
 
-            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 when a view is long clicked
+     */
+    @Override
+    public boolean onLongClick(View v) {
+        CheckableFrameLayout c = (CheckableFrameLayout) v;
+        c.toggle();
 
-            // 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;
-                }
+        if (mActionMode != null) {
+            mActionMode.invalidate();
+        } else {
+            // Start the CAB using the ActionMode.Callback defined below
+            mActionMode = startActionMode(this);
+            int childCount = mWallpapersView.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                mWallpapersView.getChildAt(i).setSelected(false);
             }
-
-            // 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;
-            }
-        };
+        }
+        return true;
     }
 
     public void setWallpaperButtonEnabled(boolean enabled) {
         mSetWallpaperButton.setEnabled(enabled);
     }
 
-    @Thunk void selectTile(View v) {
+    public void selectTile(View v) {
         if (mSelectedTile != null) {
             mSelectedTile.setSelected(false);
             mSelectedTile = null;
@@ -688,35 +293,6 @@
         }
     }
 
-    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);
@@ -739,21 +315,6 @@
         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();
@@ -795,126 +356,71 @@
         }
     }
 
-    @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, true, null);
-        } else if (imageBytes != null) {
-            cropTask = new BitmapCropTask(
-                    imageBytes, null, rotation, width, height, false, true, null);
-        }  else {
-            cropTask = new BitmapCropTask(
-                    context, res, resId, null, rotation, width, height, false, true, null);
-        }
-        Point bounds = cropTask.getImageBounds();
-        if (bounds == null || bounds.x == 0 || bounds.y == 0) {
-            return null;
-        }
-
-        Matrix rotateMatrix = new Matrix();
-        rotateMatrix.setRotate(rotation);
-        float[] rotatedBounds = new float[] { bounds.x, bounds.y };
-        rotateMatrix.mapPoints(rotatedBounds);
-        rotatedBounds[0] = Math.abs(rotatedBounds[0]);
-        rotatedBounds[1] = Math.abs(rotatedBounds[1]);
-
-        RectF cropRect = 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) {
+
         // Add a tile for the image picked from Gallery, reusing the existing tile if there is one.
-        FrameLayout existingImageThumbnail = null;
+        View existingTile = null;
         int indexOfExistingTile = 0;
         for (; indexOfExistingTile < mWallpapersView.getChildCount(); indexOfExistingTile++) {
-            FrameLayout thumbnail = (FrameLayout) mWallpapersView.getChildAt(indexOfExistingTile);
+            View thumbnail = mWallpapersView.getChildAt(indexOfExistingTile);
             Object tag = thumbnail.getTag();
             if (tag instanceof UriWallpaperInfo && ((UriWallpaperInfo) tag).mUri.equals(uri)) {
-                existingImageThumbnail = thumbnail;
+                existingTile = thumbnail;
                 break;
             }
         }
-        final FrameLayout pickedImageThumbnail;
-        if (existingImageThumbnail != null) {
-            pickedImageThumbnail = existingImageThumbnail;
+        final View pickedImageThumbnail;
+        final UriWallpaperInfo info;
+        if (existingTile != null) {
+            pickedImageThumbnail = existingTile;
             // Always move the existing wallpaper to the front so user can see it without scrolling.
             mWallpapersView.removeViewAt(indexOfExistingTile);
-            mWallpapersView.addView(existingImageThumbnail, 0);
+            mWallpapersView.addView(pickedImageThumbnail, 0);
+            info = (UriWallpaperInfo) pickedImageThumbnail.getTag();
         } else {
             // This is the first time this temporary wallpaper has been added
-            pickedImageThumbnail = (FrameLayout) getLayoutInflater()
-                    .inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
-            pickedImageThumbnail.setVisibility(View.GONE);
+            info = new UriWallpaperInfo(uri);
+            pickedImageThumbnail = createTileView(mWallpapersView, info, true);
             mWallpapersView.addView(pickedImageThumbnail, 0);
             mTempWallpaperTiles.add(uri);
         }
+        info.loadThumbnaleAsync(this);
 
-        // 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);
-        pickedImageThumbnail.setTag(info);
-        info.setView(pickedImageThumbnail);
-        addLongPressHandler(pickedImageThumbnail);
         updateTileIndices();
-        pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
         if (!fromRestore) {
-            mThumbnailOnClickListener.onClick(pickedImageThumbnail);
+            onClick(existingTile);
         }
     }
 
+    @Thunk void populateWallpapers(ViewGroup parent, List<? extends WallpaperTileInfo> wallpapers,
+            boolean addLongPressHandler) {
+        for (WallpaperTileInfo info : wallpapers) {
+            parent.addView(createTileView(parent, info, addLongPressHandler));
+        }
+    }
+
+    private View createTileView(ViewGroup parent, WallpaperTileInfo info, boolean addLongPress) {
+        View view = info.createView(this, getLayoutInflater(), parent);
+        view.setTag(info);
+
+        if (addLongPress) {
+            view.setOnLongClickListener(this);
+
+            // Enable stylus button to also trigger long click.
+            final StylusEventHelper stylusEventHelper =
+                    new StylusEventHelper(new SimpleOnStylusPressListener(view), view);
+            view.setOnTouchListener(new View.OnTouchListener() {
+                @SuppressLint("ClickableViewAccessibility")
+                @Override
+                public boolean onTouch(View view, MotionEvent event) {
+                    return stylusEventHelper.onMotionEvent(event);
+                }
+            });
+        }
+        view.setOnClickListener(this);
+        return view;
+    }
+
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == IMAGE_PICK && resultCode == Activity.RESULT_OK) {
             if (data != null && data.getData() != null) {
@@ -929,20 +435,6 @@
         }
     }
 
-    private void addLongPressHandler(View v) {
-        v.setOnLongClickListener(mLongClickListener);
-
-        // Enable stylus button to also trigger long click.
-        final StylusEventHelper stylusEventHelper =
-                new StylusEventHelper(new SimpleOnStylusPressListener(v), v);
-        v.setOnTouchListener(new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View view, MotionEvent event) {
-                return stylusEventHelper.onMotionEvent(event);
-            }
-        });
-    }
-
     private ArrayList<WallpaperTileInfo> findBundledWallpapers() {
         final PackageManager pm = getContext().getPackageManager();
         final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24);
@@ -979,26 +471,28 @@
                     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)));
+                        bundled.add(new FileWallpaperInfo(
+                                file, new BitmapDrawable(getResources(), thumb)));
                     }
                 }
             }
         }
 
-        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) {
-            }
-        }
+        // Context.getPackageName() may return the "original" package name,
+        // com.android.launcher3; Resources needs the real package name,
+        // com.android.launcher3. So we ask Resources for what it thinks the
+        // package name should be.
+        try {
+            final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
+            ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
+            Resources wallpaperRes = getContext().getPackageManager()
+                    .getResourcesForApplication(info);
+            addWallpapers(bundled, wallpaperRes, info.packageName, R.array.wallpapers);
+        } catch (PackageManager.NameNotFoundException e) { }
 
         if (partner == null || !partner.hideDefaultWallpaper()) {
             // Add an entry for the default wallpaper (stored in system resources)
-            WallpaperTileInfo defaultWallpaperInfo = Utilities.ATLEAST_KITKAT
-                    ? getDefaultWallpaper() : getPreKKDefaultWallpaperInfo();
+            WallpaperTileInfo defaultWallpaperInfo = DefaultWallpaperInfo.get(this);
             if (defaultWallpaperInfo != null) {
                 bundled.add(0, defaultWallpaperInfo);
             }
@@ -1006,109 +500,6 @@
         return bundled;
     }
 
-    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
-                + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL);
-    }
-
-    private boolean saveDefaultWallpaperThumb(Bitmap b) {
-        // Delete old thumbnails.
-        new File(getContext().getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete();
-        new File(getContext().getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
-
-        for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) {
-            new File(getContext().getFilesDir(), i + "_"
-                    + LauncherFiles.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, this);
-            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));
-        }
-        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() {
-        // Context.getPackageName() may return the "original" package name,
-        // com.android.launcher3; Resources needs the real package name,
-        // com.android.launcher3. So we ask Resources for what it thinks the
-        // package name should be.
-        final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
-        try {
-            ApplicationInfo info = getContext().getPackageManager().getApplicationInfo(packageName, 0);
-            return new Pair<ApplicationInfo, Integer>(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);
@@ -1129,51 +520,10 @@
         }
     }
 
-    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);
     }
@@ -1186,4 +536,95 @@
                         Utilities.ALLOW_ROTATION_PREFERENCE_KEY, new Bundle())
                 .getBoolean(LauncherSettings.Settings.EXTRA_VALUE);
     }
+
+    // CAB for deleting items
+    /**
+     * 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;
+    }
+
+    /**
+     * 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 childCount = mWallpapersView.getChildCount();
+        int numCheckedItems = 0;
+        for (int i = 0; i < childCount; i++) {
+            CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+            if (c.isChecked()) {
+                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;
+    }
 }
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DefaultWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DefaultWallpaperInfo.java
new file mode 100644
index 0000000..49310b0
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DefaultWallpaperInfo.java
@@ -0,0 +1,159 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WallpaperCropActivity.CropViewScaleProvider;
+import com.android.launcher3.WallpaperPickerActivity;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class DefaultWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private static final String TAG = "DefaultWallpaperInfo";
+
+    public DefaultWallpaperInfo(Drawable thumb) {
+        super(thumb);
+    }
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        a.setCropViewTileSource(null, false, false, new CropViewScaleProvider() {
+
+            @Override
+            public float getScale(TileSource src) {
+                return 1f;
+            }
+        }, null);
+    }
+
+    @Override
+    public void onSave(WallpaperPickerActivity a) {
+        try {
+            WallpaperManager.getInstance(a.getContext()).clear();
+            a.setResult(Activity.RESULT_OK);
+        } catch (IOException e) {
+            Log.w(TAG, "Setting wallpaper to default threw exception", e);
+        }
+        a.finish();
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+
+    /**
+     * @return the system default wallpaper tile or null
+     */
+    public static WallpaperTileInfo get(Context context) {
+        return Utilities.ATLEAST_KITKAT
+                ? getDefaultWallpaper(context) : getPreKKDefaultWallpaperInfo(context);
+    }
+
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    private static DefaultWallpaperInfo getDefaultWallpaper(Context context) {
+        File defaultThumbFile = getDefaultThumbFile(context);
+        Bitmap thumb = null;
+        boolean defaultWallpaperExists = false;
+        Resources res = context.getResources();
+
+        if (defaultThumbFile.exists()) {
+            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+            defaultWallpaperExists = true;
+        } else {
+            Point defaultThumbSize = getDefaultThumbSize(res);
+            Drawable wallpaperDrawable = WallpaperManager.getInstance(context).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(context, thumb);
+            }
+        }
+        if (defaultWallpaperExists) {
+            return new DefaultWallpaperInfo(new BitmapDrawable(res, thumb));
+        }
+        return null;
+    }
+
+    private static ResourceWallpaperInfo getPreKKDefaultWallpaperInfo(Context context) {
+        Resources sysRes = Resources.getSystem();
+        Resources res = context.getResources();
+
+        int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
+
+        File defaultThumbFile = getDefaultThumbFile(context);
+        Bitmap thumb = null;
+        boolean defaultWallpaperExists = false;
+        if (defaultThumbFile.exists()) {
+            thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+            defaultWallpaperExists = true;
+        } else {
+            int rotation = BitmapUtils.getRotationFromExif(res, resId, context);
+            thumb = createThumbnail(context, null, null, sysRes, resId, rotation, false);
+            if (thumb != null) {
+                defaultWallpaperExists = saveDefaultWallpaperThumb(context, thumb);
+            }
+        }
+        if (defaultWallpaperExists) {
+            return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(res, thumb));
+        }
+        return null;
+    }
+
+    private static File getDefaultThumbFile(Context context) {
+        return new File(context.getFilesDir(), Build.VERSION.SDK_INT
+                + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL);
+    }
+
+    private static boolean saveDefaultWallpaperThumb(Context c, Bitmap b) {
+        // Delete old thumbnails.
+        new File(c.getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete();
+        new File(c.getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+
+        for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) {
+            new File(c.getFilesDir(), i + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete();
+        }
+        File f = getDefaultThumbFile(c);
+        try {
+            f.createNewFile();
+            FileOutputStream thumbFileStream = c.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;
+        }
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DrawableThumbWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DrawableThumbWallpaperInfo.java
new file mode 100644
index 0000000..a55375d
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/DrawableThumbWallpaperInfo.java
@@ -0,0 +1,37 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+
+/**
+ * WallpaperTileInfo which uses drawable as the thumbnail.
+ */
+public abstract class DrawableThumbWallpaperInfo extends WallpaperTileInfo {
+
+    private final Drawable mThumb;
+
+    DrawableThumbWallpaperInfo(Drawable thumb) {
+        mThumb = thumb;
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_item, parent, false);
+        setThumb(mThumb);
+        return mView;
+    }
+
+    public void setThumb(Drawable thumb) {
+        if (mView != null && thumb != null) {
+            thumb.setDither(true);
+            ImageView image = (ImageView) mView.findViewById(R.id.wallpaper_image);
+            image.setImageDrawable(thumb);
+        }
+    }
+}
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/FileWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/FileWallpaperInfo.java
new file mode 100644
index 0000000..be93e13
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/FileWallpaperInfo.java
@@ -0,0 +1,51 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import com.android.launcher3.WallpaperPickerActivity;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+
+import java.io.File;
+
+public class FileWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private final File mFile;
+
+    public FileWallpaperInfo(File target, Drawable thumb) {
+        super(thumb);
+        mFile = target;
+    }
+
+    @Override
+    public void onClick(final WallpaperPickerActivity a) {
+        a.setWallpaperButtonEnabled(false);
+        final BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
+                new BitmapRegionTileSource.FilePathBitmapSource(mFile.getAbsolutePath());
+        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));
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/LiveWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/LiveWallpaperInfo.java
new file mode 100644
index 0000000..d800ba6
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/LiveWallpaperInfo.java
@@ -0,0 +1,118 @@
+package com.android.launcher3.wallpapertileinfo;
+
+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.ImageView;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+
+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 LiveWallpaperInfo extends WallpaperTileInfo {
+
+    private static final String TAG = "LiveWallpaperTile";
+
+    private Drawable mThumbnail;
+    private WallpaperInfo mInfo;
+
+    public LiveWallpaperInfo(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);
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false);
+
+        ImageView image = (ImageView) mView.findViewById(R.id.wallpaper_image);
+        ImageView icon = (ImageView) mView.findViewById(R.id.wallpaper_icon);
+        if (mThumbnail != null) {
+            image.setImageDrawable(mThumbnail);
+            icon.setVisibility(View.GONE);
+        } else {
+            icon.setImageDrawable(mInfo.loadIcon(context.getPackageManager()));
+            icon.setVisibility(View.VISIBLE);
+        }
+
+        TextView label = (TextView) mView.findViewById(R.id.wallpaper_item_label);
+        label.setText(mInfo.loadLabel(context.getPackageManager()));
+        return mView;
+    }
+
+    /**
+     * An async task to load various live wallpaper tiles.
+     */
+    public static class LoaderTask extends AsyncTask<Void, Void, List<LiveWallpaperInfo>> {
+        private final Context mContext;
+
+        public LoaderTask(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        protected List<LiveWallpaperInfo> doInBackground(Void... params) {
+            final PackageManager pm = mContext.getPackageManager();
+
+            List<ResolveInfo> list = pm.queryIntentServices(
+                    new Intent(WallpaperService.SERVICE_INTERFACE),
+                    PackageManager.GET_META_DATA);
+
+            Collections.sort(list, new Comparator<ResolveInfo>() {
+                final Collator mCollator = Collator.getInstance();
+
+                public int compare(ResolveInfo info1, ResolveInfo info2) {
+                    return mCollator.compare(info1.loadLabel(pm), info2.loadLabel(pm));
+                }
+            });
+
+            List<LiveWallpaperInfo> result = new ArrayList<>();
+
+            for (ResolveInfo resolveInfo : list) {
+                WallpaperInfo info = null;
+                try {
+                    info = new WallpaperInfo(mContext, resolveInfo);
+                } catch (XmlPullParserException | IOException e) {
+                    Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+                    continue;
+                }
+
+
+                Drawable thumb = info.loadThumbnail(pm);
+                Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
+                launchIntent.setClassName(info.getPackageName(), info.getServiceName());
+                result.add(new LiveWallpaperInfo(thumb, info, launchIntent));
+            }
+
+            return result;
+        }
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/PickImageInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/PickImageInfo.java
new file mode 100644
index 0000000..9d8cc1c
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/PickImageInfo.java
@@ -0,0 +1,74 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
+import android.os.Process;
+import android.provider.MediaStore;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+
+public class PickImageInfo extends WallpaperTileInfo {
+
+    @Override
+    public void onClick(WallpaperPickerActivity a) {
+        Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
+        a.startActivityForResultSafely(intent, WallpaperPickerActivity.IMAGE_PICK);
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_image_picker_item, parent, false);
+
+        // Make its background the last photo taken on external storage
+        Bitmap lastPhoto = getThumbnailOfLastPhoto(context);
+        if (lastPhoto != null) {
+            ImageView galleryThumbnailBg =
+                    (ImageView) mView.findViewById(R.id.wallpaper_image);
+            galleryThumbnailBg.setImageBitmap(lastPhoto);
+            int colorOverlay = context.getResources().getColor(R.color.wallpaper_picker_translucent_gray);
+            galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
+        }
+
+        mView.setTag(this);
+        return mView;
+    }
+
+    private Bitmap getThumbnailOfLastPhoto(Context context) {
+        boolean canReadExternalStorage = context.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(context.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(context.getContentResolver(),
+                        id, MediaStore.Images.Thumbnails.MINI_KIND, null);
+            }
+            cursor.close();
+        }
+        return thumb;
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ResourceWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ResourceWallpaperInfo.java
new file mode 100644
index 0000000..6f28311
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ResourceWallpaperInfo.java
@@ -0,0 +1,68 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+import com.android.gallery3d.common.Utils;
+import com.android.launcher3.WallpaperCropActivity.CropViewScaleProvider;
+import com.android.launcher3.WallpaperPickerActivity;
+import com.android.launcher3.util.WallpaperUtils;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+public class ResourceWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private final Resources mResources;
+    private final int mResId;
+
+    public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
+        super(thumb);
+        mResources = res;
+        mResId = resId;
+    }
+
+    @Override
+    public void onClick(final WallpaperPickerActivity a) {
+        a.setWallpaperButtonEnabled(false);
+        final BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
+                new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId, a);
+        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) {
+        a.cropImageAndSetWallpaper(mResources, mResId);
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ThirdPartyWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ThirdPartyWallpaperInfo.java
new file mode 100644
index 0000000..e7ea511
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/ThirdPartyWallpaperInfo.java
@@ -0,0 +1,76 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+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.TextView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class ThirdPartyWallpaperInfo extends WallpaperTileInfo {
+
+    private final ResolveInfo mResolveInfo;
+    private final int mIconSize;
+
+    public ThirdPartyWallpaperInfo(ResolveInfo resolveInfo, int iconSize) {
+        mResolveInfo = resolveInfo;
+        mIconSize = iconSize;
+    }
+
+    @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)
+            .setComponent(itemComponentName);
+        a.startActivityForResultSafely(
+                launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+    }
+
+    @Override
+    public View createView(Context context, LayoutInflater inflator, ViewGroup parent) {
+        mView = inflator.inflate(R.layout.wallpaper_picker_third_party_item, parent, false);
+
+        TextView label = (TextView) mView.findViewById(R.id.wallpaper_item_label);
+        label.setText(mResolveInfo.loadLabel(context.getPackageManager()));
+        Drawable icon = mResolveInfo.loadIcon(context.getPackageManager());
+        icon.setBounds(new Rect(0, 0, mIconSize, mIconSize));
+        label.setCompoundDrawables(null, icon, null, null);
+        return mView;
+    }
+
+    public static List<ThirdPartyWallpaperInfo> getAll(Context context) {
+        ArrayList<ThirdPartyWallpaperInfo> result = new ArrayList<>();
+        int iconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
+
+        final PackageManager pm = context.getPackageManager();
+        Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
+        HashSet<String> excludePackages = new HashSet<>();
+        // Exclude packages which contain an image picker
+        for (ResolveInfo info : pm.queryIntentActivities(pickImageIntent, 0)) {
+            excludePackages.add(info.activityInfo.packageName);
+        }
+        excludePackages.add(context.getPackageName());
+        excludePackages.add("com.android.wallpaper.livepicker");
+
+        final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+        for (ResolveInfo info : pm.queryIntentActivities(pickWallpaperIntent, 0)) {
+            if (!excludePackages.contains(info.activityInfo.packageName)) {
+                result.add(new ThirdPartyWallpaperInfo(info, iconSize));
+            }
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/UriWallpaperInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/UriWallpaperInfo.java
new file mode 100644
index 0000000..3e76fb8
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/UriWallpaperInfo.java
@@ -0,0 +1,108 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import com.android.gallery3d.common.BitmapCropTask;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+import com.android.photos.BitmapRegionTileSource;
+import com.android.photos.BitmapRegionTileSource.BitmapSource;
+
+public class UriWallpaperInfo extends DrawableThumbWallpaperInfo {
+
+    private static final String TAG = "UriWallpaperInfo";
+
+    public final Uri mUri;
+
+    public UriWallpaperInfo(Uri uri) {
+        super(null);
+        mUri = uri;
+    }
+
+    @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) {
+        BitmapCropTask.OnBitmapCroppedHandler h = new BitmapCropTask.OnBitmapCroppedHandler() {
+            public void onBitmapCropped(byte[] imageBytes) {
+                // rotation is set to 0 since imageBytes has already been correctly rotated
+                Bitmap thumb = createThumbnail(a, null, imageBytes, null, 0, 0, true);
+                a.getSavedImages().writeImage(thumb, imageBytes);
+            }
+        };
+        a.cropImageAndSetWallpaper(mUri, h);
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isNamelessWallpaper() {
+        return true;
+    }
+
+    public void loadThumbnaleAsync(final WallpaperPickerActivity activity) {
+        mView.setVisibility(View.GONE);
+        new AsyncTask<Void, Void, Bitmap>() {
+            protected Bitmap doInBackground(Void...args) {
+                try {
+                    int rotation = BitmapUtils.getRotationFromExif(activity, mUri);
+                    return createThumbnail(activity, mUri, null, null, 0, rotation, false);
+                } catch (SecurityException securityException) {
+                    if (activity.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) {
+                    setThumb(new BitmapDrawable(activity.getResources(), thumb));
+                    mView.setVisibility(View.VISIBLE);
+                } else {
+                    Log.e(TAG, "Error loading thumbnail for uri=" + mUri);
+                }
+            }
+        }.execute();
+    }
+}
\ No newline at end of file
diff --git a/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/WallpaperTileInfo.java b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/WallpaperTileInfo.java
new file mode 100644
index 0000000..5fc317c
--- /dev/null
+++ b/WallpaperPicker/src/com/android/launcher3/wallpapertileinfo/WallpaperTileInfo.java
@@ -0,0 +1,87 @@
+package com.android.launcher3.wallpapertileinfo;
+
+import android.content.Context;
+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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.gallery3d.common.BitmapCropTask;
+import com.android.gallery3d.common.Utils;
+import com.android.launcher3.R;
+import com.android.launcher3.WallpaperPickerActivity;
+
+public abstract class WallpaperTileInfo {
+
+    protected View mView;
+
+    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 abstract View createView(Context context, LayoutInflater inflator, ViewGroup parent);
+
+    protected static Point getDefaultThumbSize(Resources res) {
+        return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
+                res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
+
+    }
+
+    protected static Bitmap createThumbnail(Context context, Uri uri, byte[] imageBytes,
+            Resources res, int resId, int rotation, boolean leftAligned) {
+        Point size = getDefaultThumbSize(context.getResources());
+        int width = size.x;
+        int height = size.y;
+
+        BitmapCropTask cropTask;
+        if (uri != null) {
+            cropTask = new BitmapCropTask(
+                    context, uri, null, rotation, width, height, false, true, null);
+        } else if (imageBytes != null) {
+            cropTask = new BitmapCropTask(
+                    imageBytes, null, rotation, width, height, false, true, null);
+        }  else {
+            cropTask = new BitmapCropTask(
+                    context, res, resId, null, rotation, width, height, false, true, null);
+        }
+        Point bounds = cropTask.getImageBounds();
+        if (bounds == null || bounds.x == 0 || bounds.y == 0) {
+            return null;
+        }
+
+        Matrix rotateMatrix = new Matrix();
+        rotateMatrix.setRotate(rotation);
+        float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+        rotateMatrix.mapPoints(rotatedBounds);
+        rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+        rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+        RectF cropRect = Utils.getMaxCropRect(
+                (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
+        cropTask.setCropBounds(cropRect);
+
+        if (cropTask.cropBitmap()) {
+            return cropTask.getCroppedBitmap();
+        } else {
+            return null;
+        }
+    }
+
+}
\ No newline at end of file