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
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 390dd00..814899b 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -61,7 +61,6 @@
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 @@
State state,
RootsAccess roots,
DocumentsAccess docs,
- ProviderAccess providers,
SelectionManager selectionMgr,
SearchViewManager searchMgr,
Lookup<String, Executor> executors) {
@@ -79,7 +77,6 @@
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 @@
mState = state;
mRoots = roots;
mDocs = docs;
- mProviders = providers;
mSelectionMgr = selectionMgr;
mSearchMgr = searchMgr;
mExecutors = executors;
@@ -180,33 +176,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);
}
@@ -225,11 +221,11 @@
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 @@
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 3928a01..1ea3b0f 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -340,7 +340,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
@@ -569,8 +569,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();
}
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/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 feead7a..d13f3b5 100644
--- a/src/com/android/documentsui/RootsMonitor.java
+++ b/src/com/android/documentsui/RootsMonitor.java
@@ -134,7 +134,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 f0d47bb..fff50c9 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 84d8b05..b7f24f5 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -1154,7 +1154,7 @@
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 09f90d4..838a431 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.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 @@
State state,
RootsAccess roots,
DocumentsAccess docs,
- ProviderAccess providers,
SelectionManager selectionMgr,
SearchViewManager searchMgr,
Lookup<String, Executor> executors,
@@ -97,7 +95,7 @@
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 @@
// 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 @@
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 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 877914b..823c9ea 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -115,7 +115,6 @@
mState,
mRoots,
mDocs,
- DocumentsApplication.getProviderAccess(this),
mSelectionMgr,
mSearchManager,
ProviderExecutor::forAuthority,
@@ -167,7 +166,7 @@
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 906b26a..e6b1144 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.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 @@
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 @@
@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 +215,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 645b7f7..17e6bb1 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -101,7 +101,6 @@
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 1551bad..b9bc651 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 b7924c8..16bda15 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);