Import translations. DO NOT MERGE
am: d896d1c29f  -s ours

Change-Id: I05e716839408a72e4d1e922acf050e70efc533d1
diff --git a/protos/launcher_dump.proto b/protos/launcher_dump.proto
new file mode 100644
index 0000000..dc8fbda
--- /dev/null
+++ b/protos/launcher_dump.proto
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+syntax = "proto2";
+
+option java_package = "com.android.launcher3.model";
+option java_outer_classname = "LauncherDumpProto";
+
+package model;
+
+message DumpTarget {
+  enum Type {
+    NONE = 0;
+    ITEM = 1;
+    CONTAINER = 2;
+  }
+
+  optional Type type = 1;
+  optional int32 page_id = 2;
+  optional int32 grid_x = 3;
+  optional int32 grid_y = 4;
+
+  // For container types only
+  optional ContainerType container_type = 5;
+
+  // For item types only
+  optional ItemType item_type = 6;
+
+  optional string package_name = 7; // All ItemTypes except UNKNOWN type
+  optional string component = 8;   // All ItemTypes except UNKNOWN type
+  optional string item_id = 9; // For Pinned Shortcuts and appWidgetId
+
+  optional int32 span_x = 10 [default = 1];// Used for ItemType.WIDGET
+  optional int32 span_y = 11 [default = 1];// Used for ItemType.WIDGET
+  optional UserType user_type = 12;
+}
+
+// Used to define what type of item a Target would represent.
+enum ItemType {
+  UNKNOWN_ITEMTYPE = 0;  // Launcher specific items
+  APP_ICON = 1; // Regular app icons
+  WIDGET = 2;   // Elements from AppWidgetManager
+  SHORTCUT = 3; // ShortcutManager
+}
+
+// Used to define what type of container a Target would represent.
+enum ContainerType {
+  UNKNOWN_CONTAINERTYPE = 0;
+  WORKSPACE = 1;
+  HOTSEAT = 2;
+  FOLDER = 3;
+}
+
+// Used to define what type of control a Target would represent.
+enum UserType {
+  DEFAULT = 0;
+  WORK = 1;
+}
+
+// Main message;
+message LauncherImpression {
+  repeated DumpTarget targets = 1;
+}
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 8d16653..610dc10 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -33,10 +33,8 @@
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite i zadržite da biste izabrali vidžet ili koristite prilagođene radnje."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"širina od %1$d i visina od %2$d"</string>
-    <!-- no translation found for add_item_request_drag_hint (8662194377800507270) -->
-    <skip />
-    <!-- no translation found for place_automatically (1502491650329146581) -->
-    <skip />
+    <string name="add_item_request_drag_hint" msgid="8662194377800507270">"Dodirnite i zadržite da biste postavili na početni ekran"</string>
+    <string name="place_automatically" msgid="1502491650329146581">"Postavi automatski"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretražite aplikacije"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikacije se učitavaju..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nije pronađena nijedna aplikacija za „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
index f1b3aee..4487dbf 100644
--- a/res/values-be-rBY/strings.xml
+++ b/res/values-be-rBY/strings.xml
@@ -33,10 +33,8 @@
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Дакраніцеся двойчы і ўтрымлівайце, каб выбраць віджэт або выкарыстоўваць карыстальніцкія дзеянні."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Шырына: %1$d, вышыня: %2$d"</string>
-    <!-- no translation found for add_item_request_drag_hint (8662194377800507270) -->
-    <skip />
-    <!-- no translation found for place_automatically (1502491650329146581) -->
-    <skip />
+    <string name="add_item_request_drag_hint" msgid="8662194377800507270">"Дакраніцеся і ўтрымлівайце, каб размясціць на галоўным экране"</string>
+    <string name="place_automatically" msgid="1502491650329146581">"Размясціць аўтаматычна"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пошук у Праграмах"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ідзе загрузка праграм…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Праграм, якія адпавядаюць запыту \"<xliff:g id="QUERY">%1$s</xliff:g>\", не знойдзена"</string>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
index 4a658d2..28d3499 100644
--- a/res/values-bs-rBA/strings.xml
+++ b/res/values-bs-rBA/strings.xml
@@ -33,10 +33,8 @@
     <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite &amp; i držite da biste uzeli vidžet ili koristite prilagođene radnje."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, visina %2$d"</string>
