diff options
author | 2016-10-19 11:50:45 -0700 | |
---|---|---|
committer | 2016-10-20 11:17:09 -0700 | |
commit | 2a837422d25b44f2847bc82e7c90b4e8fcde1518 (patch) | |
tree | 5fa1618b4ee5dba9fb09562dc54c90fdd79176ff | |
parent | ccc18de8a6162724f54ab0ea6af350d21b0e2c13 (diff) |
Address comments in ag/1514806.
* Isolate LinkedList and stack/root manipulation in DocumentStack
* Consolidate ProviderAccess into DocumentAccess
* Pass Uri as the the param to LoadDocStackTask
* Tidy up initLocation() in picker.Actionhandler
* Add a test case for initLocation with data in picking intents
Bug: 32156176
Change-Id: I3ca07342fc594e47fc003df146dcf982f152f56d
31 files changed, 438 insertions, 353 deletions
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java index 390dd0053..814899bcf 100644 --- a/src/com/android/documentsui/AbstractActionHandler.java +++ b/src/com/android/documentsui/AbstractActionHandler.java @@ -61,7 +61,6 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> protected final State mState; protected final RootsAccess mRoots; protected final DocumentsAccess mDocs; - protected final ProviderAccess mProviders; protected final SelectionManager mSelectionMgr; protected final SearchViewManager mSearchMgr; protected final Lookup<String, Executor> mExecutors; @@ -71,7 +70,6 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> State state, RootsAccess roots, DocumentsAccess docs, - ProviderAccess providers, SelectionManager selectionMgr, SearchViewManager searchMgr, Lookup<String, Executor> executors) { @@ -79,7 +77,6 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> assert(activity != null); assert(state != null); assert(roots != null); - assert(providers != null); assert(selectionMgr != null); assert(searchMgr != null); assert(docs != null); @@ -88,7 +85,6 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> mState = state; mRoots = roots; mDocs = docs; - mProviders = providers; mSelectionMgr = selectionMgr; mSearchMgr = searchMgr; mExecutors = executors; @@ -180,33 +176,33 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> 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); } @@ -225,11 +221,11 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> 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); } @@ -247,12 +243,10 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons> 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/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index 3928a015e..1ea3b0fcc 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -340,7 +340,7 @@ public abstract class BaseActivity<T extends ActionHandler> 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 @@ -569,8 +569,9 @@ public abstract class BaseActivity<T extends ActionHandler> @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(); } diff --git a/src/com/android/documentsui/DocumentsAccess.java b/src/com/android/documentsui/DocumentsAccess.java index 8cfaa5a54..80d458987 100644 --- a/src/com/android/documentsui/DocumentsAccess.java +++ b/src/com/android/documentsui/DocumentsAccess.java @@ -18,11 +18,13 @@ package com.android.documentsui; 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.ArrayList; 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 @@ public interface DocumentsAccess { @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 @@ public interface DocumentsAccess { } @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 @@ public interface DocumentsAccess { 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 interface DocumentsAccess { 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 64eeb1403..3f1d48b76 100644 --- a/src/com/android/documentsui/DocumentsApplication.java +++ b/src/com/android/documentsui/DocumentsApplication.java @@ -28,7 +28,6 @@ import android.net.Uri; 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 @@ public class DocumentsApplication extends Application { private ClipStorage mClipStore; private DocumentClipper mClipper; - private ProviderAccess mProviderAccess; - public static RootsCache getRootsCache(Context context) { return ((DocumentsApplication) context.getApplicationContext()).mRoots; } @@ -54,11 +51,6 @@ public class DocumentsApplication extends Application { 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 @@ public class DocumentsApplication extends Application { 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 98597b8dd..03b6d097d 100644 --- a/src/com/android/documentsui/DropdownBreadcrumb.java +++ b/src/com/android/documentsui/DropdownBreadcrumb.java @@ -107,7 +107,7 @@ public final class DropdownBreadcrumb extends Spinner implements Breadcrumb { @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/HorizontalBreadcrumb.java b/src/com/android/documentsui/HorizontalBreadcrumb.java index 881fbbccf..240a0d3fc 100644 --- a/src/com/android/documentsui/HorizontalBreadcrumb.java +++ b/src/com/android/documentsui/HorizontalBreadcrumb.java @@ -195,7 +195,7 @@ public final class HorizontalBreadcrumb extends RecyclerView } 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 e849a1b4e..a73945cf1 100644 --- a/src/com/android/documentsui/LoadDocStackTask.java +++ b/src/com/android/documentsui/LoadDocStackTask.java @@ -40,45 +40,37 @@ import java.util.List; * 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 @@ public class LoadDocStackTask extends PairedTask<Activity, Void, DocumentStack> 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 4f971118f..65f90000f 100644 --- a/src/com/android/documentsui/NavigationViewManager.java +++ b/src/com/android/documentsui/NavigationViewManager.java @@ -75,7 +75,7 @@ public class NavigationViewManager { 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 67a0e3fbe..000000000 --- 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 feead7abd..d13f3b54a 100644 --- a/src/com/android/documentsui/RootsMonitor.java +++ b/src/com/android/documentsui/RootsMonitor.java @@ -134,7 +134,7 @@ final class RootsMonitor<T extends Activity & CommonAddons> { } // 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 c3a13e0c8..9e0c580ed 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 @@ import java.util.List; * 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; + mRoot = root; } + /** + * 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) { + mList = src.mList; for (DocumentInfo doc : docs) { - push(doc); + mList.addLast(doc); } - this.root = 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(); } /** - * 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. + * Returns {@link DocumentInfo} at index counted from the bottom of this stack. */ - public DocumentStack(DocumentStack src, DocumentInfo... docs) { - super(src); - for (DocumentInfo doc : docs) { - push(doc); + 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; + } - root = src.root; + 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 class DocumentStack extends LinkedList<DocumentInfo> implements Durable, } 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 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable, * {@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 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable, 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 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable, @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 f0d47bbda..fff50c9f9 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 class State implements android.os.Parcelable { 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 class State implements android.os.Parcelable { } } - 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 @@ public class State implements android.os.Parcelable { 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 @@ public class State implements android.os.Parcelable { 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 84d8b0565..b7f24f56e 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -1154,7 +1154,7 @@ public class DirectoryFragment extends Fragment if (mModel.isEmpty()) { if (mLocalState.mSearchMode) { - showNoResults(mState.stack.root); + showNoResults(mState.stack.getRoot()); } else { showEmptyDirectory(); } diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java index 09f90d443..838a431b5 100644 --- a/src/com/android/documentsui/files/ActionHandler.java +++ b/src/com/android/documentsui/files/ActionHandler.java @@ -41,7 +41,6 @@ import com.android.documentsui.base.DocumentStack; 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.State; import com.android.documentsui.clipping.ClipStore; @@ -87,7 +86,6 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa State state, RootsAccess roots, DocumentsAccess docs, - ProviderAccess providers, SelectionManager selectionMgr, SearchViewManager searchMgr, Lookup<String, Executor> executors, @@ -97,7 +95,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa DocumentClipper clipper, ClipStore clipStore) { - super(activity, state, roots, docs, providers, selectionMgr, searchMgr, executors); + super(activity, state, roots, docs, selectionMgr, searchMgr, executors); mActionModeAddons = actionModeAddons; mDialogs = dialogs; @@ -320,12 +318,12 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa // 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); } @@ -509,7 +507,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa 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/Config.java b/src/com/android/documentsui/files/Config.java index 69da89e99..30d468a9b 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 @@ public final class Config extends ActivityConfig { // 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 877914b0e..823c9ea25 100644 --- a/src/com/android/documentsui/files/FilesActivity.java +++ b/src/com/android/documentsui/files/FilesActivity.java @@ -115,7 +115,6 @@ public class FilesActivity mState, mRoots, mDocs, - DocumentsApplication.getProviderAccess(this), mSelectionMgr, mSearchManager, ProviderExecutor::forAuthority, @@ -167,7 +166,7 @@ public class FilesActivity final DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK); if (stack != null) { - state.stack = stack; + state.stack.reset(stack); } } diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java index 906b26ae7..e6b114456 100644 --- a/src/com/android/documentsui/picker/ActionHandler.java +++ b/src/com/android/documentsui/picker/ActionHandler.java @@ -37,7 +37,6 @@ import com.android.documentsui.base.DocumentStack; 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; @@ -67,13 +66,12 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T State state, RootsAccess roots, DocumentsAccess docs, - ProviderAccess providers, SelectionManager selectionMgr, SearchViewManager searchMgr, Lookup<String, Executor> executors, ActivityConfig activityConfig) { - super(activity, state, roots, docs, providers, selectionMgr, searchMgr, executors); + super(activity, state, roots, docs, selectionMgr, searchMgr, executors); mConfig = activityConfig; mScope = new ContentScope(this::onModelLoaded); @@ -81,42 +79,65 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T @Override public void initLocation(Intent intent) { + assert(intent != null); + 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(""); - - // 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 (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 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(Uri uri, @Nullable DocumentStack stack) { + 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 +215,7 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T 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 bb1a1294f..b77ce995e 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.DocumentStack; 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 @@ public class LastAccessedProvider extends ContentProvider { 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 645b7f795..17e6bb1d4 100644 --- a/src/com/android/documentsui/picker/PickActivity.java +++ b/src/com/android/documentsui/picker/PickActivity.java @@ -101,7 +101,6 @@ public class PickActivity mState, mRoots, mDocs, - DocumentsApplication.getProviderAccess(this), mSelectionMgr, mSearchManager, ProviderExecutor::forAuthority, diff --git a/src/com/android/documentsui/services/CopyJob.java b/src/com/android/documentsui/services/CopyJob.java index 1551bade9..b9bc65151 100644 --- a/src/com/android/documentsui/services/CopyJob.java +++ b/src/com/android/documentsui/services/CopyJob.java @@ -276,7 +276,7 @@ class CopyJob extends Job { 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 @@ class CopyJob extends Job { 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 b7924c8ac..16bda157a 100644 --- a/src/com/android/documentsui/services/MoveJob.java +++ b/src/com/android/documentsui/services/MoveJob.java @@ -108,7 +108,7 @@ final class MoveJob extends CopyJob { 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/tests/common/com/android/documentsui/testing/TestActionHandler.java b/tests/common/com/android/documentsui/testing/TestActionHandler.java index 5ad056356..69f716c1d 100644 --- a/tests/common/com/android/documentsui/testing/TestActionHandler.java +++ b/tests/common/com/android/documentsui/testing/TestActionHandler.java @@ -40,7 +40,6 @@ public class TestActionHandler extends AbstractActionHandler<TestActivity> { env.state, env.roots, env.docs, - env.providers, env.selectionMgr, env.searchViewManager, (String authority) -> null); diff --git a/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java b/tests/common/com/android/documentsui/testing/TestDocumentsAccess.java index 7ded62444..fb821ee30 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 class TestDocumentsAccess implements DocumentsAccess { 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 class TestDocumentsAccess implements DocumentsAccess { 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 7ea147c7d..e0af39442 100644 --- a/tests/common/com/android/documentsui/testing/TestEnv.java +++ b/tests/common/com/android/documentsui/testing/TestEnv.java @@ -50,7 +50,6 @@ public class TestEnv { 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 TestModel model; public final SelectionManager selectionMgr; public final TestSearchViewManager searchViewManager; @@ -111,7 +110,7 @@ public class TestEnv { 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 1887f81fb..000000000 --- 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/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java index 2e56c54c3..5ac2741ae 100644 --- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java @@ -59,7 +59,6 @@ public class AbstractActionHandlerTest { mEnv.state, mEnv.roots, mEnv.docs, - mEnv.providers, mEnv.selectionMgr, mEnv.searchViewManager, mEnv::lookupExecutor) { @@ -97,18 +96,18 @@ public class AbstractActionHandlerTest { 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 +119,7 @@ public class AbstractActionHandlerTest { 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 000000000..b7e3cc782 --- /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 1f01b6714..3858ae427 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); - public void testPushDocument_ChangesLocation() { - mState.pushDocument(DIR_1); - mState.pushDocument(DIR_2); - assertTrue(mState.hasLocationChanged()); + assertArrayEquals(new String[] { "*/*" }, mState.acceptMimes); } - public void testPushDocument_ModifiesStack() { - mState.pushDocument(DIR_1); - mState.pushDocument(DIR_2); - assertEquals(DIR_2, mState.stack.getFirst()); + @Test + public void testAcceptGivenMimeTypesInExtra() { + mIntent.putExtra(Intent.EXTRA_MIME_TYPES, MIME_TYPES); + + mState.initAcceptMimes(mIntent); + + assertArrayEquals(MIME_TYPES, mState.acceptMimes); } - public void testPopDocument_ModifiesStack() { - mState.pushDocument(DIR_1); - mState.pushDocument(DIR_2); - mState.popDocument(); - assertEquals(DIR_1, mState.stack.getFirst()); + @Test + public void testAcceptIntentTypeWithoutExtra() { + mIntent.setType(MIME_TYPES[0]); + + 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 97b199d91..271639bef 100644 --- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java @@ -71,7 +71,6 @@ public class ActionHandlerTest { mEnv.state, mEnv.roots, mEnv.docs, - mEnv.providers, mEnv.selectionMgr, mEnv.searchViewManager, mEnv::lookupExecutor, diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java index b6897de85..73cb123bc 100644 --- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java @@ -24,10 +24,13 @@ import static org.junit.Assert.assertNotNull; 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.Before; 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,6 @@ public class ActionHandlerTest { mEnv.state, mEnv.roots, mEnv.docs, - mEnv.providers, mEnv.selectionMgr, mEnv.searchViewManager, mEnv::lookupExecutor, @@ -123,6 +128,26 @@ public class ActionHandlerTest { } @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 +156,17 @@ public class ActionHandlerTest { 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 c2034d24f..d6b1a7058 100644 --- a/tests/unit/com/android/documentsui/services/AbstractJobTest.java +++ b/tests/unit/com/android/documentsui/services/AbstractJobTest.java @@ -87,9 +87,8 @@ public abstract class AbstractJobTest<T extends Job> extends AndroidTestCase { 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() |