Merge "Fixing issue where shortcuts to standard android intents were getting pruned." into jb-ub-gel-agar
diff --git a/res/drawable-nodpi/wallpaper_01.jpg b/res/drawable-nodpi/wallpaper_01.jpg
deleted file mode 100644
index 6c4299c..0000000
--- a/res/drawable-nodpi/wallpaper_01.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_01_small.jpg b/res/drawable-nodpi/wallpaper_01_small.jpg
deleted file mode 100644
index 96482b8..0000000
--- a/res/drawable-nodpi/wallpaper_01_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_02.jpg b/res/drawable-nodpi/wallpaper_02.jpg
deleted file mode 100644
index c905f73..0000000
--- a/res/drawable-nodpi/wallpaper_02.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_02_small.jpg b/res/drawable-nodpi/wallpaper_02_small.jpg
deleted file mode 100644
index 3c6fccf..0000000
--- a/res/drawable-nodpi/wallpaper_02_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_03.jpg b/res/drawable-nodpi/wallpaper_03.jpg
deleted file mode 100644
index 56d20bd..0000000
--- a/res/drawable-nodpi/wallpaper_03.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_03_small.jpg b/res/drawable-nodpi/wallpaper_03_small.jpg
deleted file mode 100644
index bb4c3b4..0000000
--- a/res/drawable-nodpi/wallpaper_03_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_04.jpg b/res/drawable-nodpi/wallpaper_04.jpg
deleted file mode 100644
index 3a3e3b0..0000000
--- a/res/drawable-nodpi/wallpaper_04.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_04_small.jpg b/res/drawable-nodpi/wallpaper_04_small.jpg
deleted file mode 100644
index 6478c67..0000000
--- a/res/drawable-nodpi/wallpaper_04_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_05.jpg b/res/drawable-nodpi/wallpaper_05.jpg
deleted file mode 100644
index dec9b56..0000000
--- a/res/drawable-nodpi/wallpaper_05.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_05_small.jpg b/res/drawable-nodpi/wallpaper_05_small.jpg
deleted file mode 100644
index ab359e2..0000000
--- a/res/drawable-nodpi/wallpaper_05_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_06.jpg b/res/drawable-nodpi/wallpaper_06.jpg
deleted file mode 100644
index fd5bbdd..0000000
--- a/res/drawable-nodpi/wallpaper_06.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_06_small.jpg b/res/drawable-nodpi/wallpaper_06_small.jpg
deleted file mode 100644
index 0ccd4bb..0000000
--- a/res/drawable-nodpi/wallpaper_06_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_07.jpg b/res/drawable-nodpi/wallpaper_07.jpg
deleted file mode 100644
index fa72f92..0000000
--- a/res/drawable-nodpi/wallpaper_07.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_07_small.jpg b/res/drawable-nodpi/wallpaper_07_small.jpg
deleted file mode 100644
index 64ab7f6..0000000
--- a/res/drawable-nodpi/wallpaper_07_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_08.jpg b/res/drawable-nodpi/wallpaper_08.jpg
deleted file mode 100644
index 9178e13..0000000
--- a/res/drawable-nodpi/wallpaper_08.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_08_small.jpg b/res/drawable-nodpi/wallpaper_08_small.jpg
deleted file mode 100644
index 9cbeddb..0000000
--- a/res/drawable-nodpi/wallpaper_08_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_09.jpg b/res/drawable-nodpi/wallpaper_09.jpg
deleted file mode 100644
index 0016983..0000000
--- a/res/drawable-nodpi/wallpaper_09.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_09_small.jpg b/res/drawable-nodpi/wallpaper_09_small.jpg
deleted file mode 100644
index 6cc15b5..0000000
--- a/res/drawable-nodpi/wallpaper_09_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_10.jpg b/res/drawable-nodpi/wallpaper_10.jpg
deleted file mode 100644
index 60976e1..0000000
--- a/res/drawable-nodpi/wallpaper_10.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_10_small.jpg b/res/drawable-nodpi/wallpaper_10_small.jpg
deleted file mode 100644
index 3826672..0000000
--- a/res/drawable-nodpi/wallpaper_10_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_11.jpg b/res/drawable-nodpi/wallpaper_11.jpg
deleted file mode 100644
index ac1e677..0000000
--- a/res/drawable-nodpi/wallpaper_11.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_11_small.jpg b/res/drawable-nodpi/wallpaper_11_small.jpg
deleted file mode 100644
index d3c0f53..0000000
--- a/res/drawable-nodpi/wallpaper_11_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_12.jpg b/res/drawable-nodpi/wallpaper_12.jpg
deleted file mode 100644
index 368ca2d..0000000
--- a/res/drawable-nodpi/wallpaper_12.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_12_small.jpg b/res/drawable-nodpi/wallpaper_12_small.jpg
deleted file mode 100644
index 8f03db6..0000000
--- a/res/drawable-nodpi/wallpaper_12_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_13.jpg b/res/drawable-nodpi/wallpaper_13.jpg
deleted file mode 100644
index 21147a3..0000000
--- a/res/drawable-nodpi/wallpaper_13.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_13_small.jpg b/res/drawable-nodpi/wallpaper_13_small.jpg
deleted file mode 100644
index db115fb..0000000
--- a/res/drawable-nodpi/wallpaper_13_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_14.jpg b/res/drawable-nodpi/wallpaper_14.jpg
deleted file mode 100644
index 54e1b79..0000000
--- a/res/drawable-nodpi/wallpaper_14.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-nodpi/wallpaper_14_small.jpg b/res/drawable-nodpi/wallpaper_14_small.jpg
deleted file mode 100644
index f3dd837..0000000
--- a/res/drawable-nodpi/wallpaper_14_small.jpg
+++ /dev/null
Binary files differ
diff --git a/res/layout/wallpaper_picker_item.xml b/res/layout/wallpaper_picker_item.xml
index a1f9865..c73bd73 100644
--- a/res/layout/wallpaper_picker_item.xml
+++ b/res/layout/wallpaper_picker_item.xml
@@ -23,7 +23,7 @@
     android:foreground="@drawable/wallpaper_gallery_item">
     <ImageView
         android:id="@+id/wallpaper_image"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:scaleType="fitXY" />
 </FrameLayout>
diff --git a/res/values-nodpi/wallpapers.xml b/res/values-nodpi/wallpapers.xml
index 97c567f..1e340e4 100644
--- a/res/values-nodpi/wallpapers.xml
+++ b/res/values-nodpi/wallpapers.xml
@@ -17,19 +17,5 @@
 
 <resources>
     <string-array name="wallpapers" translatable="false">
-        <item>wallpaper_01</item>
-        <item>wallpaper_02</item>
-        <item>wallpaper_03</item>
-        <item>wallpaper_04</item>
-        <item>wallpaper_05</item>
-        <item>wallpaper_06</item>
-        <item>wallpaper_07</item>
-        <item>wallpaper_08</item>
-        <item>wallpaper_09</item>
-        <item>wallpaper_10</item>
-        <item>wallpaper_11</item>
-        <item>wallpaper_12</item>
-        <item>wallpaper_13</item>
-        <item>wallpaper_14</item>
     </string-array>
 </resources>
diff --git a/res/values/extra_wallpapers.xml b/res/values/extra_wallpapers.xml
deleted file mode 100644
index 9e0951c..0000000
--- a/res/values/extra_wallpapers.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2009 Google Inc.
- *
- * 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.
- -->
-
-<resources>
-    <string-array name="extra_wallpapers">
-    </string-array>
-</resources>
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 4d66eb3..5d3aa3a 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -34,15 +34,15 @@
     public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
     
     /** The list off all apps. */
-    public ArrayList<ApplicationInfo> data =
-            new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
+    public ArrayList<AppInfo> data =
+            new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);
     /** The list of apps that have been added since the last notify() call. */
-    public ArrayList<ApplicationInfo> added =
-            new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
+    public ArrayList<AppInfo> added =
+            new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);
     /** The list of apps that have been removed since the last notify() call. */
-    public ArrayList<ApplicationInfo> removed = new ArrayList<ApplicationInfo>();
+    public ArrayList<AppInfo> removed = new ArrayList<AppInfo>();
     /** The list of apps that have been modified since the last notify() call. */
-    public ArrayList<ApplicationInfo> modified = new ArrayList<ApplicationInfo>();
+    public ArrayList<AppInfo> modified = new ArrayList<AppInfo>();
 
     private IconCache mIconCache;
 
@@ -59,7 +59,7 @@
      *
      * If the app is already in the list, doesn't add it.
      */
