Merge "Provide previous stack when opening new window."
diff --git a/res/layout/drawer_layout.xml b/res/layout/drawer_layout.xml
index dec4e92..0dac0d5 100644
--- a/res/layout/drawer_layout.xml
+++ b/res/layout/drawer_layout.xml
@@ -14,62 +14,71 @@
      limitations under the License.
 -->
 
-<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/drawer_layout"
+<!-- CoordinatorLayout is necessary for various components (e.g. Snackbars, and
+     floating action buttons) to operate correctly. -->
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:id="@+id/coordinator_layout">
 
-    <LinearLayout
+    <android.support.v4.widget.DrawerLayout
+        android:id="@+id/drawer_layout"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
+        android:layout_height="match_parent">
 
-        <com.android.documentsui.DocumentsToolBar
-            android:id="@+id/toolbar"
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="?android:attr/actionBarSize"
-            android:background="?android:attr/colorPrimary"
-            android:elevation="8dp"
-            android:theme="?actionBarTheme"
-            android:popupTheme="?actionBarPopupTheme">
+            android:layout_height="match_parent"
+            android:orientation="vertical">
 
-            <Spinner
-                android:id="@+id/stack"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="4dp"
-                android:overlapAnchor="true" />
+            <com.android.documentsui.DocumentsToolBar
+                android:id="@+id/toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?android:attr/actionBarSize"
+                android:background="?android:attr/colorPrimary"
+                android:elevation="8dp"
+                android:theme="?actionBarTheme"
+                android:popupTheme="?actionBarPopupTheme">
 
-        </com.android.documentsui.DocumentsToolBar>
+                <Spinner
+                    android:id="@+id/stack"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="4dp"
+                    android:overlapAnchor="true" />
 
-        <include layout="@layout/directory_cluster"/>
+            </com.android.documentsui.DocumentsToolBar>
 
-    </LinearLayout>
+            <include layout="@layout/directory_cluster"/>
 
-    <LinearLayout
-        android:id="@+id/drawer_roots"
-        android:layout_width="256dp"
-        android:layout_height="match_parent"
-        android:layout_gravity="start"
-        android:orientation="vertical"
-        android:elevation="16dp"
-        android:background="@*android:color/white">
+        </LinearLayout>
 
-        <Toolbar
-            android:id="@+id/roots_toolbar"
-            android:layout_width="match_parent"
-            android:layout_height="?android:attr/actionBarSize"
-            android:background="?android:attr/colorPrimary"
-            android:elevation="8dp"
-            android:theme="?actionBarTheme"
-            android:popupTheme="?actionBarPopupTheme" />
+        <LinearLayout
+            android:id="@+id/drawer_roots"
+            android:layout_width="256dp"
+            android:layout_height="match_parent"
+            android:layout_gravity="start"
+            android:orientation="vertical"
+            android:elevation="16dp"
+            android:background="@*android:color/white">
 
-        <FrameLayout
-            android:id="@+id/container_roots"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1" />
+            <Toolbar
+                android:id="@+id/roots_toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?android:attr/actionBarSize"
+                android:background="?android:attr/colorPrimary"
+                android:elevation="8dp"
+                android:theme="?actionBarTheme"
+                android:popupTheme="?actionBarPopupTheme" />
 
-    </LinearLayout>
+            <FrameLayout
+                android:id="@+id/container_roots"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
 
-</android.support.v4.widget.DrawerLayout>
+        </LinearLayout>
+
+    </android.support.v4.widget.DrawerLayout>
+</android.support.design.widget.CoordinatorLayout>
diff --git a/res/layout/fixed_layout.xml b/res/layout/fixed_layout.xml
index eba9af4..403c667 100644
--- a/res/layout/fixed_layout.xml
+++ b/res/layout/fixed_layout.xml
@@ -14,49 +14,59 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<!-- CoordinatorLayout is necessary for various components (e.g. Snackbars, and
+     floating action buttons) to operate correctly. -->
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:id="@+id/coordinator_layout">
 
-    <com.android.documentsui.DocumentsToolBar
-        android:id="@+id/toolbar"
+    <LinearLayout 
         android:layout_width="match_parent"
-        android:layout_height="?android:attr/actionBarSize"
-        android:background="?android:attr/colorPrimary"
-        android:elevation="8dp"
-        android:theme="?actionBarTheme"
-        android:popupTheme="?actionBarPopupTheme">
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-        <Spinner
-            android:id="@+id/stack"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="4dp"
-            android:overlapAnchor="true" />
-
-    </com.android.documentsui.DocumentsToolBar>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
-        android:orientation="horizontal"
-        android:baselineAligned="false"
-        android:divider="?android:attr/dividerVertical"
-        android:showDividers="middle">
-
-        <FrameLayout
-            android:id="@+id/container_roots"
-            android:layout_width="256dp"
-            android:layout_height="match_parent" />
-
-        <include layout="@layout/directory_cluster"
-            android:layout_width="0dp"
-            android:layout_weight="1"
+        <com.android.documentsui.DocumentsToolBar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?android:attr/actionBarSize"
+            android:background="?android:attr/colorPrimary"
             android:elevation="8dp"
