Merge "Creating database without localized collators on NYC and above" into ub-launcher3-calgary
diff --git a/res/drawable/bg_celllayout.xml b/res/drawable/bg_celllayout.xml
index 0ce083e..651ae52 100644
--- a/res/drawable/bg_celllayout.xml
+++ b/res/drawable/bg_celllayout.xml
@@ -22,13 +22,14 @@
 
     <item>
         <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
-            <solid android:color="#3fffffff" />
+            <solid android:color="@color/spring_loaded_panel_color" />
         </shape>
     </item>
     <item>
         <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
-            <solid android:color="#3fffffff" />
-            <stroke android:width="1dp" android:color="#fff" />
+            <stroke
+                android:width="@dimen/spring_loaded_panel_border"
+                android:color="@color/spring_loaded_highlighted_panel_border_color" />
         </shape>
     </item>
 
diff --git a/res/values/colors.xml b/res/values/colors.xml
index b679270..dc3d4fa 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,6 +39,9 @@
     <color name="outline_color">#FFFFFFFF</color>
     <color name="launcher_accent_color">#ff009688</color>
 
+    <color name="spring_loaded_panel_color">#40FFFFFF</color>
+    <color name="spring_loaded_highlighted_panel_border_color">#FFF</color>
+
     <!-- Containers -->
     <color name="container_fastscroll_thumb_inactive_color">#009688</color>
     <color name="container_fastscroll_thumb_active_color">#009688</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index fee62dc..b06afc4 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -134,6 +134,8 @@
 
     <dimen name="drag_flingToDeleteMinVelocity">-1500dp</dimen>
 
+    <dimen name="spring_loaded_panel_border">1dp</dimen>
+
 <!-- Theme -->
     <dimen name="quantum_panel_outer_padding">4dp</dimen>
 
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 0ba9499..daeca3b 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -363,7 +363,7 @@
                 sTmpRect.right, sTmpRect.bottom);
     }
 
-    public static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) {
+    public static Rect getWidgetSizeRanges(Context context, int spanX, int spanY, Rect rect) {
         if (sCellSize == null) {
             InvariantDeviceProfile inv = LauncherAppState.getInstance().getInvariantDeviceProfile();
 
@@ -376,7 +376,7 @@
         if (rect == null) {
             rect = new Rect();
         }
-        final float density = launcher.getResources().getDisplayMetrics().density;
+        final float density = context.getResources().getDisplayMetrics().density;
 
         // Compute landscape size
         int landWidth = (int) ((spanX * sCellSize[0].x) / density);
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 052f7ac..e691b48 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -55,7 +55,7 @@
         mHasVerticalHotseat = mLauncher.getDeviceProfile().isVerticalBarLayout();
     }
 
-    CellLayout getLayout() {
+    public CellLayout getLayout() {
         return mContent;
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e411527..245b399 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -4013,7 +4013,7 @@
                 // Note: This assumes that the id remap broadcast is received before this step.
                 // If that is not the case, the id remap will be ignored and user may see the
                 // click to setup view.
-                PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
+                PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo);
                 pendingInfo.spanX = item.spanX;
                 pendingInfo.spanY = item.spanY;
                 pendingInfo.minSpanX = item.minSpanX;
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index c789082..570607e 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -27,6 +27,7 @@
 import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.RemoteViews;
 
 import com.android.launcher3.dragndrop.DragLayer;
@@ -291,4 +292,10 @@
             });
         }
     }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setClassName(getClass().getName());