-    public void add(ApplicationInfo info) {
+    public void add(AppInfo info) {
         if (findActivity(data, info.componentName)) {
             return;
         }
@@ -79,7 +79,7 @@
         return data.size();
     }
 
-    public ApplicationInfo get(int index) {
+    public AppInfo get(int index) {
         return data.get(index);
     }
 
@@ -91,7 +91,7 @@
 
         if (matches.size() > 0) {
             for (ResolveInfo info : matches) {
-                add(new ApplicationInfo(context.getPackageManager(), info, mIconCache, null));
+                add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
             }
         }
     }
@@ -100,9 +100,9 @@
      * Remove the apps for the given apk identified by packageName.
      */
     public void removePackage(String packageName) {
-        final List<ApplicationInfo> data = this.data;
+        final List<AppInfo> data = this.data;
         for (int i = data.size() - 1; i >= 0; i--) {
-            ApplicationInfo info = data.get(i);
+            AppInfo info = data.get(i);
             final ComponentName component = info.intent.getComponent();
             if (packageName.equals(component.getPackageName())) {
                 removed.add(info);
@@ -122,7 +122,7 @@
             // Find disabled/removed activities and remove them from data and add them
             // to the removed list.
             for (int i = data.size() - 1; i >= 0; i--) {
-                final ApplicationInfo applicationInfo = data.get(i);
+                final AppInfo applicationInfo = data.get(i);
                 final ComponentName component = applicationInfo.intent.getComponent();
                 if (packageName.equals(component.getPackageName())) {
                     if (!findActivity(matches, component)) {
@@ -138,11 +138,11 @@
             int count = matches.size();
             for (int i = 0; i < count; i++) {
                 final ResolveInfo info = matches.get(i);
-                ApplicationInfo applicationInfo = findApplicationInfoLocked(
+                AppInfo applicationInfo = findApplicationInfoLocked(
                         info.activityInfo.applicationInfo.packageName,
                         info.activityInfo.name);
                 if (applicationInfo == null) {
-                    add(new ApplicationInfo(context.getPackageManager(), info, mIconCache, null));
+                    add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
                 } else {
                     mIconCache.remove(applicationInfo.componentName);
                     mIconCache.getTitleAndIcon(applicationInfo, info, null);
@@ -152,7 +152,7 @@
         } else {
             // Remove all data for this package.
             for (int i = data.size() - 1; i >= 0; i--) {
-                final ApplicationInfo applicationInfo = data.get(i);
+                final AppInfo applicationInfo = data.get(i);
                 final ComponentName component = applicationInfo.intent.getComponent();
                 if (packageName.equals(component.getPackageName())) {
                     removed.add(applicationInfo);
@@ -194,10 +194,10 @@
     /**
      * Returns whether <em>apps</em> contains <em>component</em>.
      */
-    private static boolean findActivity(ArrayList<ApplicationInfo> apps, ComponentName component) {
+    private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component) {
         final int N = apps.size();
         for (int i=0; i<N; i++) {
-            final ApplicationInfo info = apps.get(i);
+            final AppInfo info = apps.get(i);
             if (info.componentName.equals(component)) {
                 return true;
             }
@@ -208,8 +208,8 @@
     /**
      * Find an ApplicationInfo object for the given packageName and className.
      */
-    private ApplicationInfo findApplicationInfoLocked(String packageName, String className) {
-        for (ApplicationInfo info: data) {
+    private AppInfo findApplicationInfoLocked(String packageName, String className) {
+        for (AppInfo info: data) {
             final ComponentName component = info.intent.getComponent();
             if (packageName.equals(component.getPackageName())
                     && className.equals(component.getClassName())) {
diff --git a/src/com/android/launcher3/ApplicationInfo.java b/src/com/android/launcher3/AppInfo.java
similarity index 92%
rename from src/com/android/launcher3/ApplicationInfo.java
rename to src/com/android/launcher3/AppInfo.java
index 1b396f7..53f81bb 100644
--- a/src/com/android/launcher3/ApplicationInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -31,8 +31,8 @@
 /**
  * Represents an app in AllAppsView.
  */
-class ApplicationInfo extends ItemInfo {
-    private static final String TAG = "Launcher2.ApplicationInfo";
+class AppInfo extends ItemInfo {
+    private static final String TAG = "Launcher3.AppInfo";
 
     /**
      * The intent used to start the application.
@@ -56,7 +56,7 @@
 
     int flags = 0;
 
-    ApplicationInfo() {
+    AppInfo() {
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
     }
 
@@ -67,7 +67,7 @@
     /**
      * Must not hold the Context.
      */
-    public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
+    public AppInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
             HashMap<Object, CharSequence> labelCache) {
         final String packageName = info.activityInfo.applicationInfo.packageName;
 
@@ -104,7 +104,7 @@
         return pi.firstInstallTime;
     }
 
-    public ApplicationInfo(ApplicationInfo info) {
+    public AppInfo(AppInfo info) {
         super(info);
         componentName = info.componentName;
         title = info.title.toString();
@@ -136,10 +136,9 @@
                 + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + dropPos + ")";
     }
 
-    public static void dumpApplicationInfoList(String tag, String label,
-            ArrayList<ApplicationInfo> list) {
+    public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
         Log.d(tag, label + " size=" + list.size());
-        for (ApplicationInfo info: list) {
+        for (AppInfo info: list) {
             Log.d(tag, "   title=\"" + info.title + "\" iconBitmap="
                     + info.iconBitmap + " firstInstallTime="
                     + info.firstInstallTime);
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index 09a5712..34fec46 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -168,7 +168,7 @@
     private PagedViewIcon mPressedIcon;
 
     // Content
-    private ArrayList<ApplicationInfo> mApps;
+    private ArrayList<AppInfo> mApps;
     private ArrayList<Object> mWidgets;
 
     // Cling
@@ -247,7 +247,7 @@
         super(context, attrs);
         mLayoutInflater = LayoutInflater.from(context);
         mPackageManager = context.getPackageManager();
-        mApps = new ArrayList<ApplicationInfo>();
+        mApps = new ArrayList<AppInfo>();
         mWidgets = new ArrayList<Object>();
         mIconCache = (LauncherAppState.getInstance()).getIconCache();
         mCanvas = new Canvas();
@@ -498,7 +498,7 @@
 
         if (v instanceof PagedViewIcon) {
             // Animate some feedback to the click
-            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
+            final AppInfo appInfo = (AppInfo) v.getTag();
 
             // Lock the drawable state to pressed until we return to Launcher
             if (mPressedIcon != null) {
@@ -1025,7 +1025,7 @@
         ArrayList<Object> items = new ArrayList<Object>();
         ArrayList<Bitmap> images = new ArrayList<Bitmap>();
         for (int i = startIndex; i < endIndex; ++i) {
-            ApplicationInfo info = mApps.get(i);
+            AppInfo info = mApps.get(i);
             PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
                     R.layout.apps_customize_application, layout, false);
             icon.applyFromApplicationInfo(info, true, this);
@@ -1541,59 +1541,59 @@
         }
     }
 
-    public void setApps(ArrayList<ApplicationInfo> list) {
+    public void setApps(ArrayList<AppInfo> list) {
         if (!DISABLE_ALL_APPS) {
             mApps = list;
             Collections.sort(mApps, LauncherModel.getAppNameComparator());
             updatePageCountsAndInvalidateData();
         }
     }
-    private void addAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
+    private void addAppsWithoutInvalidate(ArrayList<AppInfo> list) {
         // We add it in place, in alphabetical order
         int count = list.size();
         for (int i = 0; i < count; ++i) {
-            ApplicationInfo info = list.get(i);
+            AppInfo info = list.get(i);
             int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator());
             if (index < 0) {
                 mApps.add(-(index + 1), info);
             }
         }
     }
-    public void addApps(ArrayList<ApplicationInfo> list) {
+    public void addApps(ArrayList<AppInfo> list) {
         if (!DISABLE_ALL_APPS) {
             addAppsWithoutInvalidate(list);
             updatePageCountsAndInvalidateData();
         }
     }
-    private int findAppByComponent(List<ApplicationInfo> list, ApplicationInfo item) {
+    private int findAppByComponent(List<AppInfo> list, AppInfo item) {
         ComponentName removeComponent = item.intent.getComponent();
         int length = list.size();
         for (int i = 0; i < length; ++i) {
-            ApplicationInfo info = list.get(i);
+            AppInfo info = list.get(i);
             if (info.intent.getComponent().equals(removeComponent)) {
                 return i;
             }
         }
         return -1;
     }
-    private void removeAppsWithoutInvalidate(ArrayList<ApplicationInfo> list) {
+    private void removeAppsWithoutInvalidate(ArrayList<AppInfo> list) {
         // loop through all the apps and remove apps that have the same component
         int length = list.size();
         for (int i = 0; i < length; ++i) {
-            ApplicationInfo info = list.get(i);
+            AppInfo info = list.get(i);
             int removeIndex = findAppByComponent(mApps, info);
             if (removeIndex > -1) {
                 mApps.remove(removeIndex);
             }
         }
     }
-    public void removeApps(ArrayList<ApplicationInfo> appInfos) {
+    public void removeApps(ArrayList<AppInfo> appInfos) {
         if (!DISABLE_ALL_APPS) {
             removeAppsWithoutInvalidate(appInfos);
             updatePageCountsAndInvalidateData();
         }
     }
-    public void updateApps(ArrayList<ApplicationInfo> list) {
+    public void updateApps(ArrayList<AppInfo> list) {
         // We remove and re-add the updated applications list because it's properties may have
         // changed (ie. the title), and this will ensure that the items will be in their proper
         // place in the list.
@@ -1627,7 +1627,7 @@
 
     public void dumpState() {
         // TODO: Dump information related to current list of Applications, Widgets, etc.
-        ApplicationInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
+        AppInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
         dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets);
     }
 
diff --git a/src/com/android/launcher3/AutoScrollHelper.java b/src/com/android/launcher3/AutoScrollHelper.java
new file mode 100644
index 0000000..9a7c3b0
--- /dev/null
+++ b/src/com/android/launcher3/AutoScrollHelper.java
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewCompat;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * AutoScrollHelper is a utility class for adding automatic edge-triggered
+ * scrolling to Views.
+ * <p>
+ * <b>Note:</b> Implementing classes are responsible for overriding the
+ * {@link #scrollTargetBy}, {@link #canTargetScrollHorizontally}, and
+ * {@link #canTargetScrollVertically} methods. See
+ * {@link ListViewAutoScrollHelper} for a {@link android.widget.ListView}
+ * -specific implementation.
+ * <p>
+ * <h1>Activation</h1> Automatic scrolling starts when the user touches within
+ * an activation area. By default, activation areas are defined as the top,
+ * left, right, and bottom 20% of the host view's total area. Touching within
+ * the top activation area scrolls up, left scrolls to the left, and so on.
+ * <p>
+ * As the user touches closer to the extreme edge of the activation area,
+ * scrolling accelerates up to a maximum velocity. When using the default edge
+ * type, {@link #EDGE_TYPE_INSIDE_EXTEND}, moving outside of the view bounds
+ * will scroll at the maximum velocity.
+ * <p>
+ * The following activation properties may be configured:
+ * <ul>
+ * <li>Delay after entering activation area before auto-scrolling begins, see
+ * {@link #setActivationDelay}. Default value is
+ * {@link ViewConfiguration#getTapTimeout()} to avoid conflicting with taps.
+ * <li>Location of activation areas, see {@link #setEdgeType}. Default value is
+ * {@link #EDGE_TYPE_INSIDE_EXTEND}.
+ * <li>Size of activation areas relative to view size, see
+ * {@link #setRelativeEdges}. Default value is 20% for both vertical and
+ * horizontal edges.
+ * <li>Maximum size used to constrain relative size, see
+ * {@link #setMaximumEdges}. Default value is {@link #NO_MAX}.
+ * </ul>
+ * <h1>Scrolling</h1> When automatic scrolling is active, the helper will
+ * repeatedly call {@link #scrollTargetBy} to apply new scrolling offsets.
+ * <p>
+ * The following scrolling properties may be configured:
+ * <ul>
+ * <li>Acceleration ramp-up duration, see {@link #setRampUpDuration}. Default
+ * value is 2500 milliseconds.
+ * <li>Acceleration ramp-down duration, see {@link #setRampDownDuration}.
+ * Default value is 500 milliseconds.
+ * <li>Target velocity relative to view size, see {@link #setRelativeVelocity}.
+ * Default value is 100% per second for both vertical and horizontal.
+ * <li>Minimum velocity used to constrain relative velocity, see
+ * {@link #setMinimumVelocity}. When set, scrolling will accelerate to the
+ * larger of either this value or the relative target value. Default value is
+ * approximately 5 centimeters or 315 dips per second.
+ * <li>Maximum velocity used to constrain relative velocity, see
+ * {@link #setMaximumVelocity}. Default value is approximately 25 centimeters or
+ * 1575 dips per second.
+ * </ul>
+ */
+public abstract class AutoScrollHelper implements View.OnTouchListener {
+    /**
+     * Constant passed to {@link #setRelativeEdges} or
+     * {@link #setRelativeVelocity}. Using this value ensures that the computed
+     * relative value is ignored and the absolute maximum value is always used.
+     */
+    public static final float RELATIVE_UNSPECIFIED = 0;
+
+    /**
+     * Constant passed to {@link #setMaximumEdges}, {@link #setMaximumVelocity},
+     * or {@link #setMinimumVelocity}. Using this value ensures that the
+     * computed relative value is always used without constraining to a
+     * particular minimum or maximum value.
+     */
+    public static final float NO_MAX = Float.MAX_VALUE;
+
+    /**
+     * Constant passed to {@link #setMaximumEdges}, or
+     * {@link #setMaximumVelocity}, or {@link #setMinimumVelocity}. Using this
+     * value ensures that the computed relative value is always used without
+     * constraining to a particular minimum or maximum value.
+     */
+    public static final float NO_MIN = 0;
+
+    /**
+     * Edge type that specifies an activation area starting at the view bounds
+     * and extending inward. Moving outside the view bounds will stop scrolling.
+     *
+     * @see #setEdgeType
+     */
+    public static final int EDGE_TYPE_INSIDE = 0;
+
+    /**
+     * Edge type that specifies an activation area starting at the view bounds
+     * and extending inward. After activation begins, moving outside the view
+     * bounds will continue scrolling.
+     *
+     * @see #setEdgeType
+     */
+    public static final int EDGE_TYPE_INSIDE_EXTEND = 1;
+
+    /**
+     * Edge type that specifies an activation area starting at the view bounds
+     * and extending outward. Moving inside the view bounds will stop scrolling.
+     *
+     * @see #setEdgeType
+     */
+    public static final int EDGE_TYPE_OUTSIDE = 2;
+
+    private static final int HORIZONTAL = 0;
+    private static final int VERTICAL = 1;
+
+    /** Scroller used to control acceleration toward maximum velocity. */
+    private final ClampedScroller mScroller = new ClampedScroller();
+
+    /** Interpolator used to scale velocity with touch position. */
+    private final Interpolator mEdgeInterpolator = new AccelerateInterpolator();
+
+    /** The view to auto-scroll. Might not be the source of touch events. */
+    private final View mTarget;
+
+    /** Runnable used to animate scrolling. */
+    private Runnable mRunnable;
+
+    /** Edge insets used to activate auto-scrolling. */
+    private float[] mRelativeEdges = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED };
+
+    /** Clamping values for edge insets used to activate auto-scrolling. */
+    private float[] mMaximumEdges = new float[] { NO_MAX, NO_MAX };
+
+    /** The type of edge being used. */
+    private int mEdgeType;
+
+    /** Delay after entering an activation edge before auto-scrolling begins. */
+    private int mActivationDelay;
+
+    /** Relative scrolling velocity at maximum edge distance. */
+    private float[] mRelativeVelocity = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED };
+
+    /** Clamping values used for scrolling velocity. */
+    private float[] mMinimumVelocity = new float[] { NO_MIN, NO_MIN };
+
+    /** Clamping values used for scrolling velocity. */
+    private float[] mMaximumVelocity = new float[] { NO_MAX, NO_MAX };
+
+    /** Whether to start activation immediately. */
+    private boolean mAlreadyDelayed;
+
+    /** Whether to reset the scroller start time on the next animation. */
+    private boolean mNeedsReset;
+
+    /** Whether to send a cancel motion event to the target view. */
+    private boolean mNeedsCancel;
+
+    /** Whether the auto-scroller is actively scrolling. */
+    private boolean mAnimating;
+
+    /** Whether the auto-scroller is enabled. */
+    private boolean mEnabled;
+
+    /** Whether the auto-scroller consumes events when scrolling. */
+    private boolean mExclusive;
+
+    // Default values.
+    private static final int DEFAULT_EDGE_TYPE = EDGE_TYPE_INSIDE_EXTEND;
+    private static final int DEFAULT_MINIMUM_VELOCITY_DIPS = 315;
+    private static final int DEFAULT_MAXIMUM_VELOCITY_DIPS = 1575;
+    private static final float DEFAULT_MAXIMUM_EDGE = NO_MAX;
+    private static final float DEFAULT_RELATIVE_EDGE = 0.2f;
+    private static final float DEFAULT_RELATIVE_VELOCITY = 1f;
+    private static final int DEFAULT_ACTIVATION_DELAY = ViewConfiguration.getTapTimeout();
+    private static final int DEFAULT_RAMP_UP_DURATION = 2500;
+    private static final int DEFAULT_RAMP_DOWN_DURATION = 500;
+
+    /**
+     * Creates a new helper for scrolling the specified target view.
+     * <p>
+     * The resulting helper may be configured by chaining setter calls and
+     * should be set as a touch listener on the target view.
+     * <p>
+     * By default, the helper is disabled and will not respond to touch events
+     * until it is enabled using {@link #setEnabled}.
+     *
+     * @param target The view to automatically scroll.
+     */
+    public AutoScrollHelper(View target) {
+        mTarget = target;
+
+        final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
+        final int maxVelocity = (int) (DEFAULT_MAXIMUM_VELOCITY_DIPS * metrics.density + 0.5f);
+        final int minVelocity = (int) (DEFAULT_MINIMUM_VELOCITY_DIPS * metrics.density + 0.5f);
+        setMaximumVelocity(maxVelocity, maxVelocity);
+        setMinimumVelocity(minVelocity, minVelocity);
+
+        setEdgeType(DEFAULT_EDGE_TYPE);
+        setMaximumEdges(DEFAULT_MAXIMUM_EDGE, DEFAULT_MAXIMUM_EDGE);
+        setRelativeEdges(DEFAULT_RELATIVE_EDGE, DEFAULT_RELATIVE_EDGE);
+        setRelativeVelocity(DEFAULT_RELATIVE_VELOCITY, DEFAULT_RELATIVE_VELOCITY);
+        setActivationDelay(DEFAULT_ACTIVATION_DELAY);
+        setRampUpDuration(DEFAULT_RAMP_UP_DURATION);
+        setRampDownDuration(DEFAULT_RAMP_DOWN_DURATION);
+    }
+
+    /**
+     * Sets whether the scroll helper is enabled and should respond to touch
+     * events.
+     *
+     * @param enabled Whether the scroll helper is enabled.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setEnabled(boolean enabled) {
+        if (mEnabled && !enabled) {
+            requestStop();
+        }
+
+        mEnabled = enabled;
+        return this;
+    }
+
+    /**
+     * @return True if this helper is enabled and responding to touch events.
+     */
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Enables or disables exclusive handling of touch events during scrolling.
+     * By default, exclusive handling is disabled and the target view receives
+     * all touch events.
+     * <p>
+     * When enabled, {@link #onTouch} will return true if the helper is
+     * currently scrolling and false otherwise.
+     *
+     * @param exclusive True to exclusively handle touch events during scrolling,
+     *            false to allow the target view to receive all touch events.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setExclusive(boolean exclusive) {
+        mExclusive = exclusive;
+        return this;
+    }
+
+    /**
+     * Indicates whether the scroll helper handles touch events exclusively
+     * during scrolling.
+     *
+     * @return True if exclusive handling of touch events during scrolling is
+     *         enabled, false otherwise.
+     * @see #setExclusive(boolean)
+     */
+    public boolean isExclusive() {
+        return mExclusive;
+    }
+
+    /**
+     * Sets the absolute maximum scrolling velocity.
+     * <p>
+     * If relative velocity is not specified, scrolling will always reach the
+     * same maximum velocity. If both relative and maximum velocities are
+     * specified, the maximum velocity will be used to clamp the calculated
+     * relative velocity.
+     *
+     * @param horizontalMax The maximum horizontal scrolling velocity, or
+     *            {@link #NO_MAX} to leave the relative value unconstrained.
+     * @param verticalMax The maximum vertical scrolling velocity, or
+     *            {@link #NO_MAX} to leave the relative value unconstrained.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setMaximumVelocity(float horizontalMax, float verticalMax) {
+        mMaximumVelocity[HORIZONTAL] = horizontalMax / 1000f;
+        mMaximumVelocity[VERTICAL] = verticalMax / 1000f;
+        return this;
+    }
+
+    /**
+     * Sets the absolute minimum scrolling velocity.
+     * <p>
+     * If both relative and minimum velocities are specified, the minimum
+     * velocity will be used to clamp the calculated relative velocity.
+     *
+     * @param horizontalMin The minimum horizontal scrolling velocity, or
+     *            {@link #NO_MIN} to leave the relative value unconstrained.
+     * @param verticalMin The minimum vertical scrolling velocity, or
+     *            {@link #NO_MIN} to leave the relative value unconstrained.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setMinimumVelocity(float horizontalMin, float verticalMin) {
+        mMinimumVelocity[HORIZONTAL] = horizontalMin / 1000f;
+        mMinimumVelocity[VERTICAL] = verticalMin / 1000f;
+        return this;
+    }
+
+    /**
+     * Sets the target scrolling velocity relative to the host view's
+     * dimensions.
+     * <p>
+     * If both relative and maximum velocities are specified, the maximum
+     * velocity will be used to clamp the calculated relative velocity.
+     *
+     * @param horizontal The target horizontal velocity as a fraction of the
+     *            host view width per second, or {@link #RELATIVE_UNSPECIFIED}
+     *            to ignore.
+     * @param vertical The target vertical velocity as a fraction of the host
+     *            view height per second, or {@link #RELATIVE_UNSPECIFIED} to
+     *            ignore.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setRelativeVelocity(float horizontal, float vertical) {
+        mRelativeVelocity[HORIZONTAL] = horizontal / 1000f;
+        mRelativeVelocity[VERTICAL] = vertical / 1000f;
+        return this;
+    }
+
+    /**
+     * Sets the activation edge type, one of:
+     * <ul>
+     * <li>{@link #EDGE_TYPE_INSIDE} for edges that respond to touches inside
+     * the bounds of the host view. If touch moves outside the bounds, scrolling
+     * will stop.
+     * <li>{@link #EDGE_TYPE_INSIDE_EXTEND} for inside edges that continued to
+     * scroll when touch moves outside the bounds of the host view.
+     * <li>{@link #EDGE_TYPE_OUTSIDE} for edges that only respond to touches
+     * that move outside the bounds of the host view.
+     * </ul>
+     *
+     * @param type The type of edge to use.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setEdgeType(int type) {
+        mEdgeType = type;
+        return this;
+    }
+
+    /**
+     * Sets the activation edge size relative to the host view's dimensions.
+     * <p>
+     * If both relative and maximum edges are specified, the maximum edge will
+     * be used to constrain the calculated relative edge size.
+     *
+     * @param horizontal The horizontal edge size as a fraction of the host view
+     *            width, or {@link #RELATIVE_UNSPECIFIED} to always use the
+     *            maximum value.
+     * @param vertical The vertical edge size as a fraction of the host view
+     *            height, or {@link #RELATIVE_UNSPECIFIED} to always use the
+     *            maximum value.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setRelativeEdges(float horizontal, float vertical) {
+        mRelativeEdges[HORIZONTAL] = horizontal;
+        mRelativeEdges[VERTICAL] = vertical;
+        return this;
+    }
+
+    /**
+     * Sets the absolute maximum edge size.
+     * <p>
+     * If relative edge size is not specified, activation edges will always be
+     * the maximum edge size. If both relative and maximum edges are specified,
+     * the maximum edge will be used to constrain the calculated relative edge
+     * size.
+     *
+     * @param horizontalMax The maximum horizontal edge size in pixels, or
+     *            {@link #NO_MAX} to use the unconstrained calculated relative
+     *            value.
+     * @param verticalMax The maximum vertical edge size in pixels, or
+     *            {@link #NO_MAX} to use the unconstrained calculated relative
+     *            value.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setMaximumEdges(float horizontalMax, float verticalMax) {
+        mMaximumEdges[HORIZONTAL] = horizontalMax;
+        mMaximumEdges[VERTICAL] = verticalMax;
+        return this;
+    }
+
+    /**
+     * Sets the delay after entering an activation edge before activation of
+     * auto-scrolling. By default, the activation delay is set to
+     * {@link ViewConfiguration#getTapTimeout()}.
+     * <p>
+     * Specifying a delay of zero will start auto-scrolling immediately after
+     * the touch position enters an activation edge.
+     *
+     * @param delayMillis The activation delay in milliseconds.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setActivationDelay(int delayMillis) {
+        mActivationDelay = delayMillis;
+        return this;
+    }
+
+    /**
+     * Sets the amount of time after activation of auto-scrolling that is takes
+     * to reach target velocity for the current touch position.
+     * <p>
+     * Specifying a duration greater than zero prevents sudden jumps in
+     * velocity.
+     *
+     * @param durationMillis The ramp-up duration in milliseconds.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setRampUpDuration(int durationMillis) {
+        mScroller.setRampUpDuration(durationMillis);
+        return this;
+    }
+
+    /**
+     * Sets the amount of time after de-activation of auto-scrolling that is
+     * takes to slow to a stop.
+     * <p>
+     * Specifying a duration greater than zero prevents sudden jumps in
+     * velocity.
+     *
+     * @param durationMillis The ramp-down duration in milliseconds.
+     * @return The scroll helper, which may used to chain setter calls.
+     */
+    public AutoScrollHelper setRampDownDuration(int durationMillis) {
+        mScroller.setRampDownDuration(durationMillis);
+        return this;
+    }
+
+    /**
+     * Handles touch events by activating automatic scrolling, adjusting scroll
+     * velocity, or stopping.
+     * <p>
+     * If {@link #isExclusive()} is false, always returns false so that
+     * the host view may handle touch events. Otherwise, returns true when
+     * automatic scrolling is active and false otherwise.
+     */
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        if (!mEnabled) {
+            return false;
+        }
+
+        final int action = MotionEventCompat.getActionMasked(event);
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mNeedsCancel = true;
+                mAlreadyDelayed = false;
+                // $FALL-THROUGH$
+            case MotionEvent.ACTION_MOVE:
+                final float xTargetVelocity = computeTargetVelocity(
+                        HORIZONTAL, event.getX(), v.getWidth(), mTarget.getWidth());
+                final float yTargetVelocity = computeTargetVelocity(
+                        VERTICAL, event.getY(), v.getHeight(), mTarget.getHeight());
+                mScroller.setTargetVelocity(xTargetVelocity, yTargetVelocity);
+
+                // If the auto scroller was not previously active, but it should
+                // be, then update the state and start animations.
+                if (!mAnimating && shouldAnimate()) {
+                    startAnimating();
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                requestStop();
+                break;
+        }
+
+        return mExclusive && mAnimating;
+    }
+
+    /**
+     * @return whether the target is able to scroll in the requested direction
+     */
+    private boolean shouldAnimate() {
+        final ClampedScroller scroller = mScroller;
+        final int verticalDirection = scroller.getVerticalDirection();
+        final int horizontalDirection = scroller.getHorizontalDirection();
+
+        return verticalDirection != 0 && canTargetScrollVertically(verticalDirection)
+                || horizontalDirection != 0 && canTargetScrollHorizontally(horizontalDirection);
+    }
+
+    /**
+     * Starts the scroll animation.
+     */
+    private void startAnimating() {
+        if (mRunnable == null) {
+            mRunnable = new ScrollAnimationRunnable();
+        }
+
+        mAnimating = true;
+        mNeedsReset = true;
+
+        if (!mAlreadyDelayed && mActivationDelay > 0) {
+            ViewCompat.postOnAnimationDelayed(mTarget, mRunnable, mActivationDelay);
+        } else {
+            mRunnable.run();
+        }
+
+        // If we start animating again before the user lifts their finger, we
+        // already know it's not a tap and don't need an activation delay.
+        mAlreadyDelayed = true;
+    }
+
+    /**
+     * Requests that the scroll animation slow to a stop. If there is an
+     * activation delay, this may occur between posting the animation and
+     * actually running it.
+     */
+    private void requestStop() {
+        if (mNeedsReset) {
+            // The animation has been posted, but hasn't run yet. Manually
+            // stopping animation will prevent it from running.
+            mAnimating = false;
+        } else {
+            mScroller.requestStop();
+        }
+    }
+
+    private float computeTargetVelocity(
+            int direction, float coordinate, float srcSize, float dstSize) {
+        final float relativeEdge = mRelativeEdges[direction];
+        final float maximumEdge = mMaximumEdges[direction];
+        final float value = getEdgeValue(relativeEdge, srcSize, maximumEdge, coordinate);
+        if (value == 0) {
+            // The edge in this direction is not activated.
+            return 0;
+        }
+
+        final float relativeVelocity = mRelativeVelocity[direction];
+        final float minimumVelocity = mMinimumVelocity[direction];
+        final float maximumVelocity = mMaximumVelocity[direction];
+        final float targetVelocity = relativeVelocity * dstSize;
+
+        // Target velocity is adjusted for interpolated edge position, then
+        // clamped to the minimum and maximum values. Later, this value will be
+        // adjusted for time-based acceleration.
+        if (value > 0) {
+            return constrain(value * targetVelocity, minimumVelocity, maximumVelocity);
+        } else {
+            return -constrain(-value * targetVelocity, minimumVelocity, maximumVelocity);
+        }
+    }
+
+    /**
+     * Override this method to scroll the target view by the specified number of
+     * pixels.
+     *
+     * @param deltaX The number of pixels to scroll by horizontally.
+     * @param deltaY The number of pixels to scroll by vertically.
+     */
+    public abstract void scrollTargetBy(int deltaX, int deltaY);
+
+    /**
+     * Override this method to return whether the target view can be scrolled
+     * horizontally in a certain direction.
+     *
+     * @param direction Negative to check scrolling left, positive to check
+     *            scrolling right.
+     * @return true if the target view is able to horizontally scroll in the
+     *         specified direction.
+     */
+    public abstract boolean canTargetScrollHorizontally(int direction);
+
+    /**
+     * Override this method to return whether the target view can be scrolled
+     * vertically in a certain direction.
+     *
+     * @param direction Negative to check scrolling up, positive to check
+     *            scrolling down.
+     * @return true if the target view is able to vertically scroll in the
+     *         specified direction.
+     */
+    public abstract boolean canTargetScrollVertically(int direction);
+
+    /**
+     * Returns the interpolated position of a touch point relative to an edge
+     * defined by its relative inset, its maximum absolute inset, and the edge
+     * interpolator.
+     *
+     * @param relativeValue The size of the inset relative to the total size.
+     * @param size Total size.
+     * @param maxValue The maximum size of the inset, used to clamp (relative *
+     *            total).
+     * @param current Touch position within within the total size.
+     * @return Interpolated value of the touch position within the edge.
+     */
+    private float getEdgeValue(float relativeValue, float size, float maxValue, float current) {
+        // For now, leading and trailing edges are always the same size.
+        final float edgeSize = constrain(relativeValue * size, NO_MIN, maxValue);
+        final float valueLeading = constrainEdgeValue(current, edgeSize);
+        final float valueTrailing = constrainEdgeValue(size - current, edgeSize);
+        final float value = (valueTrailing - valueLeading);
+        final float interpolated;
+        if (value < 0) {
+            interpolated = -mEdgeInterpolator.getInterpolation(-value);
+        } else if (value > 0) {
+            interpolated = mEdgeInterpolator.getInterpolation(value);
+        } else {
+            return 0;
+        }
+
+        return constrain(interpolated, -1, 1);
+    }
+
+    private float constrainEdgeValue(float current, float leading) {
+        if (leading == 0) {
+            return 0;
+        }
+
+        switch (mEdgeType) {
+            case EDGE_TYPE_INSIDE:
+            case EDGE_TYPE_INSIDE_EXTEND:
+                if (current < leading) {
+                    if (current > 0) {
+                        // Movement up to the edge is scaled.
+                        return 1f - current / leading;
+                    } else if (mAnimating && (mEdgeType == EDGE_TYPE_INSIDE_EXTEND)) {
+                        // Movement beyond the edge is always maximum.
+                        return 1f;
+                    }
+                }
+                break;
+            case EDGE_TYPE_OUTSIDE:
+                if (current < 0) {
+                    // Movement beyond the edge is scaled.
+                    return current / -leading;
+                }
+                break;
+        }
+
+        return 0;
+    }
+
+    private static int constrain(int value, int min, int max) {
+        if (value > max) {
+            return max;
+        } else if (value < min) {
+            return min;
+        } else {
+            return value;
+        }
+    }
+
+    private static float constrain(float value, float min, float max) {
+        if (value > max) {
+            return max;
+        } else if (value < min) {
+            return min;
+        } else {
+            return value;
+        }
+    }
+
+    /**
+     * Sends a {@link MotionEvent#ACTION_CANCEL} event to the target view,
+     * canceling any ongoing touch events.
+     */
+    private void cancelTargetTouch() {
+        final long eventTime = SystemClock.uptimeMillis();
+        final MotionEvent cancel = MotionEvent.obtain(
+                eventTime, eventTime, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+        mTarget.onTouchEvent(cancel);
+        cancel.recycle();
+    }
+
+    private class ScrollAnimationRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (!mAnimating) {
+                return;
+            }
+
+            if (mNeedsReset) {
+                mNeedsReset = false;
+                mScroller.start();
+            }
+
+            final ClampedScroller scroller = mScroller;
+            if (scroller.isFinished() || !shouldAnimate()) {
+                mAnimating = false;
+                return;
+            }
+
+            if (mNeedsCancel) {
+                mNeedsCancel = false;
+                cancelTargetTouch();
+            }
+
+            scroller.computeScrollDelta();
+
+            final int deltaX = scroller.getDeltaX();
+            final int deltaY = scroller.getDeltaY();
+            scrollTargetBy(deltaX,  deltaY);
+
+            // Keep going until the scroller has permanently stopped.
+            ViewCompat.postOnAnimation(mTarget, this);
+        }
+    }
+
+    /**
+     * Scroller whose velocity follows the curve of an {@link Interpolator} and
+     * is clamped to the interpolated 0f value before starting and the
+     * interpolated 1f value after a specified duration.
+     */
+    private static class ClampedScroller {
+        private int mRampUpDuration;
+        private int mRampDownDuration;
+        private float mTargetVelocityX;
+        private float mTargetVelocityY;
+
+        private long mStartTime;
+
+        private long mDeltaTime;
+        private int mDeltaX;
+        private int mDeltaY;
+
+        private long mStopTime;
+        private float mStopValue;
+        private int mEffectiveRampDown;
+
+        /**
+         * Creates a new ramp-up scroller that reaches full velocity after a
+         * specified duration.
+         */
+        public ClampedScroller() {
+            mStartTime = Long.MIN_VALUE;
+            mStopTime = -1;
+            mDeltaTime = 0;
+            mDeltaX = 0;
+            mDeltaY = 0;
+        }
+
+        public void setRampUpDuration(int durationMillis) {
+            mRampUpDuration = durationMillis;
+        }
+
+        public void setRampDownDuration(int durationMillis) {
+            mRampDownDuration = durationMillis;
+        }
+
+        /**
+         * Starts the scroller at the current animation time.
+         */
+        public void start() {
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+            mStopTime = -1;
+            mDeltaTime = mStartTime;
+            mStopValue = 0.5f;
+            mDeltaX = 0;
+            mDeltaY = 0;
+        }
+
+        /**
+         * Stops the scroller at the current animation time.
+         */
+        public void requestStop() {
+            final long currentTime = AnimationUtils.currentAnimationTimeMillis();
+            mEffectiveRampDown = constrain((int) (currentTime - mStartTime), 0, mRampDownDuration);
+            mStopValue = getValueAt(currentTime);
+            mStopTime = currentTime;
+        }
+
+        public boolean isFinished() {
+            return mStopTime > 0
+                    && AnimationUtils.currentAnimationTimeMillis() > mStopTime + mEffectiveRampDown;
+        }
+
+        private float getValueAt(long currentTime) {
+            if (currentTime < mStartTime) {
+                return 0f;
+            } else if (mStopTime < 0 || currentTime < mStopTime) {
+                final long elapsedSinceStart = currentTime - mStartTime;
+                return 0.5f * constrain(elapsedSinceStart / (float) mRampUpDuration, 0, 1);
+            } else {
+                final long elapsedSinceEnd = currentTime - mStopTime;
+                return (1 - mStopValue) + mStopValue
+                        * constrain(elapsedSinceEnd / (float) mEffectiveRampDown, 0, 1);
+            }
+        }
+
+        /**
+         * Interpolates the value along a parabolic curve corresponding to the equation
+         * <code>y = -4x * (x-1)</code>.
+         *
+         * @param value The value to interpolate, between 0 and 1.
+         * @return the interpolated value, between 0 and 1.
+         */
+        private float interpolateValue(float value) {
+            return -4 * value * value + 4 * value;
+        }
+
+        /**
+         * Computes the current scroll deltas. This usually only be called after
+         * starting the scroller with {@link #start()}.
+         *
+         * @see #getDeltaX()
+         * @see #getDeltaY()
+         */
+        public void computeScrollDelta() {
+            if (mDeltaTime == 0) {
+                throw new RuntimeException("Cannot compute scroll delta before calling start()");
+            }
+
+            final long currentTime = AnimationUtils.currentAnimationTimeMillis();
+            final float value = getValueAt(currentTime);
+            final float scale = interpolateValue(value);
+            final long elapsedSinceDelta = currentTime - mDeltaTime;
+
+            mDeltaTime = currentTime;
+            mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX);
+            mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY);
+        }
+
+        /**
+         * Sets the target velocity for this scroller.
+         *
+         * @param x The target X velocity in pixels per millisecond.
+         * @param y The target Y velocity in pixels per millisecond.
+         */
+        public void setTargetVelocity(float x, float y) {
+            mTargetVelocityX = x;
+            mTargetVelocityY = y;
+        }
+
+        public int getHorizontalDirection() {
+            return (int) (mTargetVelocityX / Math.abs(mTargetVelocityX));
+        }
+
+        public int getVerticalDirection() {
+            return (int) (mTargetVelocityY / Math.abs(mTargetVelocityY));
+        }
+
+        /**
+         * The distance traveled in the X-coordinate computed by the last call
+         * to {@link #computeScrollDelta()}.
+         */
+        public int getDeltaX() {
+            return mDeltaX;
+        }
+
+        /**
+         * The distance traveled in the Y-coordinate computed by the last call
+         * to {@link #computeScrollDelta()}.
+         */
+        public int getDeltaY() {
+            return mDeltaY;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/AutoScroller.java b/src/com/android/launcher3/AutoScroller.java
deleted file mode 100644
index ac8e2e6..0000000
--- a/src/com/android/launcher3/AutoScroller.java
+++ /dev/null
@@ -1,423 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.SystemClock;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.widget.AbsListView;
-
-class AutoScroller implements View.OnTouchListener, Runnable {
-    private static final int SCALE_RELATIVE = 0;
-    private static final int SCALE_ABSOLUTE = 1;
-
-    private final View mTarget;
-    private final RampUpScroller mScroller;
-
-    /** Interpolator used to scale velocity with touch position, may be null. */
-    private Interpolator mEdgeInterpolator = new AccelerateInterpolator();
-
-    /**
-     * Type of maximum velocity scaling to use, one of:
-     * <ul>
-     * <li>{@link #SCALE_RELATIVE}
-     * <li>{@link #SCALE_ABSOLUTE}
-     * </ul>
-     */
-    private int mMaxVelocityScale = SCALE_RELATIVE;
-
-    /**
-     * Type of activation edge scaling to use, one of:
-     * <ul>
-     * <li>{@link #SCALE_RELATIVE}
-     * <li>{@link #SCALE_ABSOLUTE}
-     * </ul>
-     */
-    private int mActivationEdgeScale = SCALE_RELATIVE;
-
-    /** Edge insets used to activate auto-scrolling. */
-    private RectF mActivationEdges = new RectF(0.2f, 0.2f, 0.2f, 0.2f);
-
-    /** Delay after entering an activation edge before auto-scrolling begins. */
-    private int mActivationDelay;
-
-    /** Maximum horizontal scrolling velocity. */
-    private float mMaxVelocityX = 0.001f;
-
-    /** Maximum vertical scrolling velocity. */
-    private float mMaxVelocityY = 0.001f;
-
-    /**
-     * Whether positive insets should also extend beyond the view bounds when
-     * auto-scrolling is already active. This allows a user to start scrolling
-     * at an inside edge, then move beyond the edge and continue scrolling.
-     */
-    private boolean mExtendsBeyondEdges = true;
-
-    /** Whether to start activation immediately. */
-    private boolean mSkipDelay;
-
-    /** Whether to reset the scroller start time on the next animation. */
-    private boolean mResetScroller;
-
-    /** Whether the auto-scroller is active. */
-    private boolean mActive;
-    private long[] mScrollStart = new long[2];
-
-    /**
-     * If the event is within this percentage of the edge of the scrolling area,
-     * use accelerated scrolling.
-     */
-    private float mFastScrollingRange = 0.8f;
-
-    /**
-     * Duration of time spent in accelerated scrolling area before reaching
-     * maximum velocity
-     */
-    private float mDurationToMax = 2500f;
-
-    private static final int X = 0;
-    private static final int Y = 1;
-
-    public AutoScroller(View target) {
-        mTarget = target;
-        mScroller = new RampUpScroller(250);
-        mActivationDelay = ViewConfiguration.getTapTimeout();
-    }
-
-    /**
-     * Sets the maximum scrolling velocity as a fraction of the host view size
-     * per second. For example, a maximum Y velocity of 1 would scroll one
-     * vertical page per second. By default, both values are 1.
-     *
-     * @param x The maximum X velocity as a fraction of the host view width per
-     *            second.
-     * @param y The maximum Y velocity as a fraction of the host view height per
-     *            second.
-     */
-    public void setMaximumVelocityRelative(float x, float y) {
-        mMaxVelocityScale = SCALE_RELATIVE;
-        mMaxVelocityX = x / 1000f;
-        mMaxVelocityY = y / 1000f;
-    }
-
-    /**
-     * Sets the maximum scrolling velocity as an absolute pixel distance per
-     * second. For example, a maximum Y velocity of 100 would scroll one hundred
-     * pixels per second.
-     *
-     * @param x The maximum X velocity as a fraction of the host view width per
-     *            second.
-     * @param y The maximum Y velocity as a fraction of the host view height per
-     *            second.
-     */
-    public void setMaximumVelocityAbsolute(float x, float y) {
-        mMaxVelocityScale = SCALE_ABSOLUTE;
-        mMaxVelocityX = x / 1000f;
-        mMaxVelocityY = y / 1000f;
-    }
-
-    /**
-     * Sets the delay after entering an activation edge before activation of
-     * auto-scrolling. By default, the activation delay is set to
-     * {@link ViewConfiguration#getTapTimeout()}.
-     *
-     * @param delayMillis The delay in milliseconds.
-     */
-    public void setActivationDelay(int delayMillis) {
-        mActivationDelay = delayMillis;
-    }
-
-    /**
-     * Sets the activation edges in pixels. Edges are treated as insets, so
-     * positive values expand into the view bounds while negative values extend
-     * outside the bounds.
-     *
-     * @param l The left activation edge, in pixels.
-     * @param t The top activation edge, in pixels.
-     * @param r The right activation edge, in pixels.
-     * @param b The bottom activation edge, in pixels.
-     */
-    public void setEdgesAbsolute(int l, int t, int r, int b) {
-        mActivationEdgeScale = SCALE_ABSOLUTE;
-        mActivationEdges.set(l, t, r, b);
-    }
-
-    /**
-     * Whether positive insets should also extend beyond the view bounds when
-     * auto-scrolling is already active. This allows a user to start scrolling
-     * at an inside edge, then move beyond the edge and continue scrolling.
-     *
-     * @param e
-     */
-    public void setExtendsBeyondEdges(boolean e) {
-        mExtendsBeyondEdges = e;
-    }
-
-    /**
-     * Sets the activation edges as fractions of the host view size. Edges are
-     * treated as insets, so positive values expand into the view bounds while
-     * negative values extend outside the bounds. By default, all values are
-     * 0.25.
-     *
-     * @param l The left activation edge, as a fraction of view size.
-     * @param t The top activation edge, as a fraction of view size.
-     * @param r The right activation edge, as a fraction of view size.
-     * @param b The bottom activation edge, as a fraction of view size.
-     */
-    public void setEdgesRelative(float l, float t, float r, float b) {
-        mActivationEdgeScale = SCALE_RELATIVE;
-        mActivationEdges.set(l, t, r, b);
-    }
-
-    /**
-     * Sets the {@link Interpolator} used for scaling touches within activation
-     * edges. By default, uses the {@link AccelerateInterpolator} to gradually
-     * speed up scrolling.
-     *
-     * @param edgeInterpolator The interpolator to use for activation edges, or
-     *            {@code null} to use a fixed velocity during auto-scrolling.
-     */
-    public void setEdgeInterpolator(Interpolator edgeInterpolator) {
-        mEdgeInterpolator = edgeInterpolator;
-    }
-
-    /**
-     * Stop tracking scrolling.
-     */
-    public void stop() {
-        stop(true);
-    }
-
-    /**
-     * Pass the rectangle defining the drawing region for the object used to
-     * trigger drag scrolling.
-     *
-     * @param v View on which the scrolling regions are defined
-     * @param r Rect defining the drawing bounds of the object being dragged
-     * @return whether the event was handled
-     */
-    public boolean onTouch(View v, Rect r) {
-        MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
-                SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE, r.left, r.top, 0);
-        return onTouch(v, event);
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        final int action = event.getActionMasked();
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_MOVE:
-                final int sourceWidth = v.getWidth();
-                final int sourceHeight = v.getHeight();
-                final float x = event.getX();
-                final float y = event.getY();
-                final float l;
-                final float t;
-                final float r;
-                final float b;
-                final RectF activationEdges = mActivationEdges;
-                if (mActivationEdgeScale == SCALE_ABSOLUTE) {
-                    l = activationEdges.left;
-                    t = activationEdges.top;
-                    r = activationEdges.right;
-                    b = activationEdges.bottom;
-                } else {
-                    l = activationEdges.left * sourceWidth;
-                    t = activationEdges.top * sourceHeight;
-                    r = activationEdges.right * sourceWidth;
-                    b = activationEdges.bottom * sourceHeight;
-                }
-
-                final float maxVelX;
-                final float maxVelY;
-                if (mMaxVelocityScale == SCALE_ABSOLUTE) {
-                    maxVelX = mMaxVelocityX;
-                    maxVelY = mMaxVelocityY;
-                } else {
-                    maxVelX = mMaxVelocityX * mTarget.getWidth();
-                    maxVelY = mMaxVelocityY * mTarget.getHeight();
-                }
-
-                final float velocityX = getEdgeVelocity(X, l, r, x, sourceWidth, event);
-                final float velocityY = getEdgeVelocity(Y, t, b, y, sourceHeight, event);
-                mScroller.setTargetVelocity(velocityX * maxVelX, velocityY * maxVelY);
-
-                if ((velocityX != 0 || velocityY != 0) && !mActive) {
-                    mActive = true;
-                    mResetScroller = true;
-                    if (mSkipDelay) {
-                        mTarget.postOnAnimation(this);
-                    } else {
-                        mSkipDelay = true;
-                        mTarget.postOnAnimationDelayed(this, mActivationDelay);
-                    }
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                stop(true);
-                break;
-        }
-
-        return false;
-    }
-
-    /**
-     * @param leading Size of the leading activation inset.
-     * @param trailing Size of the trailing activation inset.
-     * @param current Position within within the total area.
-     * @param size Size of the total area.
-     * @return The fraction of the activation area.
-     */
-    private float getEdgeVelocity(int dir, float leading, float trailing,
-            float current, float size, MotionEvent ev) {
-        float valueLeading = 0;
-        if (leading > 0) {
-            if (current < leading) {
-                if (current > 0) {
-                    // Movement up to the edge is scaled.
-                    valueLeading = 1f - current / leading;
-                } else if (mActive && mExtendsBeyondEdges) {
-                    // Movement beyond the edge is always maximum.
-                    valueLeading = 1f;
-                }
-            }
-        } else if (leading < 0) {
-            if (current < 0) {
-                // Movement beyond the edge is scaled.
-                valueLeading = current / leading;
-            }
-        }
-
-        float valueTrailing = 0;
-        if (trailing > 0) {
-            if (current > size - trailing) {
-                if (current < size) {
-                    // Movement up to the edge is scaled.
-                    valueTrailing = 1f - (size - current) / trailing;
-                } else if (mActive && mExtendsBeyondEdges) {
-                    // Movement beyond the edge is always maximum.
-                    valueTrailing = 1f;
-                }
-            }
-        } else if (trailing < 0) {
-            if (current > size) {
-                // Movement beyond the edge is scaled.
-                valueTrailing = (size - current) / trailing;
-            }
-        }
-
-        float value = (valueTrailing - valueLeading);
-        if ((value > mFastScrollingRange || value < -mFastScrollingRange)
-            && mScrollStart[dir] == 0) {
-            // within auto scrolling area
-            mScrollStart[dir] = ev.getEventTime();
-        } else {
-            // Outside fast scrolling area; reset duration
-            mScrollStart[dir] = 0;
-        }
-        final float duration = (ev.getEventTime() - mScrollStart[dir])/mDurationToMax;
-        final float interpolated;
-        if (value < 0) {
-            if (value < -mFastScrollingRange) {
-                // Close to top; use duration!
-                value += mEdgeInterpolator.getInterpolation(-duration);
-            }
-            interpolated = mEdgeInterpolator == null ? -1
-                    : -mEdgeInterpolator.getInterpolation(-value);
-        } else if (value > 0) {
-            // Close to bottom; use duration
-            if (value > mFastScrollingRange) {
-                // Close to bottom; use duration!
-                value += mEdgeInterpolator.getInterpolation(duration);
-            }
-            interpolated = mEdgeInterpolator == null ? 1
-                    : mEdgeInterpolator.getInterpolation(value);
-        } else {
-            mScrollStart[dir] = 0;
-            return 0;
-        }
-
-        return constrain(interpolated, -1, 1);
-    }
-
-    private static float constrain(float value, float min, float max) {
-        if (value > max) {
-            return max;
-        } else if (value < min) {
-            return min;
-        } else {
-            return value;
-        }
-    }
-
-    /**
-     * Stops auto-scrolling immediately, optionally reseting the auto-scrolling
-     * delay.
-     *
-     * @param reset Whether to reset the auto-scrolling delay.
-     */
-    private void stop(boolean reset) {
-        mActive = false;
-        mSkipDelay = !reset;
-        mTarget.removeCallbacks(this);
-    }
-
-    @Override
-    public void run() {
-        if (!mActive) {
-            return;
-        }
-
-        if (mResetScroller) {
-            mResetScroller = false;
-            mScroller.start();
-        }
-
-        final View target = mTarget;
-        final RampUpScroller scroller = mScroller;
-        final float targetVelocityX = scroller.getTargetVelocityX();
-        final float targetVelocityY = scroller.getTargetVelocityY();
-        if ((targetVelocityY == 0 || !target.canScrollVertically(targetVelocityY > 0 ? 1 : -1)
-                && (targetVelocityX == 0
-                        || !target.canScrollHorizontally(targetVelocityX > 0 ? 1 : -1)))) {
-            stop(false);
-            return;
-        }
-
-        scroller.computeScrollDelta();
-
-        final int deltaX = scroller.getDeltaX();
-        final int deltaY = scroller.getDeltaY();
-
-        if (target instanceof AbsListView) {
-            final AbsListView list = (AbsListView) target;
-            list.smoothScrollBy(deltaY, 0);
-        } else {
-            target.scrollBy(deltaX, deltaY);
-        }
-
-        target.postOnAnimation(this);
-    }
-}
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 01edb9e..09fad83 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -96,7 +96,7 @@
     }
 
     private boolean isAllAppsApplication(DragSource source, Object info) {
-        return (source instanceof AppsCustomizePagedView) && (info instanceof ApplicationInfo);
+        return (source instanceof AppsCustomizePagedView) && (info instanceof AppInfo);
     }
     private boolean isAllAppsWidget(DragSource source, Object info) {
         if (source instanceof AppsCustomizePagedView) {
@@ -149,7 +149,7 @@
                 item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
                 item instanceof ShortcutInfo) {
                 ShortcutInfo shortcutInfo = (ShortcutInfo) info;
-                return (shortcutInfo.flags & ApplicationInfo.DOWNLOADED_FLAG) != 0;
+                return (shortcutInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
             }
         }
         return false;
@@ -259,14 +259,14 @@
         mWaitingForUninstall = false;
         if (isAllAppsApplication(d.dragSource, item)) {
             // Uninstall the application if it is being dragged from AppsCustomize
-            ApplicationInfo appInfo = (ApplicationInfo) item;
+            AppInfo appInfo = (AppInfo) item;
             mLauncher.startApplicationUninstallActivity(appInfo.componentName, appInfo.flags);
         } else if (AppsCustomizePagedView.DISABLE_ALL_APPS && isWorkspaceOrFolderApplication(d)) {
             ShortcutInfo shortcut = (ShortcutInfo) item;
             if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
                 final ComponentName componentName = shortcut.intent.getComponent();
                 final DragSource dragSource = d.dragSource;
-                int flags = ApplicationInfo.initFlags(
+                int flags = AppInfo.initFlags(
                     ShortcutInfo.getPackageInfo(getContext(), componentName.getPackageName()));
                 mWaitingForUninstall =
                     mLauncher.startApplicationUninstallActivity(componentName, flags);
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index 07b2528..f51366f 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -327,13 +327,13 @@
         }
         endDrag();
     }
-    public void onAppsRemoved(ArrayList<ApplicationInfo> appInfos, Context context) {
+    public void onAppsRemoved(ArrayList<AppInfo> appInfos, Context context) {
         // Cancel the current drag if we are removing an app that we are dragging
         if (mDragObject != null) {
             Object rawDragInfo = mDragObject.dragInfo;
             if (rawDragInfo instanceof ShortcutInfo) {
                 ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
-                for (ApplicationInfo info : appInfos) {
+                for (AppInfo info : appInfos) {
                     // Added null checks to prevent NPE we've seen in the wild
                     if (dragInfo != null &&
                         dragInfo.intent != null) {
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index f57ff07..808d85d 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -25,6 +25,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
 import android.text.InputType;
 import android.text.Selection;
 import android.text.Spannable;
@@ -107,8 +108,6 @@
     private float mFolderIconPivotX;
     private float mFolderIconPivotY;
 
-    private static final float MAX_SCROLL_VELOCITY = 1500f;
-
     private boolean mIsEditingName = false;
     private InputMethodManager mInputMethodManager;
 
@@ -121,7 +120,7 @@
 
     private boolean mDestroyed;
 
-    private AutoScroller mAutoScroller;
+    private AutoScrollHelper mAutoScrollHelper;
 
     private Runnable mDeferredAction;
     private boolean mDeferDropAfterUninstall;
@@ -193,9 +192,7 @@
         mFolderName.setSelectAllOnFocus(true);
         mFolderName.setInputType(mFolderName.getInputType() |
                 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
-        mAutoScroller = new AutoScroller(mScrollView);
-        mAutoScroller.setMaximumVelocityAbsolute(MAX_SCROLL_VELOCITY, MAX_SCROLL_VELOCITY);
-        mAutoScroller.setExtendsBeyondEdges(false);
+        mAutoScrollHelper = new FolderAutoScrollHelper(mScrollView);
     }
 
     private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
@@ -626,24 +623,29 @@
         return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
     }
 
-    private Rect getDragObjectDrawingRect(View dragView, float[] r) {
-        final Rect drawingRect = mTempRect;
-        drawingRect.left = (int) r[0];
-        drawingRect.top = (int) r[1];
-        drawingRect.right = drawingRect.left + dragView.getMeasuredWidth();
-        drawingRect.bottom = drawingRect.top + dragView.getMeasuredHeight();
-        return drawingRect;
-    }
-
     public void onDragOver(DragObject d) {
-        int scrollOffset = mScrollView.getScrollY();
-        float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null);
+        final DragView dragView = d.dragView;
+        final int scrollOffset = mScrollView.getScrollY();
+        final float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, dragView, null);
         r[0] -= getPaddingLeft();
         r[1] -= getPaddingTop();
-        if (!mAutoScroller.onTouch(this, getDragObjectDrawingRect(d.dragView, r))) {
-            mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1] + scrollOffset, 1, 1,
-                    mTargetCell);
 
+        final long downTime = SystemClock.uptimeMillis();
+        final MotionEvent translatedEv = MotionEvent.obtain(
+                downTime, downTime, MotionEvent.ACTION_MOVE, d.x, d.y, 0);
+
+        if (!mAutoScrollHelper.isEnabled()) {
+            mAutoScrollHelper.setEnabled(true);
+        }
+
+        final boolean handled = mAutoScrollHelper.onTouch(this, translatedEv);
+        translatedEv.recycle();
+
+        if (handled) {
+            mReorderAlarm.cancelAlarm();
+        } else {
+            mTargetCell = mContent.findNearestArea(
+                    (int) r[0], (int) r[1] + scrollOffset, 1, 1, mTargetCell);
             if (isLayoutRtl()) {
                 mTargetCell[0] = mContent.getCountX() - mTargetCell[0] - 1;
             }
@@ -658,8 +660,6 @@
             } else {
                 mDragMode = DRAG_MODE_NONE;
             }
-        } else {
-            mReorderAlarm.cancelAlarm();
         }
     }
 
@@ -705,7 +705,7 @@
 
     public void onDragExit(DragObject d) {
         // Exiting folder; stop the auto scroller.
-        mAutoScroller.stop();
+        mAutoScrollHelper.setEnabled(false);
         // We only close the folder if this is a true drag exit, ie. not because
         // a drop has occurred above the folder.
         if (!d.dragComplete) {
@@ -1090,9 +1090,9 @@
 
     public void onDrop(DragObject d) {
         ShortcutInfo item;
-        if (d.dragInfo instanceof ApplicationInfo) {
+        if (d.dragInfo instanceof AppInfo) {
             // Came from all apps -- make a copy
-            item = ((ApplicationInfo) d.dragInfo).makeShortcut();
+            item = ((AppInfo) d.dragInfo).makeShortcut();
             item.spanX = 1;
             item.spanY = 1;
         } else {
diff --git a/src/com/android/launcher3/FolderAutoScrollHelper.java b/src/com/android/launcher3/FolderAutoScrollHelper.java
new file mode 100644
index 0000000..d7e6770
--- /dev/null
+++ b/src/com/android/launcher3/FolderAutoScrollHelper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.widget.ScrollView;
+
+/**
+ * An implementation of {@link AutoScrollHelper} that knows how to scroll
+ * through a {@link Folder}.
+ */
+public class FolderAutoScrollHelper extends AutoScrollHelper {
+    private static final float MAX_SCROLL_VELOCITY = 1500f;
+
+    private final ScrollView mTarget;
+
+    public FolderAutoScrollHelper(ScrollView target) {
+        super(target);
+
+        mTarget = target;
+
+        setActivationDelay(0);
+        setEdgeType(EDGE_TYPE_INSIDE);
+        setExclusive(true);
+        setMaximumVelocity(MAX_SCROLL_VELOCITY, MAX_SCROLL_VELOCITY);
+    }
+
+    @Override
+    public void scrollTargetBy(int deltaX, int deltaY) {
+        mTarget.scrollBy(deltaX, deltaY);
+    }
+
+    @Override
+    public boolean canTargetScrollHorizontally(int direction) {
+        // List do not scroll horizontally.
+        return false;
+    }
+
+    @Override
+    public boolean canTargetScrollVertically(int direction) {
+        return mTarget.canScrollVertically(direction);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index fd83370..c00c9f8 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -429,9 +429,9 @@
 
     public void onDrop(DragObject d) {
         ShortcutInfo item;
-        if (d.dragInfo instanceof ApplicationInfo) {
+        if (d.dragInfo instanceof AppInfo) {
             // Came from all apps -- make a copy
-            item = ((ApplicationInfo) d.dragInfo).makeShortcut();
+            item = ((AppInfo) d.dragInfo).makeShortcut();
         } else {
             item = (ShortcutInfo) d.dragInfo;
         }
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 2d3adf6..ccdc56b 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -105,7 +105,7 @@
     }
 
     void addAllAppsFolder(IconCache iconCache,
-            ArrayList<ApplicationInfo> allApps, ArrayList<ComponentName> onWorkspace,
+            ArrayList<AppInfo> allApps, ArrayList<ComponentName> onWorkspace,
             Launcher launcher, Workspace workspace) {
         FolderInfo fi = new FolderInfo();
 
@@ -124,7 +124,7 @@
         workspace.addInScreen(folder, fi.container, fi.screenId, fi.cellX, fi.cellY,
                 fi.spanX, fi.spanY);
 
-        for (ApplicationInfo info: allApps) {
+        for (AppInfo info: allApps) {
             ComponentName cn = info.intent.getComponent();
             if (!onWorkspace.contains(cn)) {
                 Log.d(TAG, "Adding to 'more apps': " + info.intent);
@@ -134,7 +134,7 @@
         }
     }
 
-    void addAppsToAllAppsFolder(ArrayList<ApplicationInfo> apps) {
+    void addAppsToAllAppsFolder(ArrayList<AppInfo> apps) {
         View v = mContent.getChildAt(getCellXFromOrder(mAllAppsButtonRank), getCellYFromOrder(mAllAppsButtonRank));
         FolderIcon fi = null;
 
@@ -145,7 +145,7 @@
         }
 
         FolderInfo info = fi.getFolderInfo();
-        for (ApplicationInfo a: apps) {
+        for (AppInfo a: apps) {
             ShortcutInfo si = a.makeShortcut();
             info.add(si);
         }
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 6fc40e3..1797826 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -149,7 +149,7 @@
     /**
      * Fill in "application" with the icon and label for "info."
      */
-    public void getTitleAndIcon(ApplicationInfo application, ResolveInfo info,
+    public void getTitleAndIcon(AppInfo application, ResolveInfo info,
             HashMap<Object, CharSequence> labelCache) {
         synchronized (mCache) {
             CacheEntry entry = cacheLocked(application.componentName, info, labelCache);
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index a643a0d..2ad43b6 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -72,8 +72,8 @@
         // in onDrop, because it allows us to reject the drop (by returning false)
         // so that the object being dragged isn't removed from the drag source.
         ComponentName componentName = null;
-        if (d.dragInfo instanceof ApplicationInfo) {
-            componentName = ((ApplicationInfo) d.dragInfo).componentName;
+        if (d.dragInfo instanceof AppInfo) {
+            componentName = ((AppInfo) d.dragInfo).componentName;
         } else if (d.dragInfo instanceof ShortcutInfo) {
             componentName = ((ShortcutInfo) d.dragInfo).intent.getComponent();
         } else if (d.dragInfo instanceof PendingAddItemInfo) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 509560c..81a962d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -113,8 +113,6 @@
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -2071,14 +2069,17 @@
         sFolders.remove(folder.id);
     }
 
-    private void startWallpaper() {
+    protected void startWallpaper() {
         showWorkspace(true);
         final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
-        pickWallpaper.setComponent(
-                new ComponentName(getPackageName(), WallpaperPickerActivity.class.getName()));
+        pickWallpaper.setComponent(getWallpaperPickerComponent());
         startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
     }
 
+    protected ComponentName getWallpaperPickerComponent() {
+        return new ComponentName(getPackageName(), WallpaperPickerActivity.class.getName());
+    }
+
     /**
      * Registers various content observers. The current implementation registers
      * only a favorites observer to keep track of the favorites applications.
@@ -2298,7 +2299,7 @@
 
     // returns true if the activity was started
     boolean startApplicationUninstallActivity(ComponentName componentName, int flags) {
-        if ((flags & ApplicationInfo.DOWNLOADED_FLAG) == 0) {
+        if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
             // System applications cannot be installed. For now, show a toast explaining that.
             // We may give them the option of disabling apps this way.
             int messageId = R.string.uninstall_system_app_text;
@@ -3873,7 +3874,7 @@
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
+    public void bindAllApplications(final ArrayList<AppInfo> apps) {
         if (mIntentsOnWorkspaceFromUpgradePath != null) {
             if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
                 getHotseat().addAllAppsFolder(mIconCache, apps,
@@ -3888,7 +3889,7 @@
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void bindAppsUpdated(final ArrayList<ApplicationInfo> apps) {
+    public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
         Runnable r = new Runnable() {
             public void run() {
                 bindAppsUpdated(apps);
@@ -3913,7 +3914,7 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void bindComponentsRemoved(final ArrayList<String> packageNames,
-                                      final ArrayList<ApplicationInfo> appInfos,
+                                      final ArrayList<AppInfo> appInfos,
                                       final boolean packageRemoved) {
         Runnable r = new Runnable() {
             public void run() {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 338b9c3..0ebeec3 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -156,13 +156,13 @@
         public void bindFolders(HashMap<Long,FolderInfo> folders);
         public void finishBindingItems(boolean upgradePath);
         public void bindAppWidget(LauncherAppWidgetInfo info);
-        public void bindAllApplications(ArrayList<ApplicationInfo> apps);
+        public void bindAllApplications(ArrayList<AppInfo> apps);
         public void bindAppsAdded(ArrayList<Long> newScreens,
                                   ArrayList<ItemInfo> addNotAnimated,
                                   ArrayList<ItemInfo> addAnimated);
-        public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
+        public void bindAppsUpdated(ArrayList<AppInfo> apps);
         public void bindComponentsRemoved(ArrayList<String> packageNames,
-                        ArrayList<ApplicationInfo> appInfos,
+                        ArrayList<AppInfo> appInfos,
                         boolean matchPackageNamesOnly);
         public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
         public void bindSearchablesChanged();
@@ -347,8 +347,8 @@
                         ShortcutInfo shortcutInfo;
                         if (a instanceof ShortcutInfo) {
                             shortcutInfo = (ShortcutInfo) a;
-                        } else if (a instanceof ApplicationInfo) {
-                            shortcutInfo = ((ApplicationInfo) a).makeShortcut();
+                        } else if (a instanceof AppInfo) {
+                            shortcutInfo = ((AppInfo) a).makeShortcut();
                         } else {
                             throw new RuntimeException("Unexpected info type");
                         }
@@ -1544,7 +1544,7 @@
             ArrayList<ItemInfo> tmpInfos;
             ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
             synchronized (sBgLock) {
-                for (ApplicationInfo app : mBgAllAppsList.data) {
+                for (AppInfo app : mBgAllAppsList.data) {
                     tmpInfos = getItemInfoForComponentName(app.componentName);
                     Launcher.addDumpLog(TAG, "10249126 - \t" + app.componentName.getPackageName() + ", " + tmpInfos.isEmpty(), true);
                     if (tmpInfos.isEmpty()) {
@@ -2397,8 +2397,8 @@
 
             // shallow copy
             @SuppressWarnings("unchecked")
-            final ArrayList<ApplicationInfo> list
-                    = (ArrayList<ApplicationInfo>) mBgAllAppsList.data.clone();
+            final ArrayList<AppInfo> list
+                    = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
             Runnable r = new Runnable() {
                 public void run() {
                     final long t = SystemClock.uptimeMillis();
@@ -2463,13 +2463,13 @@
             // Create the ApplicationInfos
             for (int i = 0; i < apps.size(); i++) {
                 // This builds the icon bitmaps.
-                mBgAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
+                mBgAllAppsList.add(new AppInfo(packageManager, apps.get(i),
                         mIconCache, mLabelCache));
             }
 
             final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
-            final ArrayList<ApplicationInfo> added = mBgAllAppsList.added;
-            mBgAllAppsList.added = new ArrayList<ApplicationInfo>();
+            final ArrayList<AppInfo> added = mBgAllAppsList.added;
+            mBgAllAppsList.added = new ArrayList<AppInfo>();
 
             // Post callback on main thread
             mHandler.post(new Runnable() {
@@ -2555,16 +2555,16 @@
                     break;
             }
 
-            ArrayList<ApplicationInfo> added = null;
-            ArrayList<ApplicationInfo> modified = null;
-            final ArrayList<ApplicationInfo> removedApps = new ArrayList<ApplicationInfo>();
+            ArrayList<AppInfo> added = null;
+            ArrayList<AppInfo> modified = null;
+            final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
 
             if (mBgAllAppsList.added.size() > 0) {
-                added = new ArrayList<ApplicationInfo>(mBgAllAppsList.added);
+                added = new ArrayList<AppInfo>(mBgAllAppsList.added);
                 mBgAllAppsList.added.clear();
             }
             if (mBgAllAppsList.modified.size() > 0) {
-                modified = new ArrayList<ApplicationInfo>(mBgAllAppsList.modified);
+                modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
                 mBgAllAppsList.modified.clear();
             }
             if (mBgAllAppsList.removed.size() > 0) {
@@ -2585,10 +2585,10 @@
                 addAndBindAddedApps(context, addedInfos, cb);
             }
             if (modified != null) {
-                final ArrayList<ApplicationInfo> modifiedFinal = modified;
+                final ArrayList<AppInfo> modifiedFinal = modified;
 
                 // Update the launcher db to reflect the changes
-                for (ApplicationInfo a : modifiedFinal) {
+                for (AppInfo a : modifiedFinal) {
                     ArrayList<ItemInfo> infos =
                             getItemInfoForComponentName(a.componentName);
                     for (ItemInfo i : infos) {
@@ -2625,7 +2625,7 @@
                         }
                     }
                 } else {
-                    for (ApplicationInfo a : removedApps) {
+                    for (AppInfo a : removedApps) {
                         ArrayList<ItemInfo> infos =
                                 getItemInfoForComponentName(a.componentName);
                         for (ItemInfo i : infos) {
@@ -3120,10 +3120,10 @@
         return folderInfo;
     }
 
-    public static final Comparator<ApplicationInfo> getAppNameComparator() {
+    public static final Comparator<AppInfo> getAppNameComparator() {
         final Collator collator = Collator.getInstance();
-        return new Comparator<ApplicationInfo>() {
-            public final int compare(ApplicationInfo a, ApplicationInfo b) {
+        return new Comparator<AppInfo>() {
+            public final int compare(AppInfo a, AppInfo b) {
                 int result = collator.compare(a.title.toString(), b.title.toString());
                 if (result == 0) {
                     result = a.componentName.compareTo(b.componentName);
@@ -3132,9 +3132,9 @@
             }
         };
     }
-    public static final Comparator<ApplicationInfo> APP_INSTALL_TIME_COMPARATOR
-            = new Comparator<ApplicationInfo>() {
-        public final int compare(ApplicationInfo a, ApplicationInfo b) {
+    public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR
+            = new Comparator<AppInfo>() {
+        public final int compare(AppInfo a, AppInfo b) {
             if (a.firstInstallTime < b.firstInstallTime) return 1;
             if (a.firstInstallTime > b.firstInstallTime) return -1;
             return 0;
@@ -3223,10 +3223,10 @@
 
     public void dumpState() {
         Log.d(TAG, "mCallbacks=" + mCallbacks);
-        ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
-        ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
-        ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
-        ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
+        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
+        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
+        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
+        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
         if (mLoaderTask != null) {
             mLoaderTask.dumpState();
         } else {
diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java
index 73f62d6..501bc8f 100644
--- a/src/com/android/launcher3/PagedViewIcon.java
+++ b/src/com/android/launcher3/PagedViewIcon.java
@@ -52,7 +52,7 @@
         super(context, attrs, defStyle);
     }
 
-    public void applyFromApplicationInfo(ApplicationInfo info, boolean scaleUp,
+    public void applyFromApplicationInfo(AppInfo info, boolean scaleUp,
             PagedViewIcon.PressedCallback cb) {
         mIcon = info.iconBitmap;
         mPressedCallback = cb;
diff --git a/src/com/android/launcher3/PagedViewIconCache.java b/src/com/android/launcher3/PagedViewIconCache.java
index 0d03b5a..8d8924b 100644
--- a/src/com/android/launcher3/PagedViewIconCache.java
+++ b/src/com/android/launcher3/PagedViewIconCache.java
@@ -40,7 +40,7 @@
         private final ComponentName mComponentName;
         private final Type mType;
 
-        public Key(ApplicationInfo info) {
+        public Key(AppInfo info) {
             mComponentName = info.componentName;
             mType = Type.ApplicationInfoKey;
         }
@@ -95,9 +95,9 @@
         }
     }
     /** Removes all the keys to applications that aren't in the passed in collection */
-    public void retainAllApps(ArrayList<ApplicationInfo> keys) {
+    public void retainAllApps(ArrayList<AppInfo> keys) {
         HashSet<Key> keysSet = new HashSet<Key>();
-        for (ApplicationInfo info : keys) {
+        for (AppInfo info : keys) {
             keysSet.add(new Key(info));
         }
         retainAll(keysSet, Key.Type.ApplicationInfoKey);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index b4e5642..dafabb8 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -88,7 +88,7 @@
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
-    public ShortcutInfo(ApplicationInfo info) {
+    public ShortcutInfo(AppInfo info) {
         super(info);
         title = info.title.toString();
         intent = new Intent(info.intent);
@@ -109,8 +109,8 @@
     }
 
     void initFlagsAndFirstInstallTime(PackageInfo pi) {
-        flags = ApplicationInfo.initFlags(pi);
-        firstInstallTime = ApplicationInfo.initFirstInstallTime(pi);
+        flags = AppInfo.initFlags(pi);
+        firstInstallTime = AppInfo.initFirstInstallTime(pi);
     }
 
     public void setIcon(Bitmap b) {
diff --git a/src/com/android/launcher3/WallpaperCropActivity.java b/src/com/android/launcher3/WallpaperCropActivity.java
index b087506..3468f2f 100644
--- a/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/src/com/android/launcher3/WallpaperCropActivity.java
@@ -20,6 +20,7 @@
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
@@ -73,8 +74,10 @@
         boolean mSaveCroppedBitmap;
         Bitmap mCroppedBitmap;
         Runnable mOnEndRunnable;
+        Resources mResources;
 
-        public BitmapCropTask(Uri inUri, RectF cropBounds, int outWidth, int outHeight,
+        public BitmapCropTask(Uri inUri,
+                RectF cropBounds, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
             mInUri = inUri;
             mCropBounds = cropBounds;
@@ -86,7 +89,8 @@
             mOnEndRunnable = onEndRunnable;
         }
 
-        public BitmapCropTask(int inResId, RectF cropBounds, int outWidth, int outHeight,
+        public BitmapCropTask(Resources res, int inResId,
+                RectF cropBounds, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
             mInResId = inResId;
             mCropBounds = cropBounds;
@@ -96,6 +100,7 @@
             mSetWallpaper = setWallpaper;
             mSaveCroppedBitmap = saveCroppedBitmap;
             mOnEndRunnable = onEndRunnable;
+            mResources = res;
         }
 
         // Helper to setup input stream
@@ -110,7 +115,7 @@
                                 getContentResolver().openInputStream(mInUri));
                     } else {
                         mInStream = new BufferedInputStream(
-                                getResources().openRawResource(mInResId));
+                                mResources.openRawResource(mInResId));
                     }
                 } catch (FileNotFoundException e) {
                     Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java
index 536775a..8f323e8 100644
--- a/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
@@ -34,6 +35,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
+import android.util.Pair;
 import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -63,8 +65,9 @@
     private static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
     private static final float WALLPAPER_SCREENS_SPAN = 2f;
 
-    private ArrayList<Integer> mThumbs;
+    private ArrayList<Drawable> mThumbs;
     private ArrayList<Integer> mImages;
+    private Resources mWallpaperResources;
 
     private View mSelectedThumb;
     private CropView mCropView;
@@ -98,7 +101,7 @@
                         meta.mGalleryImageUri, 1024, 0), null);
                 mCropView.setTouchEnabled(true);
             } else {
-                BitmapRegionTileSource source = new BitmapRegionTileSource(
+                BitmapRegionTileSource source = new BitmapRegionTileSource(mWallpaperResources,
                         WallpaperPickerActivity.this, meta.mWallpaperResId, 1024, 0);
                 mCropView.setTileSource(source, null);
                 Point wallpaperSize = getDefaultWallpaperSize(getResources(), getWindowManager());
@@ -311,8 +314,8 @@
                                     updateWallpaperDimensions(0, 0);
                                 }
                             };
-                            BitmapCropTask cropTask = new BitmapCropTask(meta.mWallpaperResId,
-                                    crop, outSize.x, outSize.y,
+                            BitmapCropTask cropTask = new BitmapCropTask(mWallpaperResources,
+                                    meta.mWallpaperResId, crop, outSize.x, outSize.y,
                                     true, false, onEndCrop);
                             cropTask.execute();
                             setResult(Activity.RESULT_OK);
@@ -389,22 +392,35 @@
     }
 
     private void findWallpapers() {
-        mThumbs = new ArrayList<Integer>(24);
+        mThumbs = new ArrayList<Drawable>(24);
         mImages = new ArrayList<Integer>(24);
 
-        final Resources resources = getResources();
+        Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
+        if (r != null) {
+            try {
+                mWallpaperResources = getPackageManager().getResourcesForApplication(r.first);
+                addWallpapers(mWallpaperResources, r.first.packageName, r.second);
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+        }
+    }
+
+    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 = resources.getResourcePackageName(R.array.wallpapers);
-
-        addWallpapers(resources, packageName, R.array.wallpapers);
-        addWallpapers(resources, packageName, R.array.extra_wallpapers);
+        final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
+        try {
+            ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
+            return new Pair<ApplicationInfo, Integer>(info, R.array.wallpapers);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
     }
 
-    private void addWallpapers(Resources resources, String packageName, int list) {
-        final String[] extras = resources.getStringArray(list);
+    private void addWallpapers(Resources resources, String packageName, int listResId) {
+        final String[] extras = resources.getStringArray(listResId);
         for (String extra : extras) {
             int res = resources.getIdentifier(extra, "drawable", packageName);
             if (res != 0) {
@@ -412,7 +428,7 @@
                         "drawable", packageName);
 
                 if (thumbRes != 0) {
-                    mThumbs.add(thumbRes);
+                    mThumbs.add(resources.getDrawable(thumbRes));
                     mImages.add(res);
                     // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
                 }
@@ -537,14 +553,12 @@
 
             ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
 
-            int thumbRes = mThumbs.get(position);
-            image.setImageResource(thumbRes);
-            Drawable thumbDrawable = image.getDrawable();
+            Drawable thumbDrawable = mThumbs.get(position);
             if (thumbDrawable != null) {
+                image.setImageDrawable(thumbDrawable);
                 thumbDrawable.setDither(true);
             } else {
-                Log.e(TAG, "Error decoding thumbnail resId=" + thumbRes + " for wallpaper #"
-                        + position);
+                Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
             }
 
             return view;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 339f0ab..01a8adf 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3246,9 +3246,9 @@
             switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                if (info.container == NO_ID && info instanceof ApplicationInfo) {
+                if (info.container == NO_ID && info instanceof AppInfo) {
                     // Came from all apps -- make a copy
-                    info = new ShortcutInfo((ApplicationInfo) info);
+                    info = new ShortcutInfo((AppInfo) info);
                 }
                 view = mLauncher.createShortcut(R.layout.application, cellLayout,
                         (ShortcutInfo) info);
@@ -3943,10 +3943,10 @@
     // Removes items that match the application info specified, when applications are removed
     // as a part of an update, this is called to ensure that other widgets and application
     // shortcuts are not removed.
-    void removeItemsByApplicationInfo(final ArrayList<ApplicationInfo> appInfos) {
+    void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) {
         // Just create a hash table of all the specific components that this will affect
         HashSet<ComponentName> cns = new HashSet<ComponentName>();
-        for (ApplicationInfo info : appInfos) {
+        for (AppInfo info : appInfos) {
             cns.add(info.componentName);
         }
 
@@ -4024,7 +4024,7 @@
         stripEmptyScreens();
     }
 
-    void updateShortcuts(ArrayList<ApplicationInfo> apps) {
+    void updateShortcuts(ArrayList<AppInfo> apps) {
         ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
         for (ShortcutAndWidgetContainer layout: childrenLayouts) {
             int childCount = layout.getChildCount();
@@ -4039,7 +4039,7 @@
                     final ComponentName name = intent.getComponent();
                     final int appCount = apps.size();
                     for (int k = 0; k < appCount; k++) {
-                        ApplicationInfo app = apps.get(k);
+                        AppInfo app = apps.get(k);
                         if (app.componentName.equals(name)) {
                             BubbleTextView shortcut = (BubbleTextView) view;
                             info.updateIcon(mIconCache);
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
index 9647485..5f64018 100644
--- a/src/com/android/photos/BitmapRegionTileSource.java
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -18,6 +18,7 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
@@ -68,18 +69,19 @@
     private Canvas mCanvas;
 
     public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
-        this(context, path, null, 0, previewSize, rotation);
+        this(null, context, path, null, 0, previewSize, rotation);
     }
 
     public BitmapRegionTileSource(Context context, Uri uri, int previewSize, int rotation) {
-        this(context, null, uri, 0, previewSize, rotation);
+        this(null, context, null, uri, 0, previewSize, rotation);
     }
 
-    public BitmapRegionTileSource(Context context, int resId, int previewSize, int rotation) {
-        this(context, null, null, resId, previewSize, rotation);
+    public BitmapRegionTileSource(Resources res,
+            Context context, int resId, int previewSize, int rotation) {
+        this(res, context, null, null, resId, previewSize, rotation);
     }
 
-    private BitmapRegionTileSource(
+    private BitmapRegionTileSource(Resources res,
             Context context, String path, Uri uri, int resId, int previewSize, int rotation) {
         mTileSize = TiledImageRenderer.suggestedTileSize(context);
         mRotation = rotation;
@@ -91,7 +93,7 @@
                 BufferedInputStream bis = new BufferedInputStream(is);
                 mDecoder = BitmapRegionDecoder.newInstance(bis, true);
             } else {
-                InputStream is = context.getResources().openRawResource(resId);
+                InputStream is = res.openRawResource(resId);
                 BufferedInputStream bis = new BufferedInputStream(is);
                 mDecoder = BitmapRegionDecoder.newInstance(bis, true);
             }
@@ -109,7 +111,7 @@
             // Although this is the same size as the Bitmap that is likely already
             // loaded, the lifecycle is different and interactions are on a different
             // thread. Thus to simplify, this source will decode its own bitmap.
-            Bitmap preview = decodePreview(context, path, uri, resId, previewSize);
+            Bitmap preview = decodePreview(res, context, path, uri, resId, previewSize);
             if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
                 mPreview = new BitmapTexture(preview);
             } else {
@@ -213,7 +215,8 @@
      * Note that the returned bitmap may have a long edge that's longer
      * than the targetSize, but it will always be less than 2x the targetSize
      */
-    private Bitmap decodePreview(Context context, String file, Uri uri, int resId, int targetSize) {
+    private Bitmap decodePreview(
+            Resources res, Context context, String file, Uri uri, int resId, int targetSize) {
         float scale = (float) targetSize / Math.max(mWidth, mHeight);
         mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
         mOptions.inJustDecodeBounds = false;
@@ -230,7 +233,7 @@
                 Log.w("BitmapRegionTileSource", "getting preview failed", e);
             }
         } else {
-            result = BitmapFactory.decodeResource(context.getResources(), resId, mOptions);
+            result = BitmapFactory.decodeResource(res, resId, mOptions);
         }
         if (result == null) {
             return null;