Merge "Followups to ag/1553792/" into nyc-andromeda-dev
diff --git a/res/layout/dropdown_sort_widget.xml b/res/layout/dropdown_sort_widget.xml
index ce5e06f..3415f9c 100644
--- a/res/layout/dropdown_sort_widget.xml
+++ b/res/layout/dropdown_sort_widget.xml
@@ -30,7 +30,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
- android:textAppearance="@android:style/TextAppearance.Material.Subhead"/>
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead" />
<ImageView
android:id="@+id/sort_arrow"
@@ -38,6 +38,7 @@
android:layout_height="@dimen/dropdown_sort_widget_icon_size"
android:layout_marginStart="10dp"
android:padding="3dp"
- android:src="@drawable/ic_sort_arrow"/>
+ android:src="@drawable/ic_sort_arrow"
+ android:accessibilityTraversalAfter="@id/sort_dimen_dropdown"/>
</LinearLayout>
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 131d2f7..86093f9 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -38,6 +38,7 @@
import com.android.documentsui.dirlist.AnimationView.AnimationType;
import com.android.documentsui.dirlist.AnimationView;
import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.files.LauncherActivity;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.roots.LoadRootTask;
@@ -62,17 +63,18 @@
protected final State mState;
protected final RootsAccess mRoots;
protected final DocumentsAccess mDocs;
- protected final ProviderAccess mProviders;
+ protected final FocusHandler mFocusHandler;
protected final SelectionManager mSelectionMgr;
protected final SearchViewManager mSearchMgr;
protected final Lookup<String, Executor> mExecutors;
+
public AbstractActionHandler(
T activity,
State state,
RootsAccess roots,
DocumentsAccess docs,
- ProviderAccess providers,
+ FocusHandler focusHandler,
SelectionManager selectionMgr,
SearchViewManager searchMgr,
Lookup<String, Executor> executors) {
@@ -80,7 +82,7 @@
assert(activity != null);
assert(state != null);
assert(roots != null);
- assert(providers != null);
+ assert(focusHandler != null);
assert(selectionMgr != null);
assert(searchMgr != null);
assert(docs != null);
@@ -89,7 +91,7 @@
mState = state;
mRoots = roots;
mDocs = docs;
- mProviders = providers;
+ mFocusHandler = focusHandler;
mSelectionMgr = selectionMgr;
mSearchMgr = searchMgr;
mExecutors = executors;
@@ -181,33 +183,33 @@
private void openFolderInSearchResult(@Nullable DocumentStack stack, DocumentInfo doc) {
if (stack == null) {
- mState.popDocumentsToRoot();
+ mState.stack.popToRootDocument();
// Update navigator to give horizontal breadcrumb a chance to update documents. It
// doesn't update its content if the size of document stack doesn't change.
// TODO: update breadcrumb to take range update.
mActivity.updateNavigator();
- mState.pushDocument(doc);
+ mState.stack.push(doc);
} else {
- if (!Objects.equals(mState.stack.root, stack.root)) {
- Log.w(TAG, "Provider returns " + stack.root + " rather than expected "
- + mState.stack.root);
+ if (!Objects.equals(mState.stack.getRoot(), stack.getRoot())) {
+ Log.w(TAG, "Provider returns " + stack.getRoot() + " rather than expected "
+ + mState.stack.getRoot());
}
- mState.stack.clear();
+ mState.stack.reset();
// Update navigator to give horizontal breadcrumb a chance to update documents. It
// doesn't update its content if the size of document stack doesn't change.
// TODO: update breadcrumb to take range update.
mActivity.updateNavigator();
- mState.setStack(stack);
+ mState.stack.reset(stack);
}
// Show an opening animation only if pressing "back" would get us back to the
// previous directory. Especially after opening a root document, pressing
// back, wouldn't go to the previous root, but close the activity.
- final int anim = (mState.hasLocationChanged() && mState.stack.size() > 1)
+ final int anim = (mState.stack.hasLocationChanged() && mState.stack.size() > 1)
? AnimationView.ANIM_ENTER : AnimationView.ANIM_NONE;
mActivity.refreshCurrentRootAndDirectory(anim);
}
@@ -226,16 +228,26 @@
assert(currentDoc != null);
mActivity.notifyDirectoryNavigated(currentDoc.derivedUri);
- mState.pushDocument(currentDoc);
+ mState.stack.push(currentDoc);
// Show an opening animation only if pressing "back" would get us back to the
// previous directory. Especially after opening a root document, pressing
// back, wouldn't go to the previous root, but close the activity.
- final int anim = (mState.hasLocationChanged() && mState.stack.size() > 1)
+ final int anim = (mState.stack.hasLocationChanged() && mState.stack.size() > 1)
? AnimationView.ANIM_ENTER : AnimationView.ANIM_NONE;
mActivity.refreshCurrentRootAndDirectory(anim);
}
@Override
+ public void cutToClipboard() {
+ throw new UnsupportedOperationException("Cut not supported!");
+ }
+
+ @Override
+ public void copyToClipboard() {
+ throw new UnsupportedOperationException("Copy not supported!");
+ }
+
+ @Override
public void deleteSelectedDocuments() {
throw new UnsupportedOperationException("Delete not supported!");
}
@@ -248,12 +260,10 @@
protected final void loadDocument(Uri uri, LoadDocStackCallback callback) {
new LoadDocStackTask(
mActivity,
- uri,
mRoots,
mDocs,
- mProviders,
callback
- ).executeOnExecutor(mExecutors.lookup(uri.getAuthority()));
+ ).executeOnExecutor(mExecutors.lookup(uri.getAuthority()), uri);
}
@Override
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index 7f6f923..d92ebbb 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -65,6 +65,13 @@
void openContainerDocument(DocumentInfo doc);
+ void cutToClipboard();
+
+ void copyToClipboard();
+
+ /**
+ * In general, selected = selection or single focused item
+ */
void deleteSelectedDocuments();
void shareSelectedDocuments();
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index a6fa656..96b6832 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -335,7 +335,7 @@
root.isRecents() || root.isDownloads() ? View.VISIBLE : View.INVISIBLE);
// Clear entire backstack and start in new root
- mState.onRootChanged(root);
+ mState.stack.changeRoot(root);
// Recents is always in memory, so we just load it directly.
// Otherwise we delegate loading data from disk to a task
@@ -564,8 +564,9 @@
@Override
public RootInfo getCurrentRoot() {
- if (mState.stack.root != null) {
- return mState.stack.root;
+ RootInfo root = mState.stack.getRoot();
+ if (root != null) {
+ return root;
} else {
return mRoots.getRecentsRoot();
}
@@ -665,20 +666,21 @@
* locked, open/close it as appropriate.
*/
void toggleNavDrawerFocus() {
+ boolean toogleHappened = false;
if (mNavDrawerHasFocus) {
mDrawer.setOpen(false);
DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
- if (df != null) {
- df.requestFocus();
- }
+ assert (df != null);
+ toogleHappened = df.requestFocus();
} else {
mDrawer.setOpen(true);
RootsFragment rf = RootsFragment.get(getFragmentManager());
- if (rf != null) {
- rf.requestFocus();
- }
+ assert (rf != null);
+ toogleHappened = rf.requestFocus();
}
- mNavDrawerHasFocus = !mNavDrawerHasFocus;
+ if (toogleHappened) {
+ mNavDrawerHasFocus = !mNavDrawerHasFocus;
+ }
}
/**
diff --git a/src/com/android/documentsui/DocumentsAccess.java b/src/com/android/documentsui/DocumentsAccess.java
index 8cfaa5a..80d4589 100644
--- a/src/com/android/documentsui/DocumentsAccess.java
+++ b/src/com/android/documentsui/DocumentsAccess.java
@@ -18,11 +18,13 @@
import android.annotation.Nullable;
import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Path;
import android.util.Log;
import com.android.documentsui.archives.ArchivesProvider;
@@ -34,7 +36,8 @@
import java.util.List;
/**
- * Provides synchronous access to {@link DocumentInfo} instances given some identifying information.
+ * Provides synchronous access to {@link DocumentInfo} instances given some identifying information
+ * and some documents API.
*/
public interface DocumentsAccess {
@@ -42,7 +45,10 @@
@Nullable DocumentInfo getDocument(Uri uri);
@Nullable DocumentInfo getArchiveDocument(Uri uri);
- @Nullable List<DocumentInfo> getDocuments(String authority, List<String> docIds);
+ boolean isDocumentUri(Uri uri);
+ @Nullable Path findPath(Uri uri) throws RemoteException;
+
+ List<DocumentInfo> getDocuments(String authority, List<String> docIds) throws RemoteException;
public static DocumentsAccess create(Context context) {
return new RuntimeDocumentAccess(context);
@@ -76,7 +82,9 @@
}
@Override
- public @Nullable List<DocumentInfo> getDocuments(String authority, List<String> docIds) {
+ public List<DocumentInfo> getDocuments(String authority, List<String> docIds)
+ throws RemoteException {
+
try(final ContentProviderClient client = DocumentsApplication
.acquireUnstableProviderOrThrow(mContext.getContentResolver(), authority)) {
@@ -86,20 +94,14 @@
try (final Cursor cursor = client.query(uri, null, null, null, null)) {
if (!cursor.moveToNext()) {
Log.e(TAG, "Couldn't create DocumentInfo for Uri: " + uri);
- return null;
+ throw new RemoteException("Failed to move cursor.");
}
result.add(DocumentInfo.fromCursor(cursor, authority));
- } catch (Exception e) {
- Log.e(TAG, "Couldn't create DocumentInfo for Uri: " + uri);
- return null;
}
}
return result;
- } catch (RemoteException e) {
- Log.w(TAG, "Couldn't get a content provider client." ,e);
- return null;
}
}
@@ -107,5 +109,19 @@
public DocumentInfo getArchiveDocument(Uri uri) {
return getDocument(ArchivesProvider.buildUriForArchive(uri));
}
+
+ @Override
+ public boolean isDocumentUri(Uri uri) {
+ return DocumentsContract.isDocumentUri(mContext, uri);
+ }
+
+ @Override
+ public Path findPath(Uri docUri) throws RemoteException {
+ final ContentResolver resolver = mContext.getContentResolver();
+ try (final ContentProviderClient client = DocumentsApplication
+ .acquireUnstableProviderOrThrow(resolver, docUri.getAuthority())) {
+ return DocumentsContract.findPath(client, docUri);
+ }
+ }
}
}
diff --git a/src/com/android/documentsui/DocumentsApplication.java b/src/com/android/documentsui/DocumentsApplication.java
index 64eeb14..3f1d48b 100644
--- a/src/com/android/documentsui/DocumentsApplication.java
+++ b/src/com/android/documentsui/DocumentsApplication.java
@@ -28,7 +28,6 @@
import android.os.RemoteException;
import android.text.format.DateUtils;
-import com.android.documentsui.ProviderAccess.RuntimeProviderAccess;
import com.android.documentsui.clipping.ClipStorage;
import com.android.documentsui.clipping.ClipStore;
import com.android.documentsui.clipping.DocumentClipper;
@@ -43,8 +42,6 @@
private ClipStorage mClipStore;
private DocumentClipper mClipper;
- private ProviderAccess mProviderAccess;
-
public static RootsCache getRootsCache(Context context) {
return ((DocumentsApplication) context.getApplicationContext()).mRoots;
}
@@ -54,11 +51,6 @@
return app.mThumbnailCache;
}
- public static ProviderAccess getProviderAccess(Context context) {
- final DocumentsApplication app = (DocumentsApplication) context.getApplicationContext();
- return app.mProviderAccess;
- }
-
public static ContentProviderClient acquireUnstableProviderOrThrow(
ContentResolver resolver, String authority) throws RemoteException {
final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
@@ -95,8 +87,6 @@
getSharedPreferences(ClipStorage.PREF_NAME, 0));
mClipper = new DocumentClipper(this, mClipStore);
- mProviderAccess = new RuntimeProviderAccess(getContentResolver());
-
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
diff --git a/src/com/android/documentsui/DropdownBreadcrumb.java b/src/com/android/documentsui/DropdownBreadcrumb.java
index 98597b8..03b6d09 100644
--- a/src/com/android/documentsui/DropdownBreadcrumb.java
+++ b/src/com/android/documentsui/DropdownBreadcrumb.java
@@ -107,7 +107,7 @@
@Override
public DocumentInfo getItem(int position) {
- return mState.stack.get(mState.stack.size() - position - 1);
+ return mState.stack.get(position);
}
@Override
diff --git a/src/com/android/documentsui/FocusManager.java b/src/com/android/documentsui/FocusManager.java
index 2371b7f..8f5d388 100644
--- a/src/com/android/documentsui/FocusManager.java
+++ b/src/com/android/documentsui/FocusManager.java
@@ -146,6 +146,16 @@
return mScope.lastFocusPosition;
}
+ @Override
+ public @Nullable String getFocusModelId() {
+ if (mScope.lastFocusPosition != RecyclerView.NO_POSITION) {
+ DocumentHolder holder = (DocumentHolder) mScope.view
+ .findViewHolderForAdapterPosition(mScope.lastFocusPosition);
+ return holder.getModelId();
+ }
+ return null;
+ }
+
/**
* Finds the destination position where the focus should land for a given navigation event.
*
diff --git a/src/com/android/documentsui/HorizontalBreadcrumb.java b/src/com/android/documentsui/HorizontalBreadcrumb.java
index 881fbbc..240a0d3 100644
--- a/src/com/android/documentsui/HorizontalBreadcrumb.java
+++ b/src/com/android/documentsui/HorizontalBreadcrumb.java
@@ -195,7 +195,7 @@
}
private DocumentInfo getItem(int position) {
- return mState.stack.get(mState.stack.size() - position - 1);
+ return mState.stack.get(position);
}
@Override
diff --git a/src/com/android/documentsui/LoadDocStackTask.java b/src/com/android/documentsui/LoadDocStackTask.java
index e849a1b..a73945c 100644
--- a/src/com/android/documentsui/LoadDocStackTask.java
+++ b/src/com/android/documentsui/LoadDocStackTask.java
@@ -40,45 +40,37 @@
* given root is not null it calls callback with a {@link DocumentStack} as if the given doc lives
* under the root doc.
*/
-public class LoadDocStackTask extends PairedTask<Activity, Void, DocumentStack> {
+public class LoadDocStackTask extends PairedTask<Activity, Uri, DocumentStack> {
private static final String TAG = "LoadDocStackTask";
private final RootsAccess mRoots;
private final DocumentsAccess mDocs;
- private final Uri mDocUri;
- private final String mAuthority;
- private final ProviderAccess mProviders;
private final LoadDocStackCallback mCallback;
public LoadDocStackTask(
Activity activity,
- Uri docUri,
RootsAccess roots,
DocumentsAccess docs,
- ProviderAccess providers,
LoadDocStackCallback callback) {
super(activity);
mRoots = roots;
mDocs = docs;
- mDocUri = docUri;
- mAuthority = docUri.getAuthority();
- mProviders = providers;
mCallback = callback;
}
@Override
- public @Nullable DocumentStack run(Void... args) {
- if (Shared.ENABLE_OMC_API_FEATURES) {
+ public @Nullable DocumentStack run(Uri... uris) {
+ final Uri docUri = uris[0];
+ if (Shared.ENABLE_OMC_API_FEATURES && mDocs.isDocumentUri(docUri)) {
try {
- final Path path = mProviders.findPath(mDocUri);
+ final Path path = mDocs.findPath(docUri);
if (path != null) {
- return buildStack(path);
+ return buildStack(docUri.getAuthority(), path);
} else {
Log.i(TAG, "Remote provider doesn't support findPath.");
}
} catch (Exception e) {
- Log.e(TAG, "Failed to build document stack for uri: " + mDocUri, e);
- // Fallback to old behavior.
+ Log.e(TAG, "Failed to build document stack for uri: " + docUri, e);
}
}
@@ -90,21 +82,20 @@
mCallback.onDocumentStackLoaded(stack);
}
- private @Nullable DocumentStack buildStack(Path path) {
+ private DocumentStack buildStack(String authority, Path path) throws Exception {
final String rootId = path.getRootId();
if (rootId == null) {
- Log.e(TAG, "Provider doesn't provide root id.");
- return null;
+ throw new IllegalStateException("Provider doesn't provider root id.");
}
- RootInfo root = mRoots.getRootOneshot(mAuthority, path.getRootId());
- List<DocumentInfo> docs = mDocs.getDocuments(mAuthority, path.getPath());
-
- if (root == null || docs == null) {
- Log.e(TAG, "Either root: " + root + " or docs: " + docs + " failed to load.");
- return null;
+ RootInfo root = mRoots.getRootOneshot(authority, path.getRootId());
+ if (root == null) {
+ throw new IllegalStateException("Failed to load root for authority: " + authority +
+ " and root ID: " + path.getRootId() + ".");
}
+ List<DocumentInfo> docs = mDocs.getDocuments(authority, path.getPath());
+
return new DocumentStack(root, docs);
}
diff --git a/src/com/android/documentsui/NavigationViewManager.java b/src/com/android/documentsui/NavigationViewManager.java
index 4f97111..65f9000 100644
--- a/src/com/android/documentsui/NavigationViewManager.java
+++ b/src/com/android/documentsui/NavigationViewManager.java
@@ -75,7 +75,7 @@
boolean changed = false;
while (mState.stack.size() > position + 1) {
changed = true;
- mState.popDocument();
+ mState.stack.pop();
}
if (changed) {
mEnv.refreshCurrentRootAndDirectory(AnimationView.ANIM_LEAVE);
diff --git a/src/com/android/documentsui/ProviderAccess.java b/src/com/android/documentsui/ProviderAccess.java
deleted file mode 100644
index 67a0e3f..0000000
--- a/src/com/android/documentsui/ProviderAccess.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.documentsui;
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Path;
-
-/**
- * Provides synchronous {@link android.provider.DocumentsProvider} access.
- */
-public interface ProviderAccess {
- Path findPath(Uri docUri) throws RemoteException;
-
- class RuntimeProviderAccess implements ProviderAccess {
-
- private final ContentResolver mResolver;
-
- RuntimeProviderAccess(ContentResolver resolver) {
- mResolver = resolver;
- }
-
- @Override
- public Path findPath(Uri docUri) throws RemoteException {
- try (final ContentProviderClient client = DocumentsApplication
- .acquireUnstableProviderOrThrow(mResolver, docUri.getAuthority())) {
- return DocumentsContract.findPath(client, docUri);
- }
- }
- }
-}
diff --git a/src/com/android/documentsui/RootsMonitor.java b/src/com/android/documentsui/RootsMonitor.java
index da48794..a0d4d0b 100644
--- a/src/com/android/documentsui/RootsMonitor.java
+++ b/src/com/android/documentsui/RootsMonitor.java
@@ -135,7 +135,7 @@
}
// Clear entire backstack and start in new root.
- mState.onRootChanged(defaultRoot);
+ mState.stack.changeRoot(defaultRoot);
mSearchMgr.update(defaultRoot);
if (defaultRoot.isRecents()) {
diff --git a/src/com/android/documentsui/base/DocumentStack.java b/src/com/android/documentsui/base/DocumentStack.java
index c3a13e0..9e0c580 100644
--- a/src/com/android/documentsui/base/DocumentStack.java
+++ b/src/com/android/documentsui/base/DocumentStack.java
@@ -16,10 +16,13 @@
package com.android.documentsui.base;
+import static com.android.documentsui.base.Shared.DEBUG;
+
import android.content.ContentResolver;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.DocumentsProvider;
+import android.util.Log;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -34,51 +37,131 @@
* Representation of a stack of {@link DocumentInfo}, usually the result of a
* user-driven traversal.
*/
-public class DocumentStack extends LinkedList<DocumentInfo> implements Durable, Parcelable {
+public class DocumentStack implements Durable, Parcelable {
+
+ private static final String TAG = "DocumentStack";
+
private static final int VERSION_INIT = 1;
private static final int VERSION_ADD_ROOT = 2;
- public RootInfo root;
+ private LinkedList<DocumentInfo> mList;
+ private RootInfo mRoot;
- public DocumentStack() {};
+ private boolean mInitialRootChanged;
+ private boolean mInitialDocChanged;
+ private boolean mStackTouched;
+
+ public DocumentStack() {
+ mList = new LinkedList<>();
+ }
/**
* Creates an instance, and pushes all docs to it in the same order as they're passed as
* parameters, i.e. the last document will be at the top of the stack.
*/
public DocumentStack(RootInfo root, DocumentInfo... docs) {
- for (DocumentInfo doc : docs) {
- push(doc);
+ mList = new LinkedList<>();
+ for (int i = 0; i < docs.length; ++i) {
+ mList.add(docs[i]);
}
- this.root = root;
- }
-
- public DocumentStack(RootInfo root, List<DocumentInfo> docs) {
- for (DocumentInfo doc : docs) {
- push(doc);
- }
-
- this.root = root;
+ mRoot = root;
}
/**
- * Makes a new copy, and pushes all docs to the new copy in the same order as they're passed
- * as parameters, i.e. the last document will be at the top of the stack.
+ * Same as {@link #DocumentStack(DocumentStack, DocumentInfo...)} except it takes a {@link List}
+ * instead of an array.
+ */
+ public DocumentStack(RootInfo root, List<DocumentInfo> docs) {
+ mList = new LinkedList<>(docs);
+ mRoot = root;
+ }
+
+ /**
+ * Makes a new shallow copy, and pushes all docs to the new copy in the same order as they're
+ * passed as parameters, i.e. the last document will be at the top of the stack.
*/
public DocumentStack(DocumentStack src, DocumentInfo... docs) {
- super(src);
+ mList = src.mList;
for (DocumentInfo doc : docs) {
- push(doc);
+ mList.addLast(doc);
}
- root = src.root;
+ mRoot = src.mRoot;
+ }
+
+ public RootInfo getRoot() {
+ return mRoot;
+ }
+
+ public boolean isEmpty() {
+ return mList.isEmpty();
+ }
+
+ public int size() {
+ return mList.size();
+ }
+
+ public DocumentInfo peek() {
+ return mList.peekLast();
+ }
+
+ /**
+ * Returns {@link DocumentInfo} at index counted from the bottom of this stack.
+ */
+ public DocumentInfo get(int index) {
+ return mList.get(index);
+ }
+
+ public void push(DocumentInfo info) {
+ if (DEBUG) Log.d(TAG, "Adding doc to stack: " + info);
+ if (!mInitialDocChanged && !isEmpty() && !info.equals(peek())) {
+ mInitialDocChanged = true;
+ }
+ mList.addLast(info);
+ mStackTouched = true;
+ }
+
+ public DocumentInfo pop() {
+ if (DEBUG) Log.d(TAG, "Popping doc off stack.");
+ final DocumentInfo result = mList.removeLast();
+ mStackTouched = true;
+
+ return result;
+ }
+
+ public void popToRootDocument() {
+ if (DEBUG) Log.d(TAG, "Popping docs to root folder.");
+ while (mList.size() > 1) {
+ mList.removeLast();
+ }
+ mStackTouched = true;
+ }
+
+ public void changeRoot(RootInfo root) {
+ if (DEBUG) Log.d(TAG, "Root changed to: " + root);
+ if (!mInitialRootChanged && mRoot != null && !root.equals(mRoot)) {
+ mInitialRootChanged = true;
+ }
+ reset();
+ mRoot = root;
+ }
+
+ /** This will return true even when the initial location is set.
+ * To get a read on if the user has changed something, use {@link #hasInitialLocationChanged()}.
+ */
+ public boolean hasLocationChanged() {
+ return mStackTouched;
+ }
+
+ public boolean hasInitialLocationChanged() {
+ return mInitialRootChanged || mInitialDocChanged;
}
public String getTitle() {
- if (size() == 1 && root != null) {
- return root.title;
- } else if (size() > 1) {
+ if (mList.size() == 1 && mRoot != null) {
+ return mRoot.title;
+ } else if (mList.size() > 1) {
return peek().displayName;
} else {
return null;
@@ -86,17 +169,17 @@
}
public boolean isRecents() {
- return size() == 0;
+ return isEmpty();
}
public void updateRoot(Collection<RootInfo> matchingRoots) throws FileNotFoundException {
for (RootInfo root : matchingRoots) {
- if (root.equals(this.root)) {
- this.root = root;
+ if (root.equals(this.mRoot)) {
+ this.mRoot = root;
return;
}
}
- throw new FileNotFoundException("Failed to find matching root for " + root);
+ throw new FileNotFoundException("Failed to find matching mRoot for " + mRoot);
}
/**
@@ -104,34 +187,38 @@
* {@link DocumentsProvider}.
*/
public void updateDocuments(ContentResolver resolver) throws FileNotFoundException {
- for (DocumentInfo info : this) {
+ for (DocumentInfo info : mList) {
info.updateSelf(resolver);
}
}
/**
- * Build key that uniquely identifies this stack. It omits most of the raw
- * details included in {@link #write(DataOutputStream)}, since they change
- * too regularly to be used as a key.
+ * Resets this stack to the given stack. It takes the reference of {@link #mList} and
+ * {@link #mRoot} instead of making a copy.
*/
- public String buildKey() {
- final StringBuilder builder = new StringBuilder();
- if (root != null) {
- builder.append(root.authority).append('#');
- builder.append(root.rootId).append('#');
- } else {
- builder.append("[null]").append('#');
- }
- for (DocumentInfo doc : this) {
- builder.append(doc.documentId).append('#');
- }
- return builder.toString();
+ public void reset(DocumentStack stack) {
+ if (DEBUG) Log.d(TAG, "Resetting the whole darn stack to: " + stack);
+
+ mList = stack.mList;
+ mRoot = stack.mRoot;
+ mStackTouched = true;
+ }
+
+ @Override
+ public String toString() {
+ return "DocumentStack{"
+ + "root=" + mRoot
+ + ", docStack=" + mList
+ + ", stackTouched=" + mStackTouched
+ + ", initialDocChanged=" + mInitialDocChanged
+ + ", initialRootChanged=" + mInitialRootChanged
+ + "}";
}
@Override
public void reset() {
- clear();
- root = null;
+ mList.clear();
+ mRoot = null;
}
@Override
@@ -142,15 +229,18 @@
throw new ProtocolException("Ignored upgrade");
case VERSION_ADD_ROOT:
if (in.readBoolean()) {
- root = new RootInfo();
- root.read(in);
+ mRoot = new RootInfo();
+ mRoot.read(in);
}
final int size = in.readInt();
for (int i = 0; i < size; i++) {
final DocumentInfo doc = new DocumentInfo();
doc.read(in);
- add(doc);
+ mList.add(doc);
}
+ mStackTouched = in.readInt() != 0;
+ mInitialRootChanged = in.readInt() != 0;
+ mInitialDocChanged = in.readInt() != 0;
break;
default:
throw new ProtocolException("Unknown version " + version);
@@ -160,18 +250,21 @@
@Override
public void write(DataOutputStream out) throws IOException {
out.writeInt(VERSION_ADD_ROOT);
- if (root != null) {
+ if (mRoot != null) {
out.writeBoolean(true);
- root.write(out);
+ mRoot.write(out);
} else {
out.writeBoolean(false);
}
- final int size = size();
+ final int size = mList.size();
out.writeInt(size);
for (int i = 0; i < size; i++) {
- final DocumentInfo doc = get(i);
+ final DocumentInfo doc = mList.get(i);
doc.write(out);
}
+ out.writeInt(mStackTouched ? 1 : 0);
+ out.writeInt(mInitialRootChanged ? 1 : 0);
+ out.writeInt(mInitialDocChanged ? 1 : 0);
}
@Override
diff --git a/src/com/android/documentsui/base/State.java b/src/com/android/documentsui/base/State.java
index 8059625..32d3cc2 100644
--- a/src/com/android/documentsui/base/State.java
+++ b/src/com/android/documentsui/base/State.java
@@ -16,13 +16,10 @@
package com.android.documentsui.base;
-import static com.android.documentsui.base.Shared.DEBUG;
-
import android.annotation.IntDef;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
import android.util.SparseArray;
import com.android.documentsui.services.FileOperationService;
@@ -102,10 +99,7 @@
public @OpType int copyOperationSubType = FileOperationService.OPERATION_UNKNOWN;
/** Current user navigation stack; empty implies recents. */
- public DocumentStack stack = new DocumentStack();
- private boolean mStackTouched;
- private boolean mInitialRootChanged;
- private boolean mInitialDocChanged;
+ public final DocumentStack stack = new DocumentStack();
/** Instance configs for every shown directory */
public HashMap<String, SparseArray<Parcelable>> dirConfigs = new HashMap<>();
@@ -122,55 +116,6 @@
}
}
- public void onRootChanged(RootInfo root) {
- if (DEBUG) Log.d(TAG, "Root changed to: " + root);
- if (!mInitialRootChanged && stack.root != null && !root.equals(stack.root)) {
- mInitialRootChanged = true;
- }
- stack.root = root;
- stack.clear();
- mStackTouched = true;
- }
-
- public void pushDocument(DocumentInfo info) {
- if (DEBUG) Log.d(TAG, "Adding doc to stack: " + info);
- if (!mInitialDocChanged && stack.size() > 0 && !info.equals(stack.peek())) {
- mInitialDocChanged = true;
- }
- stack.push(info);
- mStackTouched = true;
- }
-
- public void popDocument() {
- if (DEBUG) Log.d(TAG, "Popping doc off stack.");
- stack.pop();
- mStackTouched = true;
- }
-
- public void popDocumentsToRoot() {
- if (DEBUG) Log.d(TAG, "Popping docs to root folder.");
- while (stack.size() > 1) {
- stack.pop();
- }
- mStackTouched = true;
- }
-
- public void setStack(DocumentStack stack) {
- if (DEBUG) Log.d(TAG, "Setting the whole darn stack to: " + stack);
- this.stack = stack;
- mStackTouched = true;
- }
-
- // This will return true even when the initial location is set.
- // To get a read on if the user has changed something, use #hasInitialLocationChanged.
- public boolean hasLocationChanged() {
- return mStackTouched;
- }
-
- public boolean hasInitialLocationChanged() {
- return mInitialRootChanged || mInitialDocChanged;
- }
-
@Override
public int describeContents() {
return 0;
@@ -190,9 +135,6 @@
out.writeMap(dirConfigs);
out.writeList(excludedAuthorities);
out.writeInt(openableOnly ? 1 : 0);
- out.writeInt(mStackTouched ? 1 : 0);
- out.writeInt(mInitialRootChanged ? 1 : 0);
- out.writeInt(mInitialDocChanged ? 1 : 0);
out.writeParcelable(sortModel, 0);
}
@@ -217,9 +159,6 @@
in.readMap(state.dirConfigs, loader);
in.readList(state.excludedAuthorities, loader);
state.openableOnly = in.readInt() != 0;
- state.mStackTouched = in.readInt() != 0;
- state.mInitialRootChanged = in.readInt() != 0;
- state.mInitialDocChanged = in.readInt() != 0;
state.sortModel = in.readParcelable(getClass().getClassLoader());
return state;
}
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index e385af2..cc6a548 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -633,11 +633,11 @@
return true;
case R.id.menu_cut_to_clipboard:
- cutSelectedToClipboard();
+ mActions.cutToClipboard();
return true;
case R.id.menu_copy_to_clipboard:
- copySelectedToClipboard();
+ mActions.copyToClipboard();
return true;
case R.id.menu_paste_from_clipboard:
@@ -838,34 +838,6 @@
mRecView.requestFocus();
}
- public void copySelectedToClipboard() {
- Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_CLIPBOARD);
-
- Selection selection = mSelectionMgr.getSelection(new Selection());
- if (selection.isEmpty()) {
- return;
- }
- mSelectionMgr.clearSelection();
-
- mClipper.clipDocumentsForCopy(mModel::getItemUri, selection);
-
- Snackbars.showDocumentsClipped(getActivity(), selection.size());
- }
-
- public void cutSelectedToClipboard() {
- Metrics.logUserAction(getContext(), Metrics.USER_ACTION_CUT_CLIPBOARD);
-
- Selection selection = mSelectionMgr.getSelection(new Selection());
- if (selection.isEmpty()) {
- return;
- }
- mSelectionMgr.clearSelection();
-
- mClipper.clipDocumentsForCut(mModel::getItemUri, selection, mState.stack.peek());
-
- Snackbars.showDocumentsClipped(getActivity(), selection.size());
- }
-
public void pasteFromClipboard() {
Metrics.logUserAction(getContext(), Metrics.USER_ACTION_PASTE_CLIPBOARD);
@@ -920,8 +892,12 @@
/**
* Attempts to restore focus on the directory listing.
*/
- public void requestFocus() {
+ public boolean requestFocus() {
+ if (mSelectionMgr.hasSelection()) {
+ return false;
+ }
mFocusManager.restoreLastFocus();
+ return true;
}
private void setupDragAndDropOnDocumentView(View view, Cursor cursor) {
@@ -1206,7 +1182,7 @@
if (mModel.isEmpty()) {
if (mLocalState.mSearchMode) {
- showNoResults(mState.stack.root);
+ showNoResults(mState.stack.getRoot());
} else {
showEmptyDirectory();
}
diff --git a/src/com/android/documentsui/dirlist/FocusHandler.java b/src/com/android/documentsui/dirlist/FocusHandler.java
index 0f43ac0..1cbb8a9 100644
--- a/src/com/android/documentsui/dirlist/FocusHandler.java
+++ b/src/com/android/documentsui/dirlist/FocusHandler.java
@@ -16,6 +16,7 @@
package com.android.documentsui.dirlist;
+import android.annotation.Nullable;
import android.view.KeyEvent;
import android.view.View;
@@ -52,4 +53,8 @@
*/
int getFocusPosition();
+ /**
+ * @return The modelId of the last focused item. If no item is focused, this should return null.
+ */
+ @Nullable String getFocusModelId();
}
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index c2da8f5..09f5ce1 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -40,7 +40,6 @@
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.MimeTypes;
-import com.android.documentsui.ProviderAccess;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
@@ -49,6 +48,7 @@
import com.android.documentsui.clipping.UrisSupplier;
import com.android.documentsui.dirlist.AnimationView;
import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.dirlist.Model.Update;
import com.android.documentsui.files.ActionHandler.Addons;
@@ -88,7 +88,7 @@
State state,
RootsAccess roots,
DocumentsAccess docs,
- ProviderAccess providers,
+ FocusHandler focusHandler,
SelectionManager selectionMgr,
SearchViewManager searchMgr,
Lookup<String, Executor> executors,
@@ -98,7 +98,7 @@
DocumentClipper clipper,
ClipStore clipStore) {
- super(activity, state, roots, docs, providers, selectionMgr, searchMgr, executors);
+ super(activity, state, roots, docs, focusHandler, selectionMgr, searchMgr, executors);
mActionModeAddons = actionModeAddons;
mDialogs = dialogs;
@@ -192,14 +192,57 @@
return previewDocument(doc);
}
+ private Selection getSelectedOrFocused() {
+ final Selection selection = this.getStableSelection();
+ if (selection.isEmpty()) {
+ String focusModelId = mFocusHandler.getFocusModelId();
+ if (focusModelId != null) {
+ selection.add(focusModelId);
+ }
+ }
+
+ return selection;
+ }
+
+ @Override
+ public void cutToClipboard() {
+ Metrics.logUserAction(mActivity, Metrics.USER_ACTION_CUT_CLIPBOARD);
+ Selection selection = getSelectedOrFocused();
+
+ if (selection.isEmpty()) {
+ return;
+ }
+ mSelectionMgr.clearSelection();
+
+ mClipper.clipDocumentsForCut(mScope.model::getItemUri, selection, mState.stack.peek());
+
+ mDialogs.showDocumentsClipped(selection.size());
+ }
+
+ @Override
+ public void copyToClipboard() {
+ Metrics.logUserAction(mActivity, Metrics.USER_ACTION_COPY_CLIPBOARD);
+ Selection selection = getSelectedOrFocused();
+
+ if (selection.isEmpty()) {
+ return;
+ }
+ mSelectionMgr.clearSelection();
+
+ mClipper.clipDocumentsForCopy(mScope.model::getItemUri, selection);
+
+ mDialogs.showDocumentsClipped(selection.size());
+ }
+
+
@Override
public void deleteSelectedDocuments() {
- assert(mSelectionMgr.hasSelection());
-
Metrics.logUserAction(mActivity, Metrics.USER_ACTION_DELETE);
+ Selection selection = getSelectedOrFocused();
- Selection selection = getStableSelection();
- assert(!selection.isEmpty());
+ if (selection.isEmpty()) {
+ return;
+ }
final DocumentInfo srcParent = mState.stack.peek();
assert(srcParent != null);
@@ -321,12 +364,12 @@
// Any other URI is *sorta* unexpected...except when browsing an archive
// in downloads.
private boolean launchToStackLocation(DocumentStack stack) {
- if (stack == null || stack.root == null) {
+ if (stack == null || stack.getRoot() == null) {
return false;
}
if (mState.stack.isEmpty()) {
- mActivity.onRootPicked(mState.stack.root);
+ mActivity.onRootPicked(mState.stack.getRoot());
} else {
mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
}
@@ -512,7 +555,7 @@
private void onModelLoaded(Model.Update update) {
// When launched into empty root, open drawer.
if (mScope.model.isEmpty()
- && !mState.hasInitialLocationChanged()
+ && !mState.stack.hasInitialLocationChanged()
&& !mScope.searchMode
&& !mScope.modelLoadObserved) {
// Opens the drawer *if* an openable drawer is present
diff --git a/src/com/android/documentsui/files/ActivityInputHandler.java b/src/com/android/documentsui/files/ActivityInputHandler.java
index a7b0e2c..35653d2 100644
--- a/src/com/android/documentsui/files/ActivityInputHandler.java
+++ b/src/com/android/documentsui/files/ActivityInputHandler.java
@@ -18,31 +18,22 @@
import android.view.KeyEvent;
-import com.android.documentsui.selection.SelectionManager;
-import com.android.documentsui.ActionHandler;
-
/**
* Used by {@link FilesActivity} to manage global keyboard shortcuts tied to file actions
*/
final class ActivityInputHandler {
- private final SelectionManager mSelectionMgr;
- private final ActionHandler mActions;
+ private final Runnable mDeleteHandler;
- ActivityInputHandler(SelectionManager selectionMgr, ActionHandler actionHandler) {
- mSelectionMgr = selectionMgr;
- mActions = actionHandler;
+ ActivityInputHandler(Runnable deleteHandler) {
+ mDeleteHandler = deleteHandler;
}
boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_DEL && event.isAltPressed())
|| keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
- if (mSelectionMgr.hasSelection()) {
- mActions.deleteSelectedDocuments();
- return true;
- } else {
- return false;
- }
+ mDeleteHandler.run();
+ return true;
}
return false;
}
diff --git a/src/com/android/documentsui/files/Config.java b/src/com/android/documentsui/files/Config.java
index 69da89e..30d468a 100644
--- a/src/com/android/documentsui/files/Config.java
+++ b/src/com/android/documentsui/files/Config.java
@@ -16,8 +16,9 @@
package com.android.documentsui.files;
-import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.ActivityConfig;
+import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.RootInfo;
/**
* Provides support for Files activity specific specializations.
@@ -30,8 +31,9 @@
// And while we don't allow folders in Downloads, we do allow Zip files in
// downloads that themselves can be opened and viewed like directories.
// This method helps us understand when to kick in on those special behaviors.
- return stack.root != null
- && stack.root.isDownloads()
+ final RootInfo root = stack.getRoot();
+ return root != null
+ && root.isDownloads()
&& stack.size() == 1;
}
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 0de44ea..7a2284f 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -56,6 +56,7 @@
import com.android.documentsui.dirlist.DirectoryFragment;
import com.android.documentsui.dirlist.DocumentsAdapter;
import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.selection.Selection;
import com.android.documentsui.selection.SelectionManager;
import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
import com.android.documentsui.services.FileOperationService;
@@ -124,7 +125,7 @@
mState,
mRoots,
mDocs,
- DocumentsApplication.getProviderAccess(this),
+ mFocusManager,
mSelectionMgr,
mSearchManager,
ProviderExecutor::forAuthority,
@@ -134,7 +135,7 @@
mClipper,
DocumentsApplication.getClipStore(this));
- mActivityInputHandler = new ActivityInputHandler(mSelectionMgr, mActions);
+ mActivityInputHandler = new ActivityInputHandler(mActions::deleteSelectedDocuments);
RootsFragment.show(getFragmentManager(), null);
@@ -176,7 +177,7 @@
final DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
if (stack != null) {
- state.stack = stack;
+ state.stack.reset(stack);
}
}
@@ -325,16 +326,10 @@
}
return true;
case KeyEvent.KEYCODE_X:
- dir = getDirectoryFragment();
- if (dir != null) {
- dir.cutSelectedToClipboard();
- }
+ mActions.cutToClipboard();
return true;
case KeyEvent.KEYCODE_C:
- dir = getDirectoryFragment();
- if (dir != null) {
- dir.copySelectedToClipboard();
- }
+ mActions.copyToClipboard();
return true;
case KeyEvent.KEYCODE_V:
dir = getDirectoryFragment();
diff --git a/src/com/android/documentsui/files/LauncherActivity.java b/src/com/android/documentsui/files/LauncherActivity.java
index bb25c35..e76728f 100644
--- a/src/com/android/documentsui/files/LauncherActivity.java
+++ b/src/com/android/documentsui/files/LauncherActivity.java
@@ -59,16 +59,27 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ launch();
+
+ finish();
+ }
+
+ private void launch() {
ActivityManager activities = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
Intent intent = findTask(activities);
if (intent != null) {
- restoreTask(intent);
- } else {
- startTask();
+ if (restoreTask(intent)) {
+ return;
+ } else {
+ // We failed to restore the task. It may happen when system was just updated and we
+ // moved the location of the targeted activity. Chances is that the rest of tasks
+ // can't be restored either, so clean those tasks and start a new one.
+ clearTask(activities);
+ }
}
- finish();
+ startTask();
}
private @Nullable Intent findTask(ActivityManager activities) {
@@ -92,10 +103,26 @@
startActivity(intent);
}
- private void restoreTask(Intent intent) {
+ private boolean restoreTask(Intent intent) {
if (DEBUG) Log.d(TAG, "Restoring existing task > " + intent.getData());
- // TODO: This doesn't appear to restore a task once it has stopped running.
- startActivity(intent);
+ try {
+ // TODO: This doesn't appear to restore a task once it has stopped running.
+ startActivity(intent);
+
+ return true;
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to restore task > " + intent.getData() +
+ ". Clear all existing tasks and start a new one.", e);
+ }
+
+ return false;
+ }
+
+ private void clearTask(ActivityManager activities) {
+ List<AppTask> tasks = activities.getAppTasks();
+ for (AppTask task : tasks) {
+ task.finishAndRemoveTask();
+ }
}
public static final Intent createLaunchIntent(Activity activity) {
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index d37e34c..1999381 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -36,11 +36,11 @@
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.MimeTypes;
-import com.android.documentsui.ProviderAccess;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.dirlist.Model.Update;
import com.android.documentsui.picker.ActionHandler.Addons;
@@ -67,13 +67,13 @@
State state,
RootsAccess roots,
DocumentsAccess docs,
- ProviderAccess providers,
+ FocusHandler focusHandler,
SelectionManager selectionMgr,
SearchViewManager searchMgr,
Lookup<String, Executor> executors,
ActivityConfig activityConfig) {
- super(activity, state, roots, docs, providers, selectionMgr, searchMgr, executors);
+ super(activity, state, roots, docs, focusHandler, selectionMgr, searchMgr, executors);
mConfig = activityConfig;
mScope = new ContentScope(this::onModelLoaded);
@@ -81,42 +81,65 @@
@Override
public void initLocation(Intent intent) {
- if (mState.restored) {
- if (DEBUG) Log.d(TAG, "Stack already resolved");
- } else {
- // We set the activity title in AsyncTask.onPostExecute().
- // To prevent talkback from reading aloud the default title, we clear it here.
- mActivity.setTitle("");
+ assert(intent != null);
- // As a matter of policy we don't load the last used stack for the copy
- // destination picker (user is already in Files app).
- // Concensus was that the experice was too confusing.
- // In all other cases, where the user is visiting us from another app
- // we restore the stack as last used from that app.
- if (Shared.ACTION_PICK_COPY_DESTINATION.equals(intent.getAction())) {
- if (DEBUG) Log.d(TAG, "Launching directly into Home directory.");
- loadHomeDir();
- } else if (intent.getData() != null) {
- Uri uri = intent.getData();
- loadDocument(
- uri,
- (@Nullable DocumentStack stack) -> onStackLoaded(uri, stack));
- } else {
- loadLastAccessedStack();
- }
+ if (mState.restored) {
+ if (DEBUG) Log.d(TAG, "Stack already resolved for uri: " + intent.getData());
+ return;
}
+
+ // We set the activity title in AsyncTask.onPostExecute().
+ // To prevent talkback from reading aloud the default title, we clear it here.
+ mActivity.setTitle("");
+
+ if (launchHomeForCopyDestination(intent)) {
+ if (DEBUG) Log.d(TAG, "Launching directly into Home directory for copy destination.");
+ return;
+ }
+
+ if (launchToDocument(intent)) {
+ if (DEBUG) Log.d(TAG, "Launched to a document.");
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Load last accessed stack.");
+ loadLastAccessedStack();
}
- private void onStackLoaded(Uri uri, @Nullable DocumentStack stack) {
+ private boolean launchHomeForCopyDestination(Intent intent) {
+ // As a matter of policy we don't load the last used stack for the copy
+ // destination picker (user is already in Files app).
+ // Consensus was that the experice was too confusing.
+ // In all other cases, where the user is visiting us from another app
+ // we restore the stack as last used from that app.
+ if (Shared.ACTION_PICK_COPY_DESTINATION.equals(intent.getAction())) {
+ loadHomeDir();
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean launchToDocument(Intent intent) {
+ final Uri uri = intent.getData();
+ if (uri != null) {
+ loadDocument(uri, this::onStackLoaded);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void onStackLoaded(@Nullable DocumentStack stack) {
if (stack != null) {
if (!stack.peek().isContainer()) {
// Requested document is not a container. Pop it so that we can launch into its
// parent.
stack.pop();
}
- mState.setStack(stack);
+ mState.stack.reset(stack);
} else {
- Log.w(TAG, "Failed to launch into the given uri: " + uri);
+ Log.w(TAG, "Failed to launch into the given uri. Load last accessed stack.");
loadLastAccessedStack();
}
}
@@ -194,7 +217,7 @@
showDrawer = true;
}
- if (showDrawer && !mState.hasInitialLocationChanged() && !mScope.searchMode
+ if (showDrawer && !mState.stack.hasInitialLocationChanged() && !mScope.searchMode
&& !mScope.modelLoadObserved) {
// This noops on layouts without drawer, so no need to guard.
mActivity.setRootsDrawerOpen(true);
diff --git a/src/com/android/documentsui/picker/LastAccessedProvider.java b/src/com/android/documentsui/picker/LastAccessedProvider.java
index bb1a129..b77ce99 100644
--- a/src/com/android/documentsui/picker/LastAccessedProvider.java
+++ b/src/com/android/documentsui/picker/LastAccessedProvider.java
@@ -37,10 +37,10 @@
import com.android.documentsui.base.DurableUtils;
import com.android.internal.util.Predicate;
-import com.google.android.collect.Sets;
-
import libcore.io.IoUtils;
+import com.google.android.collect.Sets;
+
import java.io.IOException;
import java.util.Set;
@@ -244,7 +244,7 @@
cursor.getColumnIndex(Columns.STACK));
DurableUtils.readFromArray(rawStack, stack);
- if (stack.root != null && predicate.apply(stack.root.authority)) {
+ if (stack.getRoot() != null && predicate.apply(stack.getRoot().authority)) {
final String packageName = getCursorString(
cursor, Columns.PACKAGE_NAME);
db.delete(TABLE_LAST_ACCESSED, Columns.PACKAGE_NAME + "=?",
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 3e71402..eee92e5 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -110,7 +110,7 @@
mState,
mRoots,
mDocs,
- DocumentsApplication.getProviderAccess(this),
+ mFocusManager,
mSelectionMgr,
mSearchManager,
ProviderExecutor::forAuthority,
diff --git a/src/com/android/documentsui/services/CopyJob.java b/src/com/android/documentsui/services/CopyJob.java
index 1122f6b..30bbbf1 100644
--- a/src/com/android/documentsui/services/CopyJob.java
+++ b/src/com/android/documentsui/services/CopyJob.java
@@ -276,7 +276,7 @@
int docProcessed = 0;
for (Uri uri : uris) {
DocumentInfo doc = DocumentInfo.fromUri(resolver, uri);
- if (canCopy(doc, stack.root)) {
+ if (canCopy(doc, stack.getRoot())) {
mSrcs.add(doc);
} else {
onFileFailed(doc);
@@ -322,9 +322,10 @@
if (batchSize >= 0) {
RootsCache cache = DocumentsApplication.getRootsCache(appContext);
+ RootInfo root = stack.getRoot();
// Query root info here instead of using stack.root because the number there may be
// stale.
- RootInfo root = cache.getRootOneshot(stack.root.authority, stack.root.rootId, true);
+ root = cache.getRootOneshot(root.authority, root.rootId, true);
if (root.availableBytes >= 0) {
result = (batchSize <= root.availableBytes);
} else {
diff --git a/src/com/android/documentsui/services/MoveJob.java b/src/com/android/documentsui/services/MoveJob.java
index 756112e..7e66cb0 100644
--- a/src/com/android/documentsui/services/MoveJob.java
+++ b/src/com/android/documentsui/services/MoveJob.java
@@ -108,7 +108,7 @@
boolean checkSpace() {
long size = 0;
for (DocumentInfo src : mSrcs) {
- if (!src.authority.equals(stack.root.authority)) {
+ if (!src.authority.equals(stack.getRoot().authority)) {
if (src.isDirectory()) {
try {
size += calculateFileSizesRecursively(getClient(src), src.derivedUri);
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index 8890fb3..4e9287e 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -357,8 +357,8 @@
/**
* Attempts to shift focus back to the navigation drawer.
*/
- public void requestFocus() {
- mList.requestFocus();
+ public boolean requestFocus() {
+ return mList.requestFocus();
}
private BaseActivity<?> getBaseActivity() {
diff --git a/src/com/android/documentsui/ui/DialogController.java b/src/com/android/documentsui/ui/DialogController.java
index 6c648a2..39bebfe 100644
--- a/src/com/android/documentsui/ui/DialogController.java
+++ b/src/com/android/documentsui/ui/DialogController.java
@@ -50,11 +50,17 @@
public void showNoApplicationFound() {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void showDocumentsClipped(int size) {
+ throw new UnsupportedOperationException();
+ }
};
void confirmDelete(List<DocumentInfo> docs, ConfirmationCallback callback);
void showFileOperationFailures(int status, int opType, int docCount);
void showNoApplicationFound();
+ void showDocumentsClipped(int size);
// Should be private, but Java doesn't like me treating an interface like a mini-package.
public static final class RuntimeDialogController implements DialogController {
@@ -137,6 +143,11 @@
Snackbars.makeSnackbar(
mActivity, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show();
}
+
+ @Override
+ public void showDocumentsClipped(int size) {
+ Snackbars.showDocumentsClipped(mActivity, size);
+ }
}
static DialogController create(Activity activity, MessageBuilder messages) {
diff --git a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java
index 45e0655..76db4e1 100644
--- a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java
+++ b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java
@@ -45,6 +45,11 @@
}
@Override
+ public String getFocusModelId() {
+ return null;
+ }
+
+ @Override
public void focusDocument(String modelId) {
}
diff --git a/tests/common/com/android/documentsui/testing/TestActionHandler.java b/tests/common/com/android/documentsui/testing/TestActionHandler.java
index 5ad0563..bd8adac 100644
--- a/tests/common/com/android/documentsui/testing/TestActionHandler.java
+++ b/tests/common/com/android/documentsui/testing/TestActionHandler.java
@@ -40,7 +40,7 @@
env.state,
env.roots,
env.docs,
- env.providers,
+ env.focusHandler,
env.selectionMgr,
env.searchViewManager,
(String authority) -> null);
diff --git a/tests/common/com/android/documentsui/testing/TestConfirmationCallback.java b/tests/common/com/android/documentsui/testing/TestConfirmationCallback.java
index 6ffdf70..f94f33f 100644
--- a/tests/common/com/android/documentsui/testing/TestConfirmationCallback.java
+++ b/tests/common/com/android/documentsui/testing/TestConfirmationCallback.java
@@ -48,4 +48,8 @@
public void assertCalled() {
Assert.assertTrue(mCalled);
}
+
+ public void assertNeverCalled() {
+ Assert.assertFalse(mCalled);
+ }
}
diff --git a/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java b/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java
index 7ded624..fb821ee 100644
--- a/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java
+++ b/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java
@@ -16,6 +16,8 @@
package com.android.documentsui.testing;
import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.DocumentsContract.Path;
import com.android.documentsui.DocumentsAccess;
import com.android.documentsui.base.DocumentInfo;
@@ -31,6 +33,9 @@
public @Nullable DocumentInfo nextDocument;
public @Nullable List<DocumentInfo> nextDocuments;
+ public boolean nextIsDocumentsUri;
+ public @Nullable Path nextPath;
+
@Override
public DocumentInfo getRootDocument(RootInfo root) {
return nextRootDocument;
@@ -50,4 +55,14 @@
public DocumentInfo getArchiveDocument(Uri uri) {
return nextDocument;
}
+
+ @Override
+ public boolean isDocumentUri(Uri uri) {
+ return nextIsDocumentsUri;
+ }
+
+ @Override
+ public Path findPath(Uri docUri) throws RemoteException {
+ return nextPath;
+ }
}
diff --git a/tests/common/com/android/documentsui/testing/TestEnv.java b/tests/common/com/android/documentsui/testing/TestEnv.java
index 7ea147c..076e621 100644
--- a/tests/common/com/android/documentsui/testing/TestEnv.java
+++ b/tests/common/com/android/documentsui/testing/TestEnv.java
@@ -21,6 +21,7 @@
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.State;
+import com.android.documentsui.dirlist.TestFocusHandler;
import com.android.documentsui.dirlist.TestModel;
import com.android.documentsui.selection.SelectionManager;
@@ -50,7 +51,7 @@
public final State state = new State();
public final TestRootsAccess roots = new TestRootsAccess();
public final TestDocumentsAccess docs = new TestDocumentsAccess();
- public final TestProviderAccess providers = new TestProviderAccess();
+ public final TestFocusHandler focusHandler = new TestFocusHandler();
public final TestModel model;
public final SelectionManager selectionMgr;
public final TestSearchViewManager searchViewManager;
@@ -111,7 +112,7 @@
Assert.assertNotNull(rootDoc);
Assert.assertEquals(rootDoc.displayName, FOLDER_0.displayName);
- state.stack.root = TestRootsAccess.HOME;
+ state.stack.changeRoot(TestRootsAccess.HOME);
state.stack.push(rootDoc);
}
diff --git a/tests/common/com/android/documentsui/testing/TestProviderAccess.java b/tests/common/com/android/documentsui/testing/TestProviderAccess.java
deleted file mode 100644
index 1887f81..0000000
--- a/tests/common/com/android/documentsui/testing/TestProviderAccess.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.documentsui.testing;
-
-import android.annotation.Nullable;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Path;
-
-import com.android.documentsui.ProviderAccess;
-
-public class TestProviderAccess implements ProviderAccess {
-
- public @Nullable Path nextPath;
-
- @Override
- public DocumentsContract.Path findPath(Uri docUri)
- throws RemoteException {
- return nextPath;
- }
-}
diff --git a/tests/common/com/android/documentsui/ui/TestDialogController.java b/tests/common/com/android/documentsui/ui/TestDialogController.java
index dbac0f5..e3135be 100644
--- a/tests/common/com/android/documentsui/ui/TestDialogController.java
+++ b/tests/common/com/android/documentsui/ui/TestDialogController.java
@@ -28,6 +28,7 @@
public int mNextConfirmationCode;
private boolean mFileOpFailed;
private boolean mNoApplicationFound;
+ private boolean mDocumentsClipped;
public TestDialogController() {
// by default, always confirm
@@ -51,6 +52,11 @@
mNoApplicationFound = true;
}
+ @Override
+ public void showDocumentsClipped(int size) {
+ mDocumentsClipped = true;
+ }
+
public void assertNoFileFailures() {
Assert.assertFalse(mFileOpFailed);
}
@@ -59,6 +65,10 @@
Assert.assertFalse(mNoApplicationFound);
}
+ public void assertDocumentsClippedNotShown() {
+ Assert.assertFalse(mDocumentsClipped);
+ }
+
public void confirmNext() {
mNextConfirmationCode = ConfirmationCallback.CONFIRM;
}
diff --git a/tests/functional/com/android/documentsui/BandSelectionUiTest.java b/tests/functional/com/android/documentsui/BandSelectionUiTest.java
index 61bb442..c1b44b4 100644
--- a/tests/functional/com/android/documentsui/BandSelectionUiTest.java
+++ b/tests/functional/com/android/documentsui/BandSelectionUiTest.java
@@ -18,7 +18,7 @@
import android.graphics.Point;
import android.graphics.Rect;
-import android.test.suitebuilder.annotation.LargeTest;
+import android.support.test.filters.LargeTest;
import com.android.documentsui.files.FilesActivity;
diff --git a/tests/functional/com/android/documentsui/FileManagementUiTest.java b/tests/functional/com/android/documentsui/FileManagementUiTest.java
index dd186cb..bd62550 100644
--- a/tests/functional/com/android/documentsui/FileManagementUiTest.java
+++ b/tests/functional/com/android/documentsui/FileManagementUiTest.java
@@ -21,11 +21,10 @@
import android.net.Uri;
import android.os.RemoteException;
+import android.support.test.filters.LargeTest;
import android.support.test.filters.Suppress;
-import android.test.suitebuilder.annotation.LargeTest;
import android.view.KeyEvent;
-import com.android.documentsui.R;
import com.android.documentsui.files.FilesActivity;
@LargeTest
diff --git a/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java b/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java
index 8fc6d27..ff86a7f 100644
--- a/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java
+++ b/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java
@@ -21,7 +21,7 @@
import android.content.Intent;
import android.provider.DocumentsContract;
-import android.test.suitebuilder.annotation.LargeTest;
+import android.support.test.filters.LargeTest;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
diff --git a/tests/functional/com/android/documentsui/FilesActivityUiTest.java b/tests/functional/com/android/documentsui/FilesActivityUiTest.java
index 4f0ce13..a798f16 100644
--- a/tests/functional/com/android/documentsui/FilesActivityUiTest.java
+++ b/tests/functional/com/android/documentsui/FilesActivityUiTest.java
@@ -18,7 +18,7 @@
import android.net.Uri;
import android.os.RemoteException;
-import android.test.suitebuilder.annotation.LargeTest;
+import android.support.test.filters.LargeTest;
import com.android.documentsui.files.FilesActivity;
diff --git a/tests/functional/com/android/documentsui/GestureSelectionUiTest.java b/tests/functional/com/android/documentsui/GestureSelectionUiTest.java
index 16c92f3..a8a1aec 100644
--- a/tests/functional/com/android/documentsui/GestureSelectionUiTest.java
+++ b/tests/functional/com/android/documentsui/GestureSelectionUiTest.java
@@ -16,7 +16,7 @@
package com.android.documentsui;
-import android.test.suitebuilder.annotation.LargeTest;
+import android.support.test.filters.LargeTest;
import com.android.documentsui.files.FilesActivity;
diff --git a/tests/functional/com/android/documentsui/IntegratedDownloadsUiTest.java b/tests/functional/com/android/documentsui/IntegratedDownloadsUiTest.java
index da340c2..62e5fb6 100644
--- a/tests/functional/com/android/documentsui/IntegratedDownloadsUiTest.java
+++ b/tests/functional/com/android/documentsui/IntegratedDownloadsUiTest.java
@@ -20,10 +20,10 @@
import android.app.DownloadManager.Request;
import android.content.Context;
import android.net.Uri;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.Suppress;
import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiObject;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
import android.view.MotionEvent;
import com.android.documentsui.files.FilesActivity;
diff --git a/tests/functional/com/android/documentsui/KeyboardNavigationUiTest.java b/tests/functional/com/android/documentsui/KeyboardNavigationUiTest.java
index f70918d..d9e2065 100644
--- a/tests/functional/com/android/documentsui/KeyboardNavigationUiTest.java
+++ b/tests/functional/com/android/documentsui/KeyboardNavigationUiTest.java
@@ -16,10 +16,9 @@
package com.android.documentsui;
-import android.net.Uri;
import android.os.RemoteException;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.Suppress;
import android.view.KeyEvent;
import com.android.documentsui.files.FilesActivity;
diff --git a/tests/functional/com/android/documentsui/RenameDocumentUiTest.java b/tests/functional/com/android/documentsui/RenameDocumentUiTest.java
index 0330ac3..9848bd5 100644
--- a/tests/functional/com/android/documentsui/RenameDocumentUiTest.java
+++ b/tests/functional/com/android/documentsui/RenameDocumentUiTest.java
@@ -16,10 +16,9 @@
package com.android.documentsui;
+import android.support.test.filters.LargeTest;
import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.test.suitebuilder.annotation.LargeTest;
-import com.android.documentsui.R;
import com.android.documentsui.files.FilesActivity;
@LargeTest
diff --git a/tests/functional/com/android/documentsui/SearchViewUiTest.java b/tests/functional/com/android/documentsui/SearchViewUiTest.java
index 1b248a9..8e54512 100644
--- a/tests/functional/com/android/documentsui/SearchViewUiTest.java
+++ b/tests/functional/com/android/documentsui/SearchViewUiTest.java
@@ -19,9 +19,9 @@
import static com.android.documentsui.StubProvider.ROOT_0_ID;
import static com.android.documentsui.StubProvider.ROOT_1_ID;
+import android.support.test.filters.LargeTest;
import android.support.test.filters.Suppress;
import android.support.v7.recyclerview.R;
-import android.test.suitebuilder.annotation.LargeTest;
import com.android.documentsui.files.FilesActivity;
diff --git a/tests/functional/com/android/documentsui/SidebarUiTest.java b/tests/functional/com/android/documentsui/SidebarUiTest.java
index 79dacea..c21c853 100644
--- a/tests/functional/com/android/documentsui/SidebarUiTest.java
+++ b/tests/functional/com/android/documentsui/SidebarUiTest.java
@@ -19,8 +19,8 @@
import static com.android.documentsui.StubProvider.ROOT_0_ID;
import static com.android.documentsui.StubProvider.ROOT_1_ID;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.Suppress;
import com.android.documentsui.files.FilesActivity;
diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
index 2e56c54..b3379bf 100644
--- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
@@ -59,7 +59,7 @@
mEnv.state,
mEnv.roots,
mEnv.docs,
- mEnv.providers,
+ mEnv.focusHandler,
mEnv.selectionMgr,
mEnv.searchViewManager,
mEnv::lookupExecutor) {
@@ -97,18 +97,18 @@
mEnv.populateStack();
mEnv.searchViewManager.isSearching = true;
- mEnv.providers.nextPath = new Path(
+ mEnv.docs.nextPath = new Path(
TestRootsAccess.HOME.rootId,
Arrays.asList(TestEnv.FOLDER_1.documentId, TestEnv.FOLDER_2.documentId));
mEnv.docs.nextDocuments = Arrays.asList(TestEnv.FOLDER_1, TestEnv.FOLDER_2);
- mEnv.state.pushDocument(TestEnv.FOLDER_0);
+ mEnv.state.stack.push(TestEnv.FOLDER_0);
mHandler.openContainerDocument(TestEnv.FOLDER_2);
mEnv.beforeAsserts();
- assertEquals(mEnv.providers.nextPath.getPath().size(), mEnv.state.stack.size());
+ assertEquals(mEnv.docs.nextPath.getPath().size(), mEnv.state.stack.size());
assertEquals(TestEnv.FOLDER_2, mEnv.state.stack.peek());
}
@@ -120,7 +120,7 @@
mEnv.searchViewManager.isSearching = true;
mEnv.docs.nextDocuments = Arrays.asList(TestEnv.FOLDER_1, TestEnv.FOLDER_2);
- mEnv.state.pushDocument(TestEnv.FOLDER_0);
+ mEnv.state.stack.push(TestEnv.FOLDER_0);
mHandler.openContainerDocument(TestEnv.FOLDER_2);
diff --git a/tests/unit/com/android/documentsui/base/DocumentStackTest.java b/tests/unit/com/android/documentsui/base/DocumentStackTest.java
new file mode 100644
index 0000000..b7e3cc7
--- /dev/null
+++ b/tests/unit/com/android/documentsui/base/DocumentStackTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.documentsui.base;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DocumentStackTest {
+ private static final DocumentInfo DIR_1;
+ private static final DocumentInfo DIR_2;
+
+ private DocumentStack mStack;
+
+ static {
+ DIR_1 = new DocumentInfo();
+ DIR_1.displayName = "firstDirectory";
+ DIR_2 = new DocumentInfo();
+ DIR_2.displayName = "secondDirectory";
+ }
+
+ @Before
+ public void setUp() {
+ mStack = new DocumentStack();
+ }
+
+ @Test
+ public void testInitialStateEmpty() {
+ assertFalse(mStack.hasLocationChanged());
+ }
+
+ @Test
+ public void testPushDocument_ChangesLocation() {
+ mStack.push(DIR_1);
+ mStack.push(DIR_2);
+ assertTrue(mStack.hasLocationChanged());
+ }
+
+ @Test
+ public void testPushDocument_ModifiesStack() {
+ mStack.push(DIR_1);
+ mStack.push(DIR_2);
+ assertEquals(DIR_2, mStack.peek());
+ }
+
+ @Test
+ public void testPopDocument_ModifiesStack() {
+ mStack.push(DIR_1);
+ mStack.push(DIR_2);
+ mStack.pop();
+ assertEquals(DIR_1, mStack.peek());
+ }
+}
diff --git a/tests/unit/com/android/documentsui/base/StateTest.java b/tests/unit/com/android/documentsui/base/StateTest.java
index 1f01b67..3858ae4 100644
--- a/tests/unit/com/android/documentsui/base/StateTest.java
+++ b/tests/unit/com/android/documentsui/base/StateTest.java
@@ -16,51 +16,54 @@
package com.android.documentsui.base;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import com.android.documentsui.base.DocumentInfo;
+import static org.junit.Assert.assertArrayEquals;
+import android.content.Intent;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
@SmallTest
-public class StateTest extends AndroidTestCase {
+public class StateTest {
- private static final DocumentInfo DIR_1;
- private static final DocumentInfo DIR_2;
+ private static final String[] MIME_TYPES = { "image/gif", "image/jpg" };
+ private Intent mIntent;
private State mState;
- static {
- DIR_1 = new DocumentInfo();
- DIR_1.displayName = "firstDirectory";
- DIR_2 = new DocumentInfo();
- DIR_2.displayName = "secondDirectory";
- }
-
- @Override
- protected void setUp() throws Exception {
+ @Before
+ public void setUp() {
+ mIntent = new Intent();
mState = new State();
}
- public void testInitialStateEmpty() {
- assertFalse(mState.hasLocationChanged());
+ @Test
+ public void testAcceptAllMimeTypesByDefault() {
+ mState.initAcceptMimes(mIntent);
+
+ assertArrayEquals(new String[] { "*/*" }, mState.acceptMimes);
}
- public void testPushDocument_ChangesLocation() {
- mState.pushDocument(DIR_1);
- mState.pushDocument(DIR_2);
- assertTrue(mState.hasLocationChanged());
+ @Test
+ public void testAcceptGivenMimeTypesInExtra() {
+ mIntent.putExtra(Intent.EXTRA_MIME_TYPES, MIME_TYPES);
+
+ mState.initAcceptMimes(mIntent);
+
+ assertArrayEquals(MIME_TYPES, mState.acceptMimes);
}
- public void testPushDocument_ModifiesStack() {
- mState.pushDocument(DIR_1);
- mState.pushDocument(DIR_2);
- assertEquals(DIR_2, mState.stack.getFirst());
- }
+ @Test
+ public void testAcceptIntentTypeWithoutExtra() {
+ mIntent.setType(MIME_TYPES[0]);
- public void testPopDocument_ModifiesStack() {
- mState.pushDocument(DIR_1);
- mState.pushDocument(DIR_2);
- mState.popDocument();
- assertEquals(DIR_1, mState.stack.getFirst());
+ mState.initAcceptMimes(mIntent);
+
+ assertArrayEquals(new String[] { MIME_TYPES[0] }, mState.acceptMimes);
}
}
diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
index 97b199d..c332bfd 100644
--- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
@@ -36,6 +36,7 @@
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
+import com.android.documentsui.selection.Selection;
import com.android.documentsui.testing.Roots;
import com.android.documentsui.testing.TestConfirmationCallback;
import com.android.documentsui.testing.TestEnv;
@@ -71,7 +72,7 @@
mEnv.state,
mEnv.roots,
mEnv.docs,
- mEnv.providers,
+ mEnv.focusHandler,
mEnv.selectionMgr,
mEnv.searchViewManager,
mEnv::lookupExecutor,
@@ -113,13 +114,32 @@
}
@Test
- public void testDeleteSelectedDocuments() {
+ public void testCutSelectedDocuments_NoGivenSelection() {
mEnv.populateStack();
+ mEnv.selectionMgr.clearSelection();
+ mHandler.cutToClipboard();
+ mDialogs.assertDocumentsClippedNotShown();
+ }
+
+ @Test
+ public void testCopySelectedDocuments_NoGivenSelection() {
+ mEnv.populateStack();
+
+ mEnv.selectionMgr.clearSelection();
+ mHandler.copyToClipboard();
+ mDialogs.assertDocumentsClippedNotShown();
+ }
+
+ @Test
+ public void testDeleteSelectedDocuments_NoSelection() {
+ mEnv.populateStack();
+
+ mEnv.selectionMgr.clearSelection();
mHandler.deleteSelectedDocuments();
mDialogs.assertNoFileFailures();
- mActivity.startService.assertCalled();
- mActionModeAddons.finishOnConfirmed.assertConfirmed();
+ mActivity.startService.assertNotCalled();
+ mActionModeAddons.finishOnConfirmed.assertNeverCalled();
}
@Test
diff --git a/tests/unit/com/android/documentsui/files/ActivityInputHandlerTest.java b/tests/unit/com/android/documentsui/files/ActivityInputHandlerTest.java
index 37a696f..b579dbe 100644
--- a/tests/unit/com/android/documentsui/files/ActivityInputHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActivityInputHandlerTest.java
@@ -16,7 +16,6 @@
package com.android.documentsui.files;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.support.test.filters.MediumTest;
@@ -24,50 +23,30 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
-import com.android.documentsui.dirlist.TestData;
-import com.android.documentsui.selection.SelectionManager;
-import com.android.documentsui.selection.SelectionProbe;
-import com.android.documentsui.testing.SelectionManagers;
-import com.android.documentsui.testing.TestActionHandler;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-
@RunWith(AndroidJUnit4.class)
@MediumTest
public class ActivityInputHandlerTest {
- private static final List<String> ITEMS = TestData.create(100);
-
- private SelectionProbe mSelection;
- private TestActionHandler mActionHandler;
private ActivityInputHandler mActivityInputHandler;
+ private boolean mDeleteHappened;
@Before
public void setUp() {
- SelectionManager selectionMgr = SelectionManagers.createTestInstance(ITEMS);
- mSelection = new SelectionProbe(selectionMgr);
- mActionHandler = new TestActionHandler();
- mActivityInputHandler = new ActivityInputHandler(selectionMgr, mActionHandler);
+ mDeleteHappened = false;
+ mActivityInputHandler = new ActivityInputHandler(() -> {
+ mDeleteHappened = true;
+ });
}
@Test
- public void testDelete_noSelection() {
- KeyEvent event = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0,
- KeyEvent.META_ALT_ON);
- assertFalse(mActivityInputHandler.onKeyDown(event.getKeyCode(), event));
- assertFalse(mActionHandler.mDeleteHappened);
- }
-
- @Test
- public void testDelete_hasSelection() {
- mSelection.select(1);
+ public void testDelete() {
KeyEvent event = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0,
KeyEvent.META_ALT_ON);
assertTrue(mActivityInputHandler.onKeyDown(event.getKeyCode(), event));
- assertTrue(mActionHandler.mDeleteHappened);
+ assertTrue(mDeleteHappened);
}
}
diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
index b6897de..5e26cd8 100644
--- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -24,10 +24,13 @@
import android.content.Intent;
import android.net.Uri;
+import android.provider.DocumentsContract.Path;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.documentsui.R;
+import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.testing.TestEnv;
@@ -38,6 +41,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.List;
+
@RunWith(AndroidJUnit4.class)
@MediumTest
public class ActionHandlerTest {
@@ -59,7 +65,7 @@
mEnv.state,
mEnv.roots,
mEnv.docs,
- mEnv.providers,
+ mEnv.focusHandler,
mEnv.selectionMgr,
mEnv.searchViewManager,
mEnv::lookupExecutor,
@@ -123,6 +129,26 @@
}
@Test
+ public void testInitLocation_LaunchToDocuments() throws Exception {
+ mEnv.docs.nextIsDocumentsUri = true;
+ mEnv.docs.nextPath = new Path(
+ TestRootsAccess.HOME.rootId,
+ Arrays.asList(
+ TestEnv.FOLDER_0.documentId,
+ TestEnv.FOLDER_1.documentId,
+ TestEnv.FILE_GIF.documentId));
+ mEnv.docs.nextDocuments =
+ Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1, TestEnv.FILE_GIF);
+
+ Intent intent = mActivity.getIntent();
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ intent.setData(TestEnv.FILE_GIF.derivedUri);
+ mHandler.initLocation(intent);
+
+ assertStackEquals(TestRootsAccess.HOME, Arrays.asList(TestEnv.FOLDER_0, TestEnv.FOLDER_1));
+ }
+
+ @Test
public void testOpenContainerDocument() {
mHandler.openContainerDocument(TestEnv.FOLDER_0);
@@ -131,6 +157,17 @@
mActivity.refreshCurrentRootAndDirectory.assertCalled();
}
+ private void assertStackEquals(RootInfo root, List<DocumentInfo> docs) throws Exception {
+ mEnv.beforeAsserts();
+
+ final DocumentStack stack = mEnv.state.stack;
+ assertEquals(stack.getRoot(), root);
+ assertEquals(docs.size(), stack.size());
+ for (int i = 0; i < docs.size(); ++i) {
+ assertEquals(docs.get(i), stack.get(i));
+ }
+ }
+
private void assertRootPicked(Uri expectedUri) throws Exception {
mEnv.beforeAsserts();
diff --git a/tests/unit/com/android/documentsui/services/AbstractJobTest.java b/tests/unit/com/android/documentsui/services/AbstractJobTest.java
index c2034d2..d6b1a70 100644
--- a/tests/unit/com/android/documentsui/services/AbstractJobTest.java
+++ b/tests/unit/com/android/documentsui/services/AbstractJobTest.java
@@ -87,9 +87,8 @@
final T createJob(@OpType int opType, List<Uri> srcs, Uri srcParent, Uri destination)
throws Exception {
- DocumentStack stack = new DocumentStack();
- stack.push(DocumentInfo.fromUri(mResolver, destination));
- stack.root = mSrcRoot;
+ DocumentStack stack =
+ new DocumentStack(mSrcRoot, DocumentInfo.fromUri(mResolver, destination));
UrisSupplier urisSupplier = DocsProviders.createDocsProvider(srcs);
FileOperation operation = new FileOperation.Builder()