-    <!-- no translation found for add_item_request_drag_hint (8662194377800507270) -->
-    <skip />
-    <!-- no translation found for place_automatically (1502491650329146581) -->
-    <skip />
+    <string name="add_item_request_drag_hint" msgid="8662194377800507270">"Dodirnite i držite da postavite na početni ekran"</string>
+    <string name="place_automatically" msgid="1502491650329146581">"Postavi automatski"</string>
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretraži aplikacije"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikacije se učitavaju…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nije pronađena nijedna aplikacija koja odgovara upitu \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index bad7018..3b8fb0a 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderIconPreviewVerifier;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.graphics.PreloadIconDrawable;
@@ -547,7 +548,9 @@
                 applyFromApplicationInfo((AppInfo) info);
             } else if (info instanceof ShortcutInfo) {
                 applyFromShortcutInfo((ShortcutInfo) info);
-                if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) {
+                FolderIconPreviewVerifier verifier =
+                        new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
+                if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) {
                     View folderIcon =
                             mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
                     if (folderIcon != null) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index dc668e6..384f202 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -48,6 +48,7 @@
 import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderIconPreviewVerifier;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
@@ -127,6 +128,11 @@
     // our monitoring of the package manager provides all updates and we never
     // need to do a requery. This is only ever touched from the loader thread.
     private boolean mModelLoaded;
+    public boolean isModelLoaded() {
+        synchronized (mLock) {
+            return mModelLoaded && mLoaderTask == null;
+        }
+    }
 
     /**
      * Set of runnables to be called on the background thread after the workspace binding
@@ -883,6 +889,8 @@
                     Intent intent;
                     String targetPkg;
 
+                    FolderIconPreviewVerifier verifier =
+                            new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
                     while (!mStopped && c.moveToNext()) {
                         try {
                             if (c.user == null) {
@@ -1007,7 +1015,7 @@
                                 }
 
                                 boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
-                                        c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
+                                        !verifier.isItemInPreview(c.getInt(rankIndex));
 
                                 if (c.restoreFlag != 0) {
                                     // Already verified above that user is same as default user
@@ -1259,17 +1267,23 @@
                     }
                 }
 
-                // Sort all the folder items and make sure the first 3 items are high resolution.
+                FolderIconPreviewVerifier verifier =
+                        new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
+                // Sort the folder items and make sure all items in the preview are high resolution.
                 for (FolderInfo folder : sBgDataModel.folders) {
                     Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
-                    int pos = 0;
+                    verifier.setFolderInfo(folder);
+
+                    int numItemsInPreview = 0;
                     for (ShortcutInfo info : folder.contents) {
-                        if (info.usingLowResIcon &&
-                                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                        if (info.usingLowResIcon
+                                && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                                && verifier.isItemInPreview(info.rank)) {
                             mIconCache.getTitleAndIcon(info, false);
+                            numItemsInPreview++;
                         }
-                        pos ++;
-                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+
+                        if (numItemsInPreview >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
                             break;
                         }
                     }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index e250b3f..e6d7181 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -47,6 +47,7 @@
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.ViewGroup;
 
 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -63,6 +64,8 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Thunk;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -85,6 +88,18 @@
 
     protected DatabaseHelper mOpenHelper;
 
+    /**
+     * $ adb shell dumpsys activity provider com.android.launcher3
+     */
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+        if (appState == null || !appState.getModel().isModelLoaded()) {
+            return;
+        }
+        appState.getModel().dumpState("", fd, writer, args);
+    }
+
     @Override
     public boolean onCreate() {
         if (ProviderConfig.IS_DOGFOOD_BUILD) {
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 39c466d..f5af979 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -32,7 +32,7 @@
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.CircleRevealOutlineProvider;
+import com.android.launcher3.anim.CircleRevealOutlineProvider;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.WidgetsContainerView;
 
diff --git a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java
similarity index 97%
rename from src/com/android/launcher3/util/CircleRevealOutlineProvider.java
rename to src/com/android/launcher3/anim/CircleRevealOutlineProvider.java
index 9fe5147..9fb6b49 100644
--- a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java
+++ b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.util;
+package com.android.launcher3.anim;
 
 public class CircleRevealOutlineProvider extends RevealOutlineAnimation {
 
diff --git a/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
index be1e2d6..679e8e3 100644
--- a/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
+++ b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java
@@ -18,8 +18,6 @@
 
 import android.graphics.Rect;
 
-import com.android.launcher3.util.PillRevealOutlineProvider;
-
 /**
  * Extension of {@link PillRevealOutlineProvider} which only changes the height of the pill.
  * For now, we assume the height is added/removed from the bottom.
diff --git a/src/com/android/launcher3/util/PillRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillRevealOutlineProvider.java
similarity index 98%
rename from src/com/android/launcher3/util/PillRevealOutlineProvider.java
rename to src/com/android/launcher3/anim/PillRevealOutlineProvider.java
index a57d69f..450f9db 100644
--- a/src/com/android/launcher3/util/PillRevealOutlineProvider.java
+++ b/src/com/android/launcher3/anim/PillRevealOutlineProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.util;
+package com.android.launcher3.anim;
 
 import android.graphics.Rect;
 import android.view.ViewOutlineProvider;
diff --git a/src/com/android/launcher3/util/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
similarity index 98%
rename from src/com/android/launcher3/util/RevealOutlineAnimation.java
rename to src/com/android/launcher3/anim/RevealOutlineAnimation.java
index 4560477..4b270db 100644
--- a/src/com/android/launcher3/util/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -1,4 +1,4 @@
-package com.android.launcher3.util;
+package com.android.launcher3.anim;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
new file mode 100644
index 0000000..9c09477
--- /dev/null
+++ b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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.anim;
+
+import android.graphics.Rect;
+
+/**
+ * A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
+ * and two {@link Rect}s.
+ *
+ * An example usage of this provider is an outline that starts out as a circle and ends
+ * as a rounded rectangle.
+ */
+public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation {
+    private final float mStartRadius;
+    private final float mEndRadius;
+
+    private final Rect mStartRect;
+    private final Rect mEndRect;
+
+    public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect,
+            Rect endRect) {
+        mStartRadius = startRadius;
+        mEndRadius = endRadius;
+        mStartRect = startRect;
+        mEndRect = endRect;
+    }
+
+    @Override
+    public boolean shouldRemoveElevationDuringAnimation() {
+        return false;
+    }
+
+    @Override
+    public void setProgress(float progress) {
+        mOutlineRadius = (1 - progress) * mStartRadius + progress * mEndRadius;
+
+        mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
+        mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
+        mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
+        mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
+    }
+}
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 840fcf5..503c2ec 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -121,6 +121,11 @@
     }
 
     @Override
+    public float getIconSize() {
+        return mIconSize;
+    }
+
+    @Override
     public int maxNumItems() {
         return MAX_NUM_ITEMS_IN_PREVIEW;
     }
@@ -129,24 +134,4 @@
     public boolean clipToBackground() {
         return true;
     }
-
-    @Override
-    public List<View> getItemsToDisplay(Folder folder) {
-        List<View> items = new ArrayList<>(folder.getItemsInReadingOrder());
-        int numItems = items.size();
-        if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION && numItems > MAX_NUM_ITEMS_IN_PREVIEW) {
-            // We match the icons in the preview with the layout of the opened folder (b/27944225),
-            // but we still need to figure out how we want to handle updating the preview when the
-            // upper left quadrant changes.
-            int appsPerRow = folder.mContent.getPageAt(0).getCountX();
-            int appsToDelete = appsPerRow - MAX_NUM_ITEMS_PER_ROW;
-
-            // We only display the upper left quadrant.
-            while (appsToDelete > 0) {
-                items.remove(MAX_NUM_ITEMS_PER_ROW);
-                appsToDelete--;
-            }
-        }
-        return items.subList(0, Math.min(numItems, MAX_NUM_ITEMS_IN_PREVIEW));
-    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 3d2ffb4..c63dd58 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -66,6 +66,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
+import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dragndrop.DragController;
@@ -75,7 +76,7 @@
 import com.android.launcher3.pageindicators.PageIndicatorDots;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.CircleRevealOutlineProvider;
+import com.android.launcher3.anim.CircleRevealOutlineProvider;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -510,6 +511,58 @@
         mState = STATE_SMALL;
     }
 