-            android:background="@color/material_grey_50" />
+            android:theme="?actionBarTheme"
+            android:popupTheme="?actionBarPopupTheme">
+
+            <Spinner
+                android:id="@+id/stack"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="4dp"
+                android:overlapAnchor="true" />
+
+        </com.android.documentsui.DocumentsToolBar>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:orientation="horizontal"
+            android:baselineAligned="false"
+            android:divider="?android:attr/dividerVertical"
+            android:showDividers="middle">
+
+            <FrameLayout
+                android:id="@+id/container_roots"
+                android:layout_width="256dp"
+                android:layout_height="match_parent" />
+
+            <include layout="@layout/directory_cluster"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:elevation="8dp"
+                android:background="@color/material_grey_50" />
+
+        </LinearLayout>
 
     </LinearLayout>
 
-</LinearLayout>
+</android.support.design.widget.CoordinatorLayout>
diff --git a/res/layout/single_pane_layout.xml b/res/layout/single_pane_layout.xml
index 20c3232..c5a5745 100644
--- a/res/layout/single_pane_layout.xml
+++ b/res/layout/single_pane_layout.xml
@@ -14,29 +14,39 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<!-- CoordinatorLayout is necessary for various components (e.g. Snackbars, and
+     floating action buttons) to operate correctly. -->
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:id="@+id/coordinator_layout">
 
-    <com.android.documentsui.DocumentsToolBar
-        android:id="@+id/toolbar"
+    <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="?android:attr/actionBarSize"
-        android:background="?android:attr/colorPrimary"
-        android:elevation="8dp"
-        android:theme="?actionBarTheme"
-        android:popupTheme="?actionBarPopupTheme">
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-        <Spinner
-            android:id="@+id/stack"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="4dp"
-            android:overlapAnchor="true" />
+        <com.android.documentsui.DocumentsToolBar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?android:attr/actionBarSize"
+            android:background="?android:attr/colorPrimary"
+            android:elevation="8dp"
+            android:theme="?actionBarTheme"
+            android:popupTheme="?actionBarPopupTheme">
 
-    </com.android.documentsui.DocumentsToolBar>
+            <Spinner
+                android:id="@+id/stack"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="4dp"
+                android:overlapAnchor="true" />
 
-    <include layout="@layout/directory_cluster"/>
+        </com.android.documentsui.DocumentsToolBar>
 
-</LinearLayout>
+        <include layout="@layout/directory_cluster"/>
+
+    </LinearLayout>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/res/values/config.xml b/res/values/config.xml
index ed7820b..ce5b174 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -15,5 +15,5 @@
 -->
 
 <resources>
-    <bool name="productivity_device">true</bool>
+    <bool name="productivity_device">false</bool>
 </resources>
diff --git a/src/com/android/documentsui/CopyService.java b/src/com/android/documentsui/CopyService.java
index b2d069d..362052c 100644
--- a/src/com/android/documentsui/CopyService.java
+++ b/src/com/android/documentsui/CopyService.java
@@ -20,6 +20,7 @@
 import static com.android.documentsui.model.DocumentInfo.getCursorLong;
 import static com.android.documentsui.model.DocumentInfo.getCursorString;
 
+import android.app.Activity;
 import android.app.IntentService;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -37,6 +38,7 @@
 import android.os.SystemClock;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
+import android.support.design.widget.Snackbar;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.widget.Toast;
@@ -106,10 +108,10 @@
      * @param srcDocs A list of src files to copy.
      * @param dstStack The copy destination stack.
      */
-    public static void start(Context context, List<DocumentInfo> srcDocs, DocumentStack dstStack,
+    public static void start(Activity activity, List<DocumentInfo> srcDocs, DocumentStack dstStack,
             int mode) {
-        final Resources res = context.getResources();
-        final Intent copyIntent = new Intent(context, CopyService.class);
+        final Resources res = activity.getResources();
+        final Intent copyIntent = new Intent(activity, CopyService.class);
         copyIntent.putParcelableArrayListExtra(
                 EXTRA_SRC_LIST, new ArrayList<DocumentInfo>(srcDocs));
         copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) dstStack);
@@ -117,10 +119,10 @@
 
         int toastMessage = (mode == TRANSFER_MODE_COPY) ? R.plurals.copy_begin
                 : R.plurals.move_begin;