+    }
 }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index fc8bb45..5606a24 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -625,17 +625,23 @@
 
     // we moved this functionality to a helper function so SmoothPagedView can reuse it
     protected boolean computeScrollHelper() {
+        return computeScrollHelper(true);
+    }
+
+    protected boolean computeScrollHelper(boolean shouldInvalidate) {
         if (mScroller.computeScrollOffset()) {
             // Don't bother scrolling if the page does not need to be moved
             if (getScrollX() != mScroller.getCurrX()
-                || getScrollY() != mScroller.getCurrY()) {
+                    || getScrollY() != mScroller.getCurrY()) {
                 float scaleX = mFreeScroll ? getScaleX() : 1f;
                 int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
                 scrollTo(scrollX, mScroller.getCurrY());
             }
-            invalidate();
+            if (shouldInvalidate) {
+                invalidate();
+            }
             return true;
-        } else if (mNextPage != INVALID_PAGE) {
+        } else if (mNextPage != INVALID_PAGE && shouldInvalidate) {
             sendScrollAccessibilityEvent();
 
             mCurrentPage = validateNewPage(mNextPage);
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index ef47485..02b6044 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -63,16 +63,24 @@
             // Don't listen for the pinch gesture if we are already animating from a previous one.
             return false;
         }
+        if (mLauncher.isWorkspaceLocked()) {
+            // Don't listen for the pinch gesture if the workspace isn't ready.
+            return false;
+        }
         if (mWorkspace == null) {
-            mWorkspace = mLauncher.mWorkspace;
+            mWorkspace = mLauncher.getWorkspace();
             mThresholdManager = new PinchThresholdManager(mWorkspace);
             mAnimationManager = new PinchAnimationManager(mLauncher);
         }
         if (mWorkspace.isSwitchingState() || mWorkspace.mScrollInteractionBegan) {
-            // Don't listen to pinches occurring while switching state, as it will cause a jump
+            // Don't listen for the pinch gesture while switching state, as it will cause a jump
             // once the state switching animation is complete.
             return false;
         }
+        if (mWorkspace.getOpenFolder() != null) {
+            // Don't listen for the pinch gesture if a folder is open.
+            return false;
+        }
 
         mPreviousProgress = mWorkspace.isInOverviewMode() ? OVERVIEW_PROGRESS : WORKSPACE_PROGRESS;
         mPreviousTimeMillis = System.currentTimeMillis();
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0b72ef7..44a17cc 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -149,7 +149,7 @@
      */
     @Thunk CellLayout mDragTargetLayout = null;
     /**
-     * The CellLayout that we will show as glowing
+     * The CellLayout that we will show as highlighted
      */
     private CellLayout mDragOverlappingLayout = null;
 
@@ -1421,6 +1421,10 @@
         mWallpaperOffset.syncWithScroll();
     }
 
+    public void computeScrollWithoutInvalidation() {
+        computeScrollHelper(false);
+    }
+
     @Override
     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
         if (!isSwitchingState()) {
@@ -2836,7 +2840,13 @@
         if (mDragOverlappingLayout != null) {
             mDragOverlappingLayout.setIsDragOverlapping(true);
         }
-        invalidate();
+        // Invalidating the scrim will also force this CellLayout
+        // to be invalidated so that it is highlighted if necessary.
+        mLauncher.getDragLayer().invalidateScrim();
+    }
+
+    public CellLayout getCurrentDragOverlappingLayout() {
+        return mDragOverlappingLayout;
     }
 
     void setCurrentDropOverCell(int x, int y) {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 647ec5e..40b9179 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -26,6 +26,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.DragEvent;
@@ -42,6 +43,7 @@
 
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.CellLayout;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
@@ -92,6 +94,7 @@
 
     private boolean mHoverPointClosesFolder = false;
     private final Rect mHitRect = new Rect();
+    private final Rect mHighlightRect = new Rect();
 
     private TouchCompleteListener mTouchCompleteListener;
 
@@ -247,12 +250,9 @@
         }
         clearAllResizeFrames();
 
-        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
-        if (currentFolder == null) {
-            if (mPinchListener.onInterceptTouchEvent(ev)) {
-                // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
-                return true;
-            }
+        if (mPinchListener.onInterceptTouchEvent(ev)) {
+            // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
+            return true;
         }
         return mDragController.onInterceptTouchEvent(ev);
     }