+    private AnimatorSet getOpeningAnimatorSet() {
+        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+
+        int width = getFolderWidth();
+        int height = getFolderHeight();
+
+        float transX = - 0.075f * (width / 2 - getPivotX());
+        float transY = - 0.075f * (height / 2 - getPivotY());
+        setTranslationX(transX);
+        setTranslationY(transY);
+        PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
+        PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
+
+        Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
+        drift.setDuration(mMaterialExpandDuration);
+        drift.setStartDelay(mMaterialExpandStagger);
+        drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
+
+        int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
+        int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
+        float radius = (float) Math.hypot(rx, ry);
+
+        Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
+                (int) getPivotY(), 0, radius).createRevealAnimator(this);
+        reveal.setDuration(mMaterialExpandDuration);
+        reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
+
+        mContent.setAlpha(0f);
+        Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
+        iconsAlpha.setDuration(mMaterialExpandDuration);
+        iconsAlpha.setStartDelay(mMaterialExpandStagger);
+        iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
+
+        mFooter.setAlpha(0f);
+        Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
+        textAlpha.setDuration(mMaterialExpandDuration);
+        textAlpha.setStartDelay(mMaterialExpandStagger);
+        textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
+
+        anim.play(drift);
+        anim.play(iconsAlpha);
+        anim.play(textAlpha);
+        anim.play(reveal);
+
+        AnimationLayerSet layerSet = new AnimationLayerSet();
+        layerSet.addView(mContent);
+        layerSet.addView(mFooter);
+        anim.addListener(layerSet);
+
+        return anim;
+    }
+
     /**
      * Opens the user folder described by the specified tag. The opening of the folder
      * is animated relative to the specified View. If the View is null, no animation
@@ -554,55 +607,10 @@
 
         mFolderIcon.growAndFadeOut();
 
-        AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
-        int width = getFolderWidth();
-        int height = getFolderHeight();
-
-        float transX = - 0.075f * (width / 2 - getPivotX());
-        float transY = - 0.075f * (height / 2 - getPivotY());
-        setTranslationX(transX);
-        setTranslationY(transY);
-        PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0);
-        PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0);
-
-        Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty);
-        drift.setDuration(mMaterialExpandDuration);
-        drift.setStartDelay(mMaterialExpandStagger);
-        drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
-        int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
-        int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
-        float radius = (float) Math.hypot(rx, ry);
-
-        Animator reveal = new CircleRevealOutlineProvider((int) getPivotX(),
-                (int) getPivotY(), 0, radius).createRevealAnimator(this);
-        reveal.setDuration(mMaterialExpandDuration);
-        reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
-        mContent.setAlpha(0f);
-        Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "alpha", 0f, 1f);
-        iconsAlpha.setDuration(mMaterialExpandDuration);
-        iconsAlpha.setStartDelay(mMaterialExpandStagger);
-        iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-
-        mFooter.setAlpha(0f);
-        Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f);
-        textAlpha.setDuration(mMaterialExpandDuration);
-        textAlpha.setStartDelay(mMaterialExpandStagger);
-        textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-
-        anim.play(drift);
-        anim.play(iconsAlpha);
-        anim.play(textAlpha);
-        anim.play(reveal);
-
-        mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
-        mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
+        AnimatorSet anim = getOpeningAnimatorSet();
         onCompleteRunnable = new Runnable() {
             @Override
             public void run() {
-                mContent.setLayerType(LAYER_TYPE_NONE, null);
-                mFooter.setLayerType(LAYER_TYPE_NONE, null);
                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
             }
         };
@@ -715,12 +723,22 @@
         parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
     }
 
+    private AnimatorSet getClosingAnimatorSet() {
+        AnimatorSet animatorSet = LauncherAnimUtils.createAnimatorSet();
+        animatorSet.play(LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f));
+
+        AnimationLayerSet layerSet = new AnimationLayerSet();
+        layerSet.addView(this);
+        animatorSet.addListener(layerSet);
+
+        return animatorSet;
+    }
+
     private void animateClosed() {
-        final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f);
-        oa.addListener(new AnimatorListenerAdapter() {
+        AnimatorSet a = getClosingAnimatorSet();
+        a.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                setLayerType(LAYER_TYPE_NONE, null);
                 closeComplete(true);
             }
             @Override
@@ -732,9 +750,8 @@
                 mState = STATE_ANIMATING;
             }
         });
-        oa.setDuration(mExpandDuration);
-        setLayerType(LAYER_TYPE_HARDWARE, null);
-        oa.start();
+        a.setDuration(mExpandDuration);
+        a.start();
     }
 
     private void closeComplete(boolean wasAnimated) {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 9697985..d84a9d2 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -121,6 +121,7 @@
 
     private float mSlop;
 
+    FolderIconPreviewVerifier mPreviewVerifier;
     private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
     private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>();
     private Drawable mReferenceDrawable = null;
@@ -222,6 +223,7 @@
 
     private void setFolder(Folder folder) {
         mFolder = folder;
+        mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
         updateItemDrawingParams(false);
     }
 
@@ -933,8 +935,26 @@
         return mFolderName.getVisibility() == VISIBLE;
     }
 
+    private List<View> getItemsToDisplay() {
+        mPreviewVerifier.setFolderInfo(mFolder.getInfo());
+
+        List<View> itemsToDisplay = new ArrayList<>();
+        List<View> allItems = mFolder.getItemsInReadingOrder();
+        int numItems = allItems.size();
+        for (int rank = 0; rank < numItems; ++rank) {
+            if (mPreviewVerifier.isItemInPreview(rank)) {
+                itemsToDisplay.add(allItems.get(rank));
+            }
+
+            if (itemsToDisplay.size() == FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+                break;
+            }
+        }
+        return itemsToDisplay;
+    }
+
     private void updateItemDrawingParams(boolean animate) {
-        List<View> items = mPreviewLayoutRule.getItemsToDisplay(mFolder);
+        List<View> items = getItemsToDisplay();
         int nItemsInPreview = items.size();
 
         int prevNumItems = mDrawingParams.size();
@@ -1118,8 +1138,8 @@
             PreviewItemDrawingParams params);
         void init(int availableSpace, int intrinsicIconSize, boolean rtl);
         float scaleForItem(int index, int totalNumItems);
+        float getIconSize();
         int maxNumItems();
         boolean clipToBackground();
-        List<View> getItemsToDisplay(Folder folder);
     }
 }
diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
new file mode 100644
index 0000000..de962b0
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.folder;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * Verifies whether an item in a Folder is displayed in the FolderIcon preview.
+ */
+public class FolderIconPreviewVerifier {
+
+    private final int mMaxGridCountX;
+    private final int mMaxGridCountY;
+    private final int mMaxItemsPerPage;
+    private final int[] mGridSize = new int[2];
+
+    private int mGridCountX;
+    private boolean mDisplayingUpperLeftQuadrant = false;
+
+    public FolderIconPreviewVerifier(InvariantDeviceProfile profile) {
+        mMaxGridCountX = profile.numFolderColumns;
+        mMaxGridCountY = profile.numFolderRows;
+        mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY;
+    }
+
+    public void setFolderInfo(FolderInfo info) {
+        int numItemsInFolder = info.contents.size();
+        mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION
+                && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON
+                && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW;
+
+        if (mDisplayingUpperLeftQuadrant) {
+            FolderPagedView.calculateGridSize(info.contents.size(), 0, 0, mMaxGridCountX,
+                    mMaxGridCountY, mMaxItemsPerPage, mGridSize);
+            mGridCountX = mGridSize[0];
+        }
+    }
+
+    public boolean isItemInPreview(int rank) {
+        if (mDisplayingUpperLeftQuadrant) {
+            // Returns true iff the icon is in the 2x2 upper left quadrant of the Folder.
+            int col = rank % mGridCountX;
+            int row = rank / mGridCountX;
+            return col < 2 && row < 2;
+        }
+        return rank < FolderIcon.NUM_ITEMS_IN_PREVIEW;
+    }
+}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index eecce18..bc78324 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
-import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.util.Themes;
@@ -68,7 +67,7 @@
      */
     private static final float SCROLL_HINT_FRACTION = 0.07f;
 