-        Toast.makeText(context,
+        Shared.makeSnackbar(activity,
                 res.getQuantityString(toastMessage, srcDocs.size(), srcDocs.size()),
-                Toast.LENGTH_SHORT).show();
-        context.startService(copyIntent);
+                Snackbar.LENGTH_SHORT).show();
+        activity.startService(copyIntent);
     }
 
     @Override
diff --git a/src/com/android/documentsui/CreateDirectoryFragment.java b/src/com/android/documentsui/CreateDirectoryFragment.java
index e408e6e..9f44516 100644
--- a/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
+import android.support.design.widget.Snackbar;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -39,7 +40,6 @@
 import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
-import android.widget.Toast;
 
 import com.android.documentsui.model.DocumentInfo;
 
@@ -147,7 +147,7 @@
                 // Navigate into newly created child
                 mActivity.onDirectoryCreated(result);
             } else {
-                Toast.makeText(mActivity, R.string.create_error, Toast.LENGTH_SHORT).show();
+                Shared.makeSnackbar(mActivity, R.string.create_error, Snackbar.LENGTH_SHORT).show();
             }
 
             mActivity.setPending(false);
diff --git a/src/com/android/documentsui/DirectoryFragment.java b/src/com/android/documentsui/DirectoryFragment.java
index ed02c28..b3ce103 100644
--- a/src/com/android/documentsui/DirectoryFragment.java
+++ b/src/com/android/documentsui/DirectoryFragment.java
@@ -91,7 +91,6 @@
 import android.view.ViewParent;
 import android.widget.ImageView;
 import android.widget.TextView;
-import android.widget.Toast;
 
 import com.android.documentsui.BaseActivity.DocumentContext;
 import com.android.documentsui.MultiSelectManager.Selection;
@@ -805,8 +804,8 @@
 
         mModel.markForDeletion(selected);
 