@@ -922,12 +922,29 @@
         invalidate();
     }
 
+    public void invalidateScrim() {
+        if (mBackgroundAlpha > 0.0f) {
+            invalidate();
+        }
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         // Draw the background below children.
         if (mBackgroundAlpha > 0.0f) {
+            // Update the scroll position first to ensure scrim cutout is in the right place.
+            mLauncher.getWorkspace().computeScrollWithoutInvalidation();
+
             int alpha = (int) (mBackgroundAlpha * 255);
+            CellLayout currCellLayout = mLauncher.getWorkspace().getCurrentDragOverlappingLayout();
+            canvas.save();
+            if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) {
+                // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
+                getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
+                canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
+            }
             canvas.drawColor((alpha << 24) | SCRIM_COLOR);
+            canvas.restore();
         }
 
         super.dispatchDraw(canvas);
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index fcb714f..de06ab6 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.widget;
 
 import android.appwidget.AppWidgetHostView;
+import android.content.Context;
 import android.os.Bundle;
 import android.os.Parcelable;
 
@@ -37,14 +38,14 @@
     public AppWidgetHostView boundWidget;
     public Bundle bindOptions = null;
 
-    public PendingAddWidgetInfo(Launcher launcher, LauncherAppWidgetProviderInfo i, Parcelable data) {
+    public PendingAddWidgetInfo(Context context, LauncherAppWidgetProviderInfo i) {
         if (i.isCustomWidget) {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
         } else {
             itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
         }
         this.info = i;
-        user = AppWidgetManagerCompat.getInstance(launcher).getUser(i);
+        user = AppWidgetManagerCompat.getInstance(context).getUser(i);
         componentName = i.provider;
         previewImage = i.previewImage;
         icon = i.icon;
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index b47ba84..297505b 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -151,15 +151,15 @@
         return true;
     }
 
-    public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
+    public static Bundle getDefaultOptionsForWidget(Context context, PendingAddWidgetInfo info) {
         Bundle options = null;
-        Rect rect = new Rect();
         if (Utilities.ATLEAST_JB_MR1) {
-            AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect);
-            Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
+            Rect rect = new Rect();
+            AppWidgetResizeFrame.getWidgetSizeRanges(context, info.spanX, info.spanY, rect);
+            Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
                     info.componentName, null);
 
-            float density = launcher.getResources().getDisplayMetrics().density;
+            float density = context.getResources().getDisplayMetrics().density;
             int xPaddingDips = (int) ((padding.left + padding.right) / density);
             int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
 
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index ac9d62e..5d8adf5 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -138,7 +138,7 @@
             WidgetCell widget = (WidgetCell) row.getChildAt(i);
             if (infoList.get(i) instanceof LauncherAppWidgetProviderInfo) {
                 LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) infoList.get(i);
-                PendingAddWidgetInfo pawi = new PendingAddWidgetInfo(mLauncher, info, null);
+                PendingAddWidgetInfo pawi = new PendingAddWidgetInfo(mLauncher, info);
                 widget.setTag(pawi);
                 widget.applyFromAppWidgetProviderInfo(info, mWidgetPreviewLoader);
             } else if (infoList.get(i) instanceof ResolveInfo) {
diff --git a/tests/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/BindWidgetTest.java
new file mode 100644
index 0000000..06e1936
--- /dev/null
+++ b/tests/src/com/android/launcher3/BindWidgetTest.java
@@ -0,0 +1,428 @@
+package com.android.launcher3;
+
+import android.annotation.TargetApi;
+import android.app.SearchManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiSelector;
+import android.test.InstrumentationTestCase;
+
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.util.ManagedProfileHeuristic;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.launcher3.widget.WidgetHostViewLoader;
+
+import java.io.FileInputStream;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests for bind widget flow.
+ *
+ * Note running these tests will clear the workspace on the device.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class BindWidgetTest extends InstrumentationTestCase {
+
+    private static final long DEFAULT_TIMEOUT = 6000;
+
+    private UiDevice mDevice;
+    private Context mTargetContext;
+    private ContentResolver mResolver;
+    private AppWidgetManagerCompat mWidgetManager;
+
+    // Objects created during test, which should be cleaned up in the end.
+    private Cursor mCursor;
+    // App install session id.
+    private int mSessionId = -1;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        mTargetContext = getInstrumentation().getTargetContext();
+        mResolver = mTargetContext.getContentResolver();
+        mWidgetManager = AppWidgetManagerCompat.getInstance(mTargetContext);
+
+        // Check bind widget permission
+        String pkg = mTargetContext.getPackageName();
+        if (mTargetContext.getPackageManager().checkPermission(
+                pkg, android.Manifest.permission.BIND_APPWIDGET)
+                != PackageManager.PERMISSION_GRANTED) {
+            ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(
+                    "appwidget grantbind --package " + pkg);
+            // Read the input stream fully.
+            FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+            while (fis.read() != -1);
+            fis.close();
+        }
+
+        // Clear all existing data
+        LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+        LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mCursor != null) {
+            mCursor.close();
+        }
+
+        if (mSessionId > -1) {
+            mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+        }
+    }
+
+    public void testBindNormalWidget_withConfig() {
+        LauncherAppWidgetProviderInfo info = findWidgetProvider(true);
+        LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+
+        setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
+    }
+
+    public void testBindNormalWidget_withoutConfig() {
+        LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
+        LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+
+        setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
+    }
+
+    public void testUnboundWidget_removed() throws Exception {
+        LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
+        LauncherAppWidgetInfo item = createWidgetInfo(info, false);
+        item.appWidgetId = 33;
+
+        // Since there is no widget to verify, just wait until the workspace is ready.
+        setupAndVerifyContents(item, Workspace.class, null);
+
+        waitUntilLoaderIdle();
+        // Item deleted from db
+        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
+                null, null, null, null, null);
+        assertEquals(0, mCursor.getCount());
+
+        // The view does not exist
+        assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists());
+    }
+
+    public void testPendingWidget_autoRestored() {
+        // A non-restored widget with no config screen gets restored automatically.
+        LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
+
+        // Do not bind the widget
+        LauncherAppWidgetInfo item = createWidgetInfo(info, false);
+        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
+
+        setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
+    }
+
+    public void testPendingWidget_withConfigScreen() throws Exception {
+        // A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
+        LauncherAppWidgetProviderInfo info = findWidgetProvider(true);
+
+        // Do not bind the widget
+        LauncherAppWidgetInfo item = createWidgetInfo(info, false);
+        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
+
+        setupAndVerifyContents(item, PendingAppWidgetHostView.class, null);
+        waitUntilLoaderIdle();
+        // Item deleted from db
+        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
+                null, null, null, null, null);
+        mCursor.moveToNext();
+
+        // Widget has a valid Id now.
+        assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
+                & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+        assertNotNull(mWidgetManager.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
+                LauncherSettings.Favorites.APPWIDGET_ID))));
+    }
+
+    public void testPendingWidget_notRestored_removed() throws Exception {
+        LauncherAppWidgetInfo item = getInvalidWidgetInfo();
+        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
+                | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
+
+        setupAndVerifyContents(item, Workspace.class, null);
+        // The view does not exist
+        assertFalse(mDevice.findObject(
+                new UiSelector().className(PendingAppWidgetHostView.class)).exists());
+        waitUntilLoaderIdle();
+        // Item deleted from db
+        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
+                null, null, null, null, null);
+        assertEquals(0, mCursor.getCount());
+    }
+
+    public void testPendingWidget_notRestored_brokenInstall() throws Exception {
+        // A widget which is was being installed once, even if its not being
+        // installed at the moment is not removed.
+        LauncherAppWidgetInfo item = getInvalidWidgetInfo();
+        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
+                | LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
+                | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
+
+        setupAndVerifyContents(item, PendingAppWidgetHostView.class, null);
+        // Verify item still exists in db
+        waitUntilLoaderIdle();
+        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
+                null, null, null, null, null);
+        assertEquals(1, mCursor.getCount());
+
+        // Widget still has an invalid id.
+        mCursor.moveToNext();
+        assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
+                mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
+                        & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+    }
+
+    public void testPendingWidget_notRestored_activeInstall() throws Exception {
+        // A widget which is being installed is not removed
+        LauncherAppWidgetInfo item = getInvalidWidgetInfo();
+        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
+                | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
+
+        // Create an active installer session
+        SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+        params.setAppPackageName(item.providerName.getPackageName());
+        PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
+        mSessionId = installer.createSession(params);
+
+        setupAndVerifyContents(item, PendingAppWidgetHostView.class, null);
+        // Verify item still exists in db
+        waitUntilLoaderIdle();
+        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
+                null, null, null, null, null);
+        assertEquals(1, mCursor.getCount());
+
+        // Widget still has an invalid id.
+        mCursor.moveToNext();
+        assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
+                mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
+                        & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+    }
+
+    /**
+     * Adds {@param item} on the homescreen on the 0th screen at 0,0, and verifies that the
+     * widget class is displayed on the homescreen.
+     * @param widgetClass the View class which is displayed on the homescreen
+     * @param desc the content description of the view or null.
+     */
+    private void setupAndVerifyContents(
+            LauncherAppWidgetInfo item, Class<?> widgetClass, String desc) {
+        // Add new screen
+        long screenId = LauncherSettings.Settings.call(
+                mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                .getLong(LauncherSettings.Settings.EXTRA_VALUE);
+        ContentValues v = new ContentValues();
+        v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+        v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, 0);
+        mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
+
+        // Insert the item
+        v = new ContentValues();
+        item.id = LauncherSettings.Settings.call(
+                mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+                .getLong(LauncherSettings.Settings.EXTRA_VALUE);
+        item.screenId = screenId;
+        item.onAddToDatabase(mTargetContext, v);
+        v.put(LauncherSettings.Favorites._ID, item.id);
+        mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, v);
+
+        // Reset loader
+        try {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
+                    LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
+                }
+            });
+        } catch (Throwable t) {
+            throw new IllegalArgumentException(t);
+        }
+        // Launch the home activity
+        getInstrumentation().getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setPackage(mTargetContext.getPackageName())
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+
+        // Verify UI
+        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
+                .className(widgetClass);
+        if (desc != null) {
+            selector = selector.description(desc);
+        }
+        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_TIMEOUT));
+    }
+
+    /**
+     * Finds a widget provider which can fit on the home screen.
+     * @param hasConfigureScreen if true, a provider with a config screen is returned.
+     */
+    private LauncherAppWidgetProviderInfo findWidgetProvider(final boolean hasConfigureScreen) {
+        LauncherAppWidgetProviderInfo info = getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
+            @Override
+            public LauncherAppWidgetProviderInfo call() throws Exception {
+                InvariantDeviceProfile idv =
+                        LauncherAppState.getInstance().getInvariantDeviceProfile();
+
+                ComponentName searchComponent = ((SearchManager) mTargetContext
+                        .getSystemService(Context.SEARCH_SERVICE)).getGlobalSearchActivity();
+                String searchPackage = searchComponent == null
+                        ? null : searchComponent.getPackageName();
+
+                for (AppWidgetProviderInfo info :
+                        AppWidgetManagerCompat.getInstance(mTargetContext).getAllProviders()) {
+                    if ((info.configure != null) ^ hasConfigureScreen) {
+                        continue;
+                    }
+                    // Exclude the widgets in search package, as Launcher already binds them in
+                    // QSB, so they can cause conflicts.
+                    if (info.provider.getPackageName().equals(searchPackage)) {
+                        continue;
+                    }
+                    LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
+                            .fromProviderInfo(mTargetContext, info);
+                    if (widgetInfo.minSpanX >= idv.numColumns
+                            || widgetInfo.minSpanY >= idv.numRows) {
+                        continue;
+                    }
+                    return widgetInfo;
+                }
+                return null;
+            }
+        });
+        if (info == null) {
+            throw new IllegalArgumentException("No valid widget provider");
+        }
+        return info;
+    }
+
+    /**
+     * Creates a LauncherAppWidgetInfo corresponding to {@param info}
+     * @param bindWidget if true the info is bound and a valid widgetId is assigned to
+     *                   the LauncherAppWidgetInfo
+     */
+    private LauncherAppWidgetInfo createWidgetInfo(
+            LauncherAppWidgetProviderInfo info, boolean bindWidget) {
+        LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
+                LauncherAppWidgetInfo.NO_ID, info.provider);
+        item.spanX = info.minSpanX;
+        item.spanY = info.minSpanY;
+        item.minSpanX = info.minSpanX;
+        item.minSpanY = info.minSpanY;
+        item.user = mWidgetManager.getUser(info);
+        item.cellX = 0;
+        item.cellY = 0;
+        item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+
+        if (bindWidget) {
+            PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(mTargetContext, info);
+            pendingInfo.spanX = item.spanX;
+            pendingInfo.spanY = item.spanY;
+            pendingInfo.minSpanX = item.minSpanX;
+            pendingInfo.minSpanY = item.minSpanY;
+            Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(mTargetContext, pendingInfo);
+
+            AppWidgetHost host = new AppWidgetHost(mTargetContext, Launcher.APPWIDGET_HOST_ID);
+            int widgetId = host.allocateAppWidgetId();
+            if (!mWidgetManager.bindAppWidgetIdIfAllowed(widgetId, info, options)) {
+                host.deleteAppWidgetId(widgetId);
+                throw new IllegalArgumentException("Unable to bind widget id");
+            }
+            item.appWidgetId = widgetId;
+        }
+        return item;
+    }
+
+    /**
+     * Returns a LauncherAppWidgetInfo with package name which is not present on the device
+     */
+    private LauncherAppWidgetInfo getInvalidWidgetInfo() {
+        String invalidPackage = "com.invalidpackage";
+        int count = 0;
+        String pkg = invalidPackage;
+
+        Set<String> activePackage = getOnUiThread(new Callable<Set<String>>() {
+            @Override
+            public Set<String> call() throws Exception {
+                return PackageInstallerCompat.getInstance(mTargetContext)
+                        .updateAndGetActiveSessionCache().keySet();
+            }
+        });
+        while(true) {
+            try {
+                mTargetContext.getPackageManager().getPackageInfo(
+                        pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
+            } catch (Exception e) {
+                if (!activePackage.contains(pkg)) {
+                    break;
+                }
+            }
+            pkg = invalidPackage + count;
+            count ++;
+        }
+        LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10,
+                new ComponentName(pkg, "com.test.widgetprovider"));
+        item.spanX = 2;
+        item.spanY = 2;
+        item.minSpanX = 2;
+        item.minSpanY = 2;
+        item.cellX = 0;
+        item.cellY = 0;
+        item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+        return item;
+    }
+
+    /**
+     * Runs the callback on the UI thread and returns the result.
+     */
+    private <T> T getOnUiThread(final Callable<T> callback) {
+        final AtomicReference<T> result = new AtomicReference<>(null);
+        try {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        result.set(callback.call());
+                    } catch (Exception e) { }
+                }
+            });
+        } catch (Throwable t) { }
+        return result.get();
+    }
+
+    /**
+     * Blocks the current thread until all the jobs in the main worker thread are complete.
+     */
+    private void waitUntilLoaderIdle() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        LauncherModel.sWorker.post(new Runnable() {
+            @Override
+            public void run() {
+                latch.countDown();
+            }
+        });
+        assertTrue(latch.await(5, TimeUnit.SECONDS));
+    }
+}