-    private static final int[] sTempPosArray = new int[2];
+    private static final int[] sTmpArray = new int[2];
 
     public final boolean mIsRtl;
 
@@ -120,40 +119,58 @@
     }
 
     /**
-     * Sets up the grid size such that {@param count} items can fit in the grid.
+     * Calculates the grid size such that {@param count} items can fit in the grid.
      * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
      * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
      */
-    private void setupContentDimensions(int count) {
-        mAllocatedContentSize = count;
+    public static void calculateGridSize(int count, int countX, int countY, int maxCountX,
+            int maxCountY, int maxItemsPerPage, int[] out) {
         boolean done;
-        if (count >= mMaxItemsPerPage) {
-            mGridCountX = mMaxCountX;
-            mGridCountY = mMaxCountY;
+        int gridCountX = countX;
+        int gridCountY = countY;
+
+        if (count >= maxItemsPerPage) {
+            gridCountX = maxCountX;
+            gridCountY = maxCountY;
             done = true;
         } else {
             done = false;
         }
 
         while (!done) {
-            int oldCountX = mGridCountX;
-            int oldCountY = mGridCountY;
-            if (mGridCountX * mGridCountY < count) {
+            int oldCountX = gridCountX;
+            int oldCountY = gridCountY;
+            if (gridCountX * gridCountY < count) {
                 // Current grid is too small, expand it
-                if ((mGridCountX <= mGridCountY || mGridCountY == mMaxCountY) && mGridCountX < mMaxCountX) {
-                    mGridCountX++;
-                } else if (mGridCountY < mMaxCountY) {
-                    mGridCountY++;
+                if ((gridCountX <= gridCountY || gridCountY == maxCountY)
+                        && gridCountX < maxCountX) {
+                    gridCountX++;
+                } else if (gridCountY < maxCountY) {
+                    gridCountY++;
                 }
-                if (mGridCountY == 0) mGridCountY++;
-            } else if ((mGridCountY - 1) * mGridCountX >= count && mGridCountY >= mGridCountX) {
-                mGridCountY = Math.max(0, mGridCountY - 1);
-            } else if ((mGridCountX - 1) * mGridCountY >= count) {
-                mGridCountX = Math.max(0, mGridCountX - 1);
+                if (gridCountY == 0) gridCountY++;
+            } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
+                gridCountY = Math.max(0, gridCountY - 1);
+            } else if ((gridCountX - 1) * gridCountY >= count) {
+                gridCountX = Math.max(0, gridCountX - 1);
             }
-            done = mGridCountX == oldCountX && mGridCountY == oldCountY;
+            done = gridCountX == oldCountX && gridCountY == oldCountY;
         }
 
+        out[0] = gridCountX;
+        out[1] = gridCountY;
+    }
+
+    /**
+     * Sets up the grid size such that {@param count} items can fit in the grid.
+     */
+    public void setupContentDimensions(int count) {
+        mAllocatedContentSize = count;
+        calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage,
+                sTmpArray);
+        mGridCountX = sTmpArray[0];
+        mGridCountY = sTmpArray[1];
+
         // Update grid size
         for (int i = getPageCount() - 1; i >= 0; i--) {
             getPageAt(i).setGridSize(mGridCountX, mGridCountY);
@@ -314,6 +331,8 @@
         int position = 0;
         int newX, newY, rank;
 
+        FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
+                Launcher.getLauncher(getContext()).getDeviceProfile().inv);
         rank = 0;
         for (int i = 0; i < itemCount; i++) {
             View v = list.size() > i ? list.get(i) : null;
@@ -346,7 +365,7 @@
                 currentPage.addViewToCellLayout(
                         v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
 
-                if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) {
+                if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) {
                     ((BubbleTextView) v).verifyHighRes();
                 }
             }
@@ -400,12 +419,12 @@
     public int findNearestArea(int pixelX, int pixelY) {
         int pageIndex = getNextPage();
         CellLayout page = getPageAt(pageIndex);
-        page.findNearestArea(pixelX, pixelY, 1, 1, sTempPosArray);
+        page.findNearestArea(pixelX, pixelY, 1, 1, sTmpArray);
         if (mFolder.isLayoutRtl()) {
-            sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1;
+            sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1;
         }
         return Math.min(mAllocatedContentSize - 1,
-                pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]);
+                pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]);
     }
 
     public boolean isFull() {
diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
index 297203a..9c8c2ef 100644
--- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java
@@ -87,6 +87,11 @@
     }
 
     @Override