-        Activity activity = getActivity();
-        Snackbar.make(this.getView(), message, Snackbar.LENGTH_LONG)
+        final Activity activity = getActivity();
+        Shared.makeSnackbar(activity, message, Snackbar.LENGTH_LONG)
                 .setAction(
                         R.string.undo,
                         new android.view.View.OnClickListener() {
@@ -821,11 +820,11 @@
                                     mModel.undoDeletion();
                                 } else {
                                     mModel.finalizeDeletion(
-                                            new Runnable() {
+                                            new Model.DeletionListener() {
                                                 @Override
-                                                public void run() {
-                                                    Snackbar.make(
-                                                            DirectoryFragment.this.getView(),
+                                                public void onError() {
+                                                    Shared.makeSnackbar(
+                                                            activity,
                                                             R.string.toast_failed_delete,
                                                             Snackbar.LENGTH_LONG)
                                                             .show();
@@ -1245,9 +1244,11 @@
 
     private void copyDocuments(final List<DocumentInfo> docs, final DocumentInfo destination) {
         if (!canCopy(docs, destination)) {
-            Toast.makeText(
+            Shared.makeSnackbar(
                     getActivity(),
-                    R.string.clipboard_files_cannot_paste, Toast.LENGTH_SHORT).show();
+                    R.string.clipboard_files_cannot_paste,
+                    Snackbar.LENGTH_SHORT)
+                    .show();
             return;
         }
 
@@ -1297,10 +1298,10 @@
             void onDocumentsReady(List<DocumentInfo> docs) {
                 mClipper.clipDocuments(docs);
                 Activity activity = getActivity();
-                Toast.makeText(activity,
+                Shared.makeSnackbar(activity,
                         activity.getResources().getQuantityString(
                                 R.plurals.clipboard_files_clipped, docs.size(), docs.size()),
-                                Toast.LENGTH_SHORT).show();
+                                Snackbar.LENGTH_SHORT).show();
             }
         }.execute(items);
     }
@@ -1864,9 +1865,9 @@
          * @param view The view which will be used to interact with the user (e.g. surfacing
          * snackbars) for errors, info, etc.
          */
-        void finalizeDeletion(Runnable errorCallback) {
+        void finalizeDeletion(DeletionListener listener) {
             final ContentResolver resolver = mContext.getContentResolver();
-            DeleteFilesTask task = new DeleteFilesTask(resolver, errorCallback);
+            DeleteFilesTask task = new DeleteFilesTask(resolver, listener);
             task.execute();
         }
 
@@ -1876,16 +1877,16 @@
          */
         private class DeleteFilesTask extends AsyncTask<Void, Void, List<DocumentInfo>> {
             private ContentResolver mResolver;
-            private Runnable mErrorCallback;
+            private DeletionListener mListener;
 
             /**
              * @param resolver A ContentResolver for performing the actual file deletions.
              * @param errorCallback A Runnable that is executed in the event that one or more errors
              *     occured while copying files.  Execution will occur on the UI thread.
              */
-            public DeleteFilesTask(ContentResolver resolver, Runnable errorCallback) {
+            public DeleteFilesTask(ContentResolver resolver, DeletionListener listener) {
                 mResolver = resolver;
-                mErrorCallback = errorCallback;
+                mListener = listener;
             }
 
             @Override
@@ -1919,15 +1920,29 @@
 
                 if (hadTrouble) {
                     // TODO show which files failed? b/23720103
-                    mErrorCallback.run();
+                    mListener.onError();
                     if (DEBUG) Log.d(TAG, "Deletion task completed.  Some deletions failed.");
                 } else {
                     if (DEBUG) Log.d(TAG, "Deletion task completed successfully.");
                 }
                 mMarkedForDeletion.clear();
+
+                mListener.onCompletion();
             }
         }
 
+        static class DeletionListener {
+            /**
+             * Called when deletion has completed (regardless of whether an error occurred).
+             */
+            void onCompletion() {}
+
+            /**
+             * Called at the end of a deletion operation that produced one or more errors.
+             */
+            void onError() {}
+        }
+
         void addUpdateListener(UpdateListener listener) {
             checkState(mUpdateListener == null);
             mUpdateListener = listener;
diff --git a/src/com/android/documentsui/DocumentsActivity.java b/src/com/android/documentsui/DocumentsActivity.java
index 6421375..ced03cc 100644
--- a/src/com/android/documentsui/DocumentsActivity.java
+++ b/src/com/android/documentsui/DocumentsActivity.java
@@ -41,6 +41,7 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.provider.DocumentsContract;
+import android.support.design.widget.Snackbar;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -48,7 +49,6 @@
 import android.view.WindowManager;
 import android.widget.BaseAdapter;
 import android.widget.Spinner;
-import android.widget.Toast;
 import android.widget.Toolbar;
 
 import com.android.documentsui.RecentsProvider.RecentColumns;
@@ -611,8 +611,8 @@
             if (result != null) {
                 onTaskFinished(result);
             } else {
-                Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT)
-                        .show();
+                Shared.makeSnackbar(
+                    DocumentsActivity.this, R.string.save_error, Snackbar.LENGTH_SHORT).show();
             }
 
             setPending(false);
diff --git a/src/com/android/documentsui/FilesActivity.java b/src/com/android/documentsui/FilesActivity.java
index 8e67079..f6a5131 100644
--- a/src/com/android/documentsui/FilesActivity.java
+++ b/src/com/android/documentsui/FilesActivity.java
@@ -33,6 +33,7 @@
 import android.os.Parcelable;
 import android.provider.DocumentsContract;
 import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.Menu;
@@ -40,7 +41,6 @@
 import android.view.View;
 import android.widget.BaseAdapter;
 import android.widget.Spinner;
-import android.widget.Toast;
 import android.widget.Toolbar;
 
 import com.android.documentsui.RecentsProvider.ResumeColumns;
@@ -347,7 +347,7 @@
         try {
             startActivity(intent);
         } catch (ActivityNotFoundException ex2) {
-            Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show();
+            Shared.makeSnackbar(this, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show();
         }
     }
 
diff --git a/src/com/android/documentsui/ManageRootActivity.java b/src/com/android/documentsui/ManageRootActivity.java
index 4754899..ed7333d 100644
--- a/src/com/android/documentsui/ManageRootActivity.java
+++ b/src/com/android/documentsui/ManageRootActivity.java
@@ -31,12 +31,12 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.DocumentsContract;
+import android.support.design.widget.Snackbar;
 import android.util.Log;
 import android.view.Menu;
 import android.view.View;
 import android.widget.BaseAdapter;
 import android.widget.Spinner;
-import android.widget.Toast;
 import android.widget.Toolbar;
 
 import com.android.documentsui.RecentsProvider.ResumeColumns;
@@ -184,7 +184,8 @@
                 try {
                     startActivity(view);
                 } catch (ActivityNotFoundException ex2) {
-                    Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show();
+                    Shared.makeSnackbar(this, R.string.toast_no_application, Snackbar.LENGTH_SHORT)
+                            .show();
                 }
             }
         }
diff --git a/src/com/android/documentsui/Shared.java b/src/com/android/documentsui/Shared.java
index a4d6dc5..e06d6a5 100644
--- a/src/com/android/documentsui/Shared.java
+++ b/src/com/android/documentsui/Shared.java
@@ -16,7 +16,12 @@
 
 package com.android.documentsui;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.app.Activity;
 import android.content.Context;
+import android.support.design.widget.Snackbar;
+import android.view.View;
 
 /** @hide */
 public final class Shared {
@@ -30,4 +35,14 @@
     public static final String getQuantityString(Context context, int resourceId, int quantity) {
         return context.getResources().getQuantityString(resourceId, quantity, quantity);
     }
+
+    public static final Snackbar makeSnackbar(Activity activity, int messageId, int duration) {
+        return makeSnackbar(activity, activity.getResources().getText(messageId), duration);
+    }
+
+    public static final Snackbar makeSnackbar(Activity activity, CharSequence message, int duration)
+    {
+        final View view = checkNotNull(activity.findViewById(R.id.coordinator_layout));
+        return Snackbar.make(view, message, duration);
+    }
 }
diff --git a/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java b/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
index 1895a6e..98ffb77 100644
--- a/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
+++ b/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
@@ -34,6 +34,9 @@
 import com.android.documentsui.model.DocumentInfo;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 
 
 public class DirectoryFragmentModelTest extends AndroidTestCase {
@@ -77,14 +80,6 @@
         delete(2, 4);
 
         assertEquals(ITEM_COUNT - 2, model.getItemCount());
-
-        // Finalize the deletion.  Provide a callback that just ignores errors.
-        model.finalizeDeletion(
-              new Runnable() {
-                  @Override
-                  public void run() {}
-              });
-        assertEquals(ITEM_COUNT - 2, model.getItemCount());
     }
 
     // Tests that the item count is correct after a deletion is undone.
@@ -95,7 +90,6 @@
         // Undo the deletion
         model.undoDeletion();
         assertEquals(ITEM_COUNT, model.getItemCount());
-
     }
 
     // Tests that the right things are marked for deletion.
@@ -125,6 +119,15 @@
         assertEquals("0", docs.get(0).documentId);
         assertEquals("1", docs.get(1).documentId);
         assertEquals("4", docs.get(2).documentId);
+
+        TestDeletionListener testListener = new TestDeletionListener();
+        model.finalizeDeletion(testListener);
+        testListener.waitForDone();
+
+        docs = getDocumentInfo(0, 1, 2);
+        assertEquals("0", docs.get(0).documentId);
+        assertEquals("1", docs.get(1).documentId);
+        assertEquals("2", docs.get(2).documentId);
     }
 
     // Tests that Model.getItem returns the right items after a deletion is undone.
@@ -176,4 +179,20 @@
             return null;
         }
     }
+
+    private static class TestDeletionListener extends Model.DeletionListener {
+        final CountDownLatch mSignal = new CountDownLatch(1);
+
+        @Override
+        public void onCompletion() {
+            mSignal.countDown();
+        }
+
+        public void waitForDone() {
+            try {
+                boolean timeout = mSignal.await(10, TimeUnit.SECONDS);
+                assertTrue("Timed out waiting for deletion completion", timeout);
+            } catch (InterruptedException e) {}
+        }
+    }
 }
diff --git a/tests/src/com/android/documentsui/MultiSelectManagerTest.java b/tests/src/com/android/documentsui/MultiSelectManagerTest.java
index 25d4ed4..2447469 100644
--- a/tests/src/com/android/documentsui/MultiSelectManagerTest.java
+++ b/tests/src/com/android/documentsui/MultiSelectManagerTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.support.v7.widget.RecyclerView;
+import android.test.AndroidTestCase;
 import android.util.SparseBooleanArray;
 import android.view.MotionEvent;
 import android.view.View;
@@ -27,8 +28,6 @@
 
 import com.android.documentsui.MultiSelectManager.Selection;
 
-import org.junit.Before;
-import org.junit.Test;
 import org.mockito.Mockito;
 
 import java.util.ArrayList;
@@ -36,7 +35,7 @@
 import java.util.List;
 import java.util.Set;
 
-public class MultiSelectManagerTest {
+public class MultiSelectManagerTest extends AndroidTestCase {
 
     private static final List<String> items;
     static {
@@ -54,7 +53,6 @@
     private TestCallback mCallback;
     private EventHelper mEventHelper;
 
-    @Before
     public void setUp() throws Exception {
         mAdapter = new TestAdapter(items);
         mCallback = new TestCallback();
@@ -63,65 +61,61 @@
         mManager.addCallback(mCallback);
     }
 
-    @Test
-    public void mouseClick_StartsSelectionMode() {
+    public void testMouseClick_StartsSelectionMode() {
         click(7);
         assertSelection(7);
     }
 
-    @Test
-    public void mouseClick_ShiftClickExtendsSelection() {
+    public void testMouseClick_NotifiesSelectionChanged() {
+        click(7);
+        mCallback.assertSelectionChanged();
+    }
+
+    public void testMouseClick_ShiftClickExtendsSelection() {
         longPress(7);
         shiftClick(11);
         assertRangeSelection(7, 11);
     }
 
-    @Test
-    public void mouseClick_NoPosition_ClearsSelection() {
+    public void testMouseClick_NoPosition_ClearsSelection() {
         longPress(7);
         click(11);
         click(RecyclerView.NO_POSITION);
         assertSelection();
     }
 
-    @Test
-    public void setSelectionFocusBegin() {
+    public void testSetSelectionFocusBegin() {
         mManager.setItemSelected(7, true);
         mManager.setSelectionFocusBegin(7);
         shiftClick(11);
         assertRangeSelection(7, 11);
     }
 
-    @Test
-    public void longPress_StartsSelectionMode() {
+    public void testLongPress_StartsSelectionMode() {
         longPress(7);
         assertSelection(7);
     }
 
-    @Test
-    public void longPress_SecondPressExtendsSelection() {
+    public void testLongPress_SecondPressExtendsSelection() {
         longPress(7);
         longPress(99);
         assertSelection(7, 99);
     }
 
-    @Test
-    public void singleTapUp_UnselectsSelectedItem() {
+    public void testSingleTapUp_UnselectsSelectedItem() {
         longPress(7);
         tap(7);
         assertSelection();
     }
 
-    @Test
-    public void singleTapUp_NoPosition_ClearsSelection() {
+    public void testSingleTapUp_NoPosition_ClearsSelection() {
         longPress(7);
         tap(11);
         tap(RecyclerView.NO_POSITION);
         assertSelection();
     }
 
-    @Test
-    public void singleTapUp_ExtendsSelection() {
+    public void testSingleTapUp_ExtendsSelection() {
         longPress(99);
         tap(7);
         tap(13);
@@ -129,30 +123,26 @@
         assertSelection(7, 99, 13, 129899);
     }
 
-    @Test
-    public void singleTapUp_ShiftCreatesRangeSelection() {
+    public void testSingleTapUp_ShiftCreatesRangeSelection() {
         longPress(7);
         shiftTap(17);
         assertRangeSelection(7, 17);
     }
 
-    @Test
-    public void singleTapUp_ShiftCreatesRangeSeletion_Backwards() {
+    public void testSingleTapUp_ShiftCreatesRangeSeletion_Backwards() {
         longPress(17);
         shiftTap(7);
         assertRangeSelection(7, 17);
     }
 
-    @Test
-    public void singleTapUp_SecondShiftClickExtendsSelection() {
+    public void testSingleTapUp_SecondShiftClickExtendsSelection() {
         longPress(7);
         shiftTap(11);
         shiftTap(17);
         assertRangeSelection(7, 17);
     }
 
-    @Test
-    public void singleTapUp_MultipleContiguousRangesSelected() {
+    public void testSingleTapUp_MultipleContiguousRangesSelected() {
         longPress(7);
         shiftTap(11);
         tap(20);
@@ -162,16 +152,14 @@
         assertSelectionSize(11);
     }
 
-    @Test
-    public void singleTapUp_ShiftReducesSelectionRange_FromPreviousShiftClick() {
+    public void testSingleTapUp_ShiftReducesSelectionRange_FromPreviousShiftClick() {
         longPress(7);
         shiftTap(17);
         shiftTap(10);
         assertRangeSelection(7, 10);
     }
 
-    @Test
-    public void singleTapUp_ShiftReducesSelectionRange_FromPreviousShiftClick_Backwards() {
+    public void testSingleTapUp_ShiftReducesSelectionRange_FromPreviousShiftClick_Backwards() {
         mManager.onLongPress(TestInputEvent.tap(17));
         shiftTap(7);
         shiftTap(14);
@@ -179,16 +167,14 @@
     }
 
 
-    @Test
-    public void singleTapUp_ShiftReversesSelectionDirection() {
+    public void testSingleTapUp_ShiftReversesSelectionDirection() {
         longPress(7);
         shiftTap(17);
         shiftTap(0);
         assertRangeSelection(0, 7);
     }
 
-    @Test
-    public void singleSelectMode() {
+    public void testSingleSelectMode() {
         mManager = new MultiSelectManager(mAdapter, mEventHelper, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
         longPress(20);
@@ -196,8 +182,7 @@
         assertSelection(13);
     }
 
-    @Test
-    public void singleSelectMode_ShiftTap() {
+    public void testSingleSelectMode_ShiftTap() {
         mManager = new MultiSelectManager(mAdapter, mEventHelper, MultiSelectManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
         longPress(13);
@@ -205,8 +190,7 @@
         assertSelection(20);
     }
 
-    @Test
-    public void provisionaSelection() {
+    public void testProvisionalSelection() {
         Selection s = mManager.getSelection();
         assertSelection();
 
@@ -298,6 +282,7 @@
         Set<Integer> ignored = new HashSet<>();
         private int mLastChangedPosition;
         private boolean mLastChangedSelected;
+        private boolean mSelectionChanged = false;
 
         @Override
         public void onItemStateChanged(int position, boolean selected) {
@@ -311,7 +296,13 @@
         }
 
         @Override
-        public void onSelectionChanged() {}
+        public void onSelectionChanged() {
+            mSelectionChanged = true;
+        }
+
+        void assertSelectionChanged() {
+            assertTrue(mSelectionChanged);
+        }
     }
 
     private static final class TestHolder extends RecyclerView.ViewHolder {
diff --git a/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java b/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java
index 87d7e15..aa50b48 100644
--- a/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java
+++ b/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java
@@ -22,14 +22,12 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.test.AndroidTestCase;
 import android.util.SparseBooleanArray;
 
 import com.android.documentsui.MultiSelectManager.GridModel;
 
-import org.junit.After;
-import org.junit.Test;
-
-public class MultiSelectManager_GridModelTest {
+public class MultiSelectManager_GridModelTest extends AndroidTestCase {
 
     private static final int VIEW_PADDING_PX = 5;
     private static final int CHILD_VIEW_EDGE_PX = 100;
@@ -53,14 +51,13 @@
                 });
     }
 
-    @After
+    @Override
     public void tearDown() {
         model = null;
         helper = null;
         lastSelection = null;
     }
 
-    @Test
     public void testSelectionLeftOfItems() {
         setUp(20, 5);
         model.startSelection(new Point(0, 10));
@@ -69,7 +66,6 @@
         assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin());
     }
 
-    @Test
     public void testSelectionRightOfItems() {
         setUp(20, 4);
         model.startSelection(new Point(viewWidth - 1, 10));
@@ -78,7 +74,6 @@
         assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin());
     }
 
-    @Test
     public void testSelectionAboveItems() {
         setUp(20, 4);
         model.startSelection(new Point(10, 0));
@@ -87,7 +82,6 @@
         assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin());
     }
 
-    @Test
     public void testSelectionBelowItems() {
         setUp(5, 4);
         model.startSelection(new Point(10, VIEWPORT_HEIGHT - 1));
@@ -96,7 +90,6 @@
         assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin());
     }
 
-    @Test
     public void testVerticalSelectionBetweenItems() {
         setUp(20, 4);
         model.startSelection(new Point(106, 0));
@@ -105,7 +98,6 @@
         assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin());
     }
 
-    @Test
     public void testHorizontalSelectionBetweenItems() {
         setUp(20, 4);
         model.startSelection(new Point(0, 105));
@@ -114,7 +106,6 @@
         assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin());
     }
 
-    @Test
     public void testGrowingAndShrinkingSelection() {
         setUp(20, 4);
         model.startSelection(new Point(0, 0));
@@ -145,7 +136,6 @@
         assertEquals(GridModel.NOT_SET, model.getPositionNearestOrigin());
     }
 
-    @Test
     public void testSelectionMovingAroundOrigin() {
         setUp(16, 4);
         model.startSelection(new Point(210, 210));
@@ -160,7 +150,6 @@
         assertEquals(10, model.getPositionNearestOrigin());
     }
 
-    @Test
     public void testScrollingBandSelect() {
         setUp(40, 4);
         model.startSelection(new Point(0, 0));
diff --git a/tests/src/com/android/documentsui/MultiSelectManager_SelectionTest.java b/tests/src/com/android/documentsui/MultiSelectManager_SelectionTest.java
index 51b542b..eddf4ef 100644
--- a/tests/src/com/android/documentsui/MultiSelectManager_SelectionTest.java
+++ b/tests/src/com/android/documentsui/MultiSelectManager_SelectionTest.java
@@ -18,16 +18,16 @@
 
 import static org.junit.Assert.*;
 
+import android.test.AndroidTestCase;
+
 import com.android.documentsui.MultiSelectManager.Selection;
 
-import org.junit.Before;
-import org.junit.Test;
 
-public class MultiSelectManager_SelectionTest {
+public class MultiSelectManager_SelectionTest extends AndroidTestCase{
 
     private Selection selection;
 
-    @Before
+    @Override
     public void setUp() throws Exception {
         selection = new Selection();
         selection.add(3);
@@ -35,8 +35,7 @@
         selection.add(9);
     }
 
-    @Test
-    public void add() {
+    public void testAdd() {
         // We added in setUp.
         assertEquals(3, selection.size());
         assertContains(3);
@@ -44,29 +43,25 @@
         assertContains(9);
     }
 
-    @Test
-    public void remove() {
+    public void testRemove() {
         selection.remove(3);
         selection.remove(5);
         assertEquals(1, selection.size());
         assertContains(9);
     }
 
-    @Test
-    public void clear() {
+    public void testClear() {
         selection.clear();
         assertEquals(0, selection.size());
     }
 
-    @Test
-    public void isEmpty() {
+    public void testIsEmpty() {
         assertTrue(new Selection().isEmpty());
         selection.clear();
         assertTrue(selection.isEmpty());
     }
 
-    @Test
-    public void sizeAndGet() {
+    public void testSizeAndGet() {
         Selection other = new Selection();
         for (int i = 0; i < selection.size(); i++) {
             other.add(selection.get(i));
@@ -74,13 +69,11 @@
         assertEquals(selection.size(), other.size());
     }
 
-    @Test
-    public void equalsSelf() {
+    public void testEqualsSelf() {
         assertEquals(selection, selection);
     }
 
-    @Test
-    public void equalsOther() {
+    public void testEqualsOther() {
         Selection other = new Selection();
         other.add(3);
         other.add(5);
@@ -89,23 +82,20 @@
         assertEquals(selection.hashCode(), other.hashCode());
     }
 
-    @Test
-    public void equalsCopy() {
+    public void testEqualsCopy() {
         Selection other = new Selection();
         other.copyFrom(selection);
         assertEquals(selection, other);
         assertEquals(selection.hashCode(), other.hashCode());
     }
 
-    @Test
-    public void notEquals() {
+    public void testNotEquals() {
         Selection other = new Selection();
         other.add(789);
         assertFalse(selection.equals(other));
     }
 
-    @Test
-    public void expandBefore() {
+    public void testExpandBefore() {
         selection.expand(2, 10);
         assertEquals(3, selection.size());
         assertContains(13);
@@ -113,8 +103,7 @@
         assertContains(19);
     }
 
-    @Test
-    public void expandAfter() {
+    public void testExpandAfter() {
         selection.expand(10, 10);
         assertEquals(3, selection.size());
         assertContains(3);
@@ -122,8 +111,7 @@
         assertContains(9);
     }
 
-    @Test
-    public void expandSplit() {
+    public void testExpandSplit() {
         selection.expand(5, 10);
         assertEquals(3, selection.size());
         assertContains(3);
@@ -131,8 +119,7 @@
         assertContains(19);
     }
 
-    @Test
-    public void expandEncompased() {
+    public void testExpandEncompased() {
         selection.expand(2, 10);
         assertEquals(3, selection.size());
         assertContains(13);
@@ -140,8 +127,7 @@
         assertContains(19);
     }
 
-    @Test
-    public void collapseBefore() {
+    public void testCollapseBefore() {
         selection.collapse(0, 2);
         assertEquals(3, selection.size());
         assertContains(1);
@@ -149,8 +135,7 @@
         assertContains(7);
     }
 
-    @Test
-    public void collapseAfter() {
+    public void testCollapseAfter() {
         selection.collapse(10, 10);
         assertEquals(3, selection.size());
         assertContains(3);
@@ -158,8 +143,7 @@
         assertContains(9);
     }
 
-    @Test
-    public void collapseAcross() {
+    public void testCollapseAcross() {
         selection.collapse(0, 10);
         assertEquals(0, selection.size());
     }
diff --git a/tests/src/com/android/documentsui/UnitTests.java b/tests/src/com/android/documentsui/UnitTests.java
deleted file mode 100644
index be3f251..0000000
--- a/tests/src/com/android/documentsui/UnitTests.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui;
-
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-import org.junit.runners.Suite.SuiteClasses;
-
-@RunWith(Suite.class)
-@SuiteClasses({
-        MultiSelectManager_GridModelTest.class,
-        MultiSelectManager_SelectionTest.class,
-        MultiSelectManagerTest.class
-})
-
-/**
- * This test suite can be run using the "art" runtime (which can be built
- * via the `build-art-host` target.) You'll also need to "mma -j32" the
- * DocumentsUI package to ensure all deps are built.
- *
- * <p>Once the dependencies have been built, the tests can be executed as follows:
- *
- * <pre>
- *  CP=$OUT/system/framework/framework.jar:\
- *      $OUT/system/framework/core-junit.jar:\
- *      $OUT/system/app/DocumentsUI/DocumentsUI.apk:\
- *      $OUT/data/app/DocumentsUITests/DocumentsUITests.apk
- *
- *  art -cp $CP org.junit.runner.JUnitCore com.android.documentsui.UnitTests
- * </pre>
- */
-public class UnitTests {}