+    public float getIconSize() {
+        return mBaselineIconSize;
+    }
+
+    @Override
     public float scaleForItem(int index, int numItems) {
         // Scale is determined by the position of the icon in the preview.
         index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1;
@@ -98,10 +103,4 @@
     public boolean clipToBackground() {
         return false;
     }
-
-    @Override
-    public List<View> getItemsToDisplay(Folder folder) {
-        List<View> items = folder.getItemsInReadingOrder();
-        return items.subList(0, Math.min(items.size(), MAX_NUM_ITEMS_IN_PREVIEW));
-    }
 }
diff --git a/src/com/android/launcher3/logging/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java
new file mode 100644
index 0000000..2646a22
--- /dev/null
+++ b/src/com/android/launcher3/logging/DumpTargetWrapper.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 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.logging;
+
+import android.os.Process;
+import android.text.TextUtils;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
+import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
+import com.android.launcher3.model.nano.LauncherDumpProto.UserType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class can be used when proto definition doesn't support nesting.
+ */
+public class DumpTargetWrapper {
+    DumpTarget node;
+    ArrayList<DumpTargetWrapper> children;
+
+    public DumpTargetWrapper() {
+        children = new ArrayList<>();
+    }
+
+    public DumpTargetWrapper(DumpTarget t) {
+        this();
+        node = t;
+    }
+
+    public DumpTargetWrapper(int containerType, int id) {
+        this();
+        node = newContainerTarget(containerType, id);
+    }
+
+    public DumpTargetWrapper(ItemInfo info) {
+        this();
+        node = newItemTarget(info);
+    }
+
+    public DumpTarget getDumpTarget() {
+        return node;
+    }
+
+    public void add(DumpTargetWrapper child) {
+        children.add(child);
+    }
+
+    public List<DumpTarget> getFlattenedList() {
+        ArrayList<DumpTarget> list = new ArrayList<>();
+        list.add(node);
+        if (!children.isEmpty()) {
+            for(DumpTargetWrapper t: children) {
+                list.addAll(t.getFlattenedList());
+            }
+            list.add(node); // add a delimiter empty object
+        }
+        return list;
+    }
+    public DumpTarget newItemTarget(ItemInfo info) {
+        DumpTarget dt = new DumpTarget();
+        dt.type = DumpTarget.Type.ITEM;
+
+        switch (info.itemType) {
+            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                dt.itemType = ItemType.APP_ICON;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                dt.itemType = ItemType.WIDGET;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                dt.itemType = ItemType.SHORTCUT;
+                break;
+        }
+        return dt;
+    }
+
+    public DumpTarget newContainerTarget(int type, int id) {
+        DumpTarget dt = new DumpTarget();
+        dt.type = DumpTarget.Type.CONTAINER;
+        dt.containerType = type;
+        dt.pageId = id;
+        return dt;
+    }
+
+    public static String getDumpTargetStr(DumpTarget t) {
+        if (t == null){
+            return "";
+        }
+        switch (t.type) {
+            case LauncherDumpProto.DumpTarget.Type.ITEM:
+                return getItemStr(t);
+            case LauncherDumpProto.DumpTarget.Type.CONTAINER:
+                String str = LoggerUtils.getFieldName(t.containerType, ContainerType.class);
+                if (t.containerType == ContainerType.WORKSPACE) {
+                    str += " id=" + t.pageId;
+                } else if (t.containerType == ContainerType.FOLDER) {
+                    str += " grid(" + t.gridX + "," + t.gridY+ ")";
+                }
+                return str;
+            default:
+                return "UNKNOWN TARGET TYPE";
+        }
+    }
+
+    private static String getItemStr(DumpTarget t) {
+        String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class);
+        if (!TextUtils.isEmpty(t.packageName)) {
+            typeStr += ", package=" + t.packageName;
+        }
+        if (!TextUtils.isEmpty(t.component)) {
+            typeStr += ", component=" + t.component;
+        }
+        return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+                + "), pageIdx=" + t.pageId + " user=" + t.userType;
+    }
+
+    public DumpTarget writeToDumpTarget(ItemInfo info) {
+        node.component = info.getTargetComponent() == null? "":
+                info.getTargetComponent().flattenToString();
+        node.packageName = info.getIntent() == null? "": info.getIntent().getPackage();
+        node.gridX = info.cellX;
+        node.gridY = info.cellY;
+        node.spanX = info.spanX;
+        node.spanY = info.spanY;
+        node.userType = (info.user.equals(Process.myUserHandle()))? UserType.DEFAULT : UserType.WORK;
+        return node;
+    }
+}
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index c13e8b3..499fdc7 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -1,5 +1,21 @@
+/*
+ * Copyright (C) 2016 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.logging;
 
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.view.View;
@@ -27,7 +43,7 @@
     private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
     private static final String UNKNOWN = "UNKNOWN";
 
-    private static String getFieldName(int value, Class c) {
+    public static String getFieldName(int value, Class c) {
         SparseArray<String> cache;
         synchronized (sNameCache) {
             cache = sNameCache.get(c);
@@ -68,8 +84,13 @@
             case Target.Type.CONTROL:
                 return getFieldName(t.controlType, ControlType.class);
             case Target.Type.CONTAINER:
-                return getFieldName(t.containerType, ContainerType.class)
-                        + " id=" + t.pageIndex;
+                String str = getFieldName(t.containerType, ContainerType.class);
+                if (t.containerType == ContainerType.WORKSPACE) {
+                    str += " id=" + t.pageIndex;
+                } else if (t.containerType == ContainerType.FOLDER) {
+                    str += " grid(" + t.gridX + "," + t.gridY+ ")";
+                }
+                return str;
             default:
                 return "UNKNOWN TARGET TYPE";
         }
@@ -86,10 +107,8 @@
         if (t.intentHash != 0) {
             typeStr += ", intentHash=" + t.intentHash;
         }
-        if (t.spanX != 0) {
-            typeStr += ", spanX=" + t.spanX;
-        }
-        return typeStr + ", grid=(" + t.gridX + "," + t.gridY + "), id=" + t.pageIndex;
+        return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY
+                + "), pageIdx=" + t.pageIndex;
     }
 
     public static Target newItemTarget(View v) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 6b64087..0e73ca6 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -24,19 +24,27 @@
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.logging.DumpTargetWrapper;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
+import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
+import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.MultiHashMap;
+import com.google.protobuf.nano.MessageNano;
 
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -102,21 +110,31 @@
         deepShortcutMap.clear();
     }
 
-    // TODO: current dump is very cryptic and hard to understand. Make it more legible.
-    public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
-        writer.println(prefix + "Data Model:");
-        for (int i = 0; i < workspaceScreens.size(); i++) {
-            writer.println(prefix + "\tIndex of workspaceScreens:" + workspaceScreens.get(i).toString());
+     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
+             String[] args) {
+        if (args.length > 0 && TextUtils.equals(args[0], "--proto")) {
+            dumpProto(prefix, fd, writer, args);
+            return;
         }
+        writer.println(prefix + "Data Model:");
+        writer.print(prefix + " ---- workspace screens: ");
+        for (int i = 0; i < workspaceScreens.size(); i++) {
+            writer.print(" " + workspaceScreens.get(i).toString());
+        }
+        writer.println();
+        writer.println(prefix + " ---- workspace items ");
         for (int i = 0; i < workspaceItems.size(); i++) {
             writer.println(prefix + '\t' + workspaceItems.get(i).toString());
         }
+        writer.println(prefix + " ---- appwidget items ");
         for (int i = 0; i < appWidgets.size(); i++) {
             writer.println(prefix + '\t' + appWidgets.get(i).toString());
         }
+        writer.println(prefix + " ---- folder items ");
         for (int i = 0; i< folders.size(); i++) {
             writer.println(prefix + '\t' + folders.valueAt(i).toString());
         }
+        writer.println(prefix + " ---- items id map ");
         for (int i = 0; i< itemsIdMap.size(); i++) {
             writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
         }
@@ -133,6 +151,88 @@
         }
     }
 
+    private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer,
+            String[] args) {
+
+        // Add top parent nodes. (L1)
+        DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0);
+        LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>();
+        for (int i = 0; i < workspaceScreens.size(); i++) {
+            workspaces.put(new Long(workspaceScreens.get(i)),
+                    new DumpTargetWrapper(ContainerType.WORKSPACE, i));
+        }
+        DumpTargetWrapper dtw;
+        // Add non leaf / non top nodes (L2)
+        for (int i = 0; i < folders.size(); i++) {
+            FolderInfo fInfo = folders.valueAt(i);
+            dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size());
+            dtw.writeToDumpTarget(fInfo);
+            for(ShortcutInfo sInfo: fInfo.contents) {
+                DumpTargetWrapper child = new DumpTargetWrapper(sInfo);
+                child.writeToDumpTarget(sInfo);
+                dtw.add(child);
+            }
+            if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                hotseat.add(dtw);
+            } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                workspaces.get(new Long(fInfo.screenId)).add(dtw);
+            }
+        }
+        // Add leaf nodes (L3): *Info
+        for (int i = 0; i < workspaceItems.size(); i++) {
+            ItemInfo info = workspaceItems.get(i);
+            if (info instanceof FolderInfo) {
+                continue;
+            }
+            dtw = new DumpTargetWrapper(info);
+            dtw.writeToDumpTarget(info);
+            if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                hotseat.add(dtw);
+            } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                workspaces.get(new Long(info.screenId)).add(dtw);
+            }
+        }
+        for (int i = 0; i < appWidgets.size(); i++) {
+            ItemInfo info = appWidgets.get(i);
+            dtw = new DumpTargetWrapper(info);
+            dtw.writeToDumpTarget(info);
+            if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                hotseat.add(dtw);
+            } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                workspaces.get(new Long(info.screenId)).add(dtw);
+            }
+        }
+
+
+        // Traverse target wrapper
+        ArrayList<DumpTarget> targetList = new ArrayList<>();
+        targetList.addAll(hotseat.getFlattenedList());
+        for (int i = 0; i < workspaces.size(); i++) {
+            targetList.addAll(workspaces.valueAt(i).getFlattenedList());
+        }
+
+        if (args.length > 1 && TextUtils.equals(args[1], "--debug")) {
+            for (int i = 0; i < targetList.size(); i++) {
+                writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i)));
+            }
+            return;
+        } else {
+            LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression();
+            proto.targets = new DumpTarget[targetList.size()];
+            for (int i = 0; i < targetList.size(); i++) {
+                proto.targets[i] = targetList.get(i);
+            }
+            FileOutputStream fos = new FileOutputStream(fd);
+            try {
+
+                fos.write(MessageNano.toByteArray(proto));
+                Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes");
+            } catch (IOException e) {
+                Log.e(TAG, "Exception writing dumpsys --proto", e);
+            }
+        }
+    }
+
     public synchronized void removeItem(Context context, ItemInfo... items) {
         removeItem(context, Arrays.asList(items));
     }
diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java
index b839d99..eeece88 100644
--- a/src/com/android/launcher3/popup/PopupItemView.java
+++ b/src/com/android/launcher3/popup/PopupItemView.java
@@ -36,7 +36,7 @@
 import com.android.launcher3.LogAccelerateInterpolator;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.PillRevealOutlineProvider;
+import com.android.launcher3.anim.PillRevealOutlineProvider;
 
 /**
  * An abstract {@link FrameLayout} that supports animating an item's content