diff options
Diffstat (limited to 'packages/DocumentsUI/src/com/android/documentsui')
14 files changed, 1169 insertions, 162 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java new file mode 100644 index 0000000..a8a0c1d --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + +import java.util.HashMap; +import java.util.List; + +import android.app.Activity; +import android.app.Fragment; +import android.content.pm.ResolveInfo; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; + +import com.android.documentsui.model.DocumentInfo; +import com.android.documentsui.model.DocumentStack; +import com.android.documentsui.model.DurableUtils; +import com.android.documentsui.model.RootInfo; +import com.google.common.collect.Maps; + +abstract class BaseActivity extends Activity { + + public abstract State getDisplayState(); + public abstract RootInfo getCurrentRoot(); + public abstract void onStateChanged(); + public abstract void setRootsDrawerOpen(boolean open); + public abstract void onDocumentPicked(DocumentInfo doc); + public abstract void onDocumentsPicked(List<DocumentInfo> docs); + public abstract DocumentInfo getCurrentDirectory(); + public abstract void setPending(boolean pending); + public abstract void onStackPicked(DocumentStack stack); + public abstract void onPickRequested(DocumentInfo pickTarget); + public abstract void onAppPicked(ResolveInfo info); + public abstract void onRootPicked(RootInfo root, boolean closeDrawer); + public abstract void onSaveRequested(DocumentInfo replaceTarget); + public abstract void onSaveRequested(String mimeType, String displayName); + + public static BaseActivity get(Fragment fragment) { + return (BaseActivity) fragment.getActivity(); + } + + public static class State implements android.os.Parcelable { + public int action; + public String[] acceptMimes; + + /** Explicit user choice */ + public int userMode = MODE_UNKNOWN; + /** Derived after loader */ + public int derivedMode = MODE_LIST; + + /** Explicit user choice */ + public int userSortOrder = SORT_ORDER_UNKNOWN; + /** Derived after loader */ + public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME; + + public boolean allowMultiple = false; + public boolean showSize = false; + public boolean localOnly = false; + public boolean forceAdvanced = false; + public boolean showAdvanced = false; + public boolean stackTouched = false; + public boolean restored = false; + + /** Current user navigation stack; empty implies recents. */ + public DocumentStack stack = new DocumentStack(); + /** Currently active search, overriding any stack. */ + public String currentSearch; + + /** Instance state for every shown directory */ + public HashMap<String, SparseArray<Parcelable>> dirState = Maps.newHashMap(); + + public static final int ACTION_OPEN = 1; + public static final int ACTION_CREATE = 2; + public static final int ACTION_GET_CONTENT = 3; + public static final int ACTION_OPEN_TREE = 4; + public static final int ACTION_MANAGE = 5; + public static final int ACTION_MANAGE_ALL = 6; + + public static final int MODE_UNKNOWN = 0; + public static final int MODE_LIST = 1; + public static final int MODE_GRID = 2; + + public static final int SORT_ORDER_UNKNOWN = 0; + public static final int SORT_ORDER_DISPLAY_NAME = 1; + public static final int SORT_ORDER_LAST_MODIFIED = 2; + public static final int SORT_ORDER_SIZE = 3; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(action); + out.writeInt(userMode); + out.writeStringArray(acceptMimes); + out.writeInt(userSortOrder); + out.writeInt(allowMultiple ? 1 : 0); + out.writeInt(showSize ? 1 : 0); + out.writeInt(localOnly ? 1 : 0); + out.writeInt(forceAdvanced ? 1 : 0); + out.writeInt(showAdvanced ? 1 : 0); + out.writeInt(stackTouched ? 1 : 0); + out.writeInt(restored ? 1 : 0); + DurableUtils.writeToParcel(out, stack); + out.writeString(currentSearch); + out.writeMap(dirState); + } + + public static final Creator<State> CREATOR = new Creator<State>() { + @Override + public State createFromParcel(Parcel in) { + final State state = new State(); + state.action = in.readInt(); + state.userMode = in.readInt(); + state.acceptMimes = in.readStringArray(); + state.userSortOrder = in.readInt(); + state.allowMultiple = in.readInt() != 0; + state.showSize = in.readInt() != 0; + state.localOnly = in.readInt() != 0; + state.forceAdvanced = in.readInt() != 0; + state.showAdvanced = in.readInt() != 0; + state.stackTouched = in.readInt() != 0; + state.restored = in.readInt() != 0; + DurableUtils.readFromParcel(in, state.stack); + state.currentSearch = in.readString(); + in.readMap(state.dirState, null); + return state; + } + + @Override + public State[] newArray(int size) { + return new State[size]; + } + }; + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java index ba8c35f..1a17ee0 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java @@ -70,7 +70,7 @@ public class CreateDirectoryFragment extends DialogFragment { public void onClick(DialogInterface dialog, int which) { final String displayName = text1.getText().toString(); - final DocumentsActivity activity = (DocumentsActivity) getActivity(); + final BaseActivity activity = (BaseActivity) getActivity(); final DocumentInfo cwd = activity.getCurrentDirectory(); new CreateDirectoryTask(activity, cwd, displayName).executeOnExecutor( @@ -83,12 +83,12 @@ public class CreateDirectoryFragment extends DialogFragment { } private class CreateDirectoryTask extends AsyncTask<Void, Void, DocumentInfo> { - private final DocumentsActivity mActivity; + private final BaseActivity mActivity; private final DocumentInfo mCwd; private final String mDisplayName; public CreateDirectoryTask( - DocumentsActivity activity, DocumentInfo cwd, String displayName) { + BaseActivity activity, DocumentInfo cwd, String displayName) { mActivity = activity; mCwd = cwd; mDisplayName = displayName; diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 39c2252..f55912c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -17,12 +17,12 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; -import static com.android.documentsui.DocumentsActivity.State.ACTION_CREATE; -import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE; -import static com.android.documentsui.DocumentsActivity.State.MODE_GRID; -import static com.android.documentsui.DocumentsActivity.State.MODE_LIST; -import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN; -import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN; +import static com.android.documentsui.BaseActivity.State.ACTION_CREATE; +import static com.android.documentsui.BaseActivity.State.ACTION_MANAGE; +import static com.android.documentsui.BaseActivity.State.MODE_GRID; +import static com.android.documentsui.BaseActivity.State.MODE_LIST; +import static com.android.documentsui.BaseActivity.State.MODE_UNKNOWN; +import static com.android.documentsui.BaseActivity.State.SORT_ORDER_UNKNOWN; import static com.android.documentsui.model.DocumentInfo.getCursorInt; import static com.android.documentsui.model.DocumentInfo.getCursorLong; import static com.android.documentsui.model.DocumentInfo.getCursorString; @@ -76,7 +76,7 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; -import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.BaseActivity.State; import com.android.documentsui.ProviderExecutor.Preemptable; import com.android.documentsui.RecentsProvider.StateColumns; import com.android.documentsui.model.DocumentInfo; @@ -301,13 +301,13 @@ public class DirectoryFragment extends Fragment { state.derivedMode = result.mode; } state.derivedSortOrder = result.sortOrder; - ((DocumentsActivity) context).onStateChanged(); + ((BaseActivity) context).onStateChanged(); updateDisplayState(); // When launched into empty recents, show drawer if (mType == TYPE_RECENT_OPEN && mAdapter.isEmpty() && !state.stackTouched) { - ((DocumentsActivity) context).setRootsDrawerOpen(true); + ((BaseActivity) context).setRootsDrawerOpen(true); } // Restore any previous instance state @@ -386,7 +386,7 @@ public class DirectoryFragment extends Fragment { // Mode change is just visual change; no need to kick loader, and // deliver change event immediately. state.derivedMode = state.userMode; - ((DocumentsActivity) getActivity()).onStateChanged(); + ((BaseActivity) getActivity()).onStateChanged(); updateDisplayState(); } @@ -441,7 +441,7 @@ public class DirectoryFragment extends Fragment { final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); if (isDocumentEnabled(docMimeType, docFlags)) { final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); - ((DocumentsActivity) getActivity()).onDocumentPicked(doc); + ((BaseActivity) getActivity()).onDocumentPicked(doc); } } } @@ -487,7 +487,7 @@ public class DirectoryFragment extends Fragment { final int id = item.getItemId(); if (id == R.id.menu_open) { - DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); + BaseActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); mode.finish(); return true; @@ -616,7 +616,7 @@ public class DirectoryFragment extends Fragment { } private static State getDisplayState(Fragment fragment) { - return ((DocumentsActivity) fragment.getActivity()).getDisplayState(); + return ((BaseActivity) fragment.getActivity()).getDisplayState(); } private static abstract class Footer { diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 163615d..8e4ec8c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -17,11 +17,11 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; -import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN; -import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME; -import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; -import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE; -import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN; +import static com.android.documentsui.BaseActivity.State.MODE_UNKNOWN; +import static com.android.documentsui.BaseActivity.State.SORT_ORDER_DISPLAY_NAME; +import static com.android.documentsui.BaseActivity.State.SORT_ORDER_LAST_MODIFIED; +import static com.android.documentsui.BaseActivity.State.SORT_ORDER_SIZE; +import static com.android.documentsui.BaseActivity.State.SORT_ORDER_UNKNOWN; import static com.android.documentsui.model.DocumentInfo.getCursorInt; import android.content.AsyncTaskLoader; @@ -36,7 +36,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.util.Log; -import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.BaseActivity.State; import com.android.documentsui.RecentsProvider.StateColumns; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 8778f11..2245b16 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -20,14 +20,13 @@ import static com.android.documentsui.DirectoryFragment.ANIM_DOWN; import static com.android.documentsui.DirectoryFragment.ANIM_NONE; import static com.android.documentsui.DirectoryFragment.ANIM_SIDE; import static com.android.documentsui.DirectoryFragment.ANIM_UP; -import static com.android.documentsui.DocumentsActivity.State.ACTION_CREATE; -import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT; -import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE; -import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN; -import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN_TREE; -import static com.android.documentsui.DocumentsActivity.State.MODE_GRID; -import static com.android.documentsui.DocumentsActivity.State.MODE_LIST; - +import static com.android.documentsui.BaseActivity.State.ACTION_CREATE; +import static com.android.documentsui.BaseActivity.State.ACTION_GET_CONTENT; +import static com.android.documentsui.BaseActivity.State.ACTION_MANAGE; +import static com.android.documentsui.BaseActivity.State.ACTION_OPEN; +import static com.android.documentsui.BaseActivity.State.ACTION_OPEN_TREE; +import static com.android.documentsui.BaseActivity.State.MODE_GRID; +import static com.android.documentsui.BaseActivity.State.MODE_LIST; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; @@ -46,15 +45,12 @@ import android.graphics.Point; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Root; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout.DrawerListener; import android.util.Log; -import android.util.SparseArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -79,7 +75,6 @@ import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.DurableUtils; import com.android.documentsui.model.RootInfo; -import com.google.common.collect.Maps; import libcore.io.IoUtils; @@ -87,11 +82,10 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.concurrent.Executor; -public class DocumentsActivity extends Activity { +public class DocumentsActivity extends BaseActivity { public static final String TAG = "Documents"; private static final String EXTRA_STATE = "state"; @@ -203,8 +197,8 @@ public class DocumentsActivity extends Activity { moreApps.setComponent(null); moreApps.setPackage(null); RootsFragment.show(getFragmentManager(), moreApps); - } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE - || mState.action == ACTION_OPEN_TREE) { + } else if (mState.action == ACTION_OPEN + || mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) { RootsFragment.show(getFragmentManager(), null); } @@ -389,6 +383,7 @@ public class DocumentsActivity extends Activity { updateActionBar(); } + @Override public void setRootsDrawerOpen(boolean open) { if (!mShowAsDialog) { if (open) { @@ -667,9 +662,7 @@ public class DocumentsActivity extends Activity { invalidateOptionsMenu(); } - /** - * Update UI to reflect internal state changes not from user. - */ + @Override public void onStateChanged() { invalidateOptionsMenu(); } @@ -690,6 +683,7 @@ public class DocumentsActivity extends Activity { DirectoryFragment.get(getFragmentManager()).onUserModeChanged(); } + @Override public void setPending(boolean pending) { final SaveFragment save = SaveFragment.get(getFragmentManager()); if (save != null) { @@ -808,6 +802,7 @@ public class DocumentsActivity extends Activity { } }; + @Override public RootInfo getCurrentRoot() { if (mState.stack.root != null) { return mState.stack.root; @@ -816,6 +811,7 @@ public class DocumentsActivity extends Activity { } } + @Override public DocumentInfo getCurrentDirectory() { return mState.stack.peek(); } @@ -834,6 +830,7 @@ public class DocumentsActivity extends Activity { } } + @Override public State getDisplayState() { return mState; } @@ -895,6 +892,7 @@ public class DocumentsActivity extends Activity { dumpStack(); } + @Override public void onStackPicked(DocumentStack stack) { try { // Update the restored stack to ensure we have freshest data @@ -909,6 +907,7 @@ public class DocumentsActivity extends Activity { } } + @Override public void onRootPicked(RootInfo root, boolean closeDrawer) { // Clear entire backstack and start in new root mState.stack.root = root; @@ -955,6 +954,7 @@ public class DocumentsActivity extends Activity { } } + @Override public void onAppPicked(ResolveInfo info) { final Intent intent = new Intent(getIntent()); intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT); @@ -985,6 +985,7 @@ public class DocumentsActivity extends Activity { } } + @Override public void onDocumentPicked(DocumentInfo doc) { final FragmentManager fm = getFragmentManager(); if (doc.isDirectory()) { @@ -1020,6 +1021,7 @@ public class DocumentsActivity extends Activity { } } + @Override public void onDocumentsPicked(List<DocumentInfo> docs) { if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { final int size = docs.size(); @@ -1031,14 +1033,17 @@ public class DocumentsActivity extends Activity { } } + @Override public void onSaveRequested(DocumentInfo replaceTarget) { new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getCurrentExecutor()); } + @Override public void onSaveRequested(String mimeType, String displayName) { new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor()); } + @Override public void onPickRequested(DocumentInfo pickTarget) { final Uri viaUri = DocumentsContract.buildTreeDocumentUri(pickTarget.authority, pickTarget.documentId); @@ -1188,102 +1193,6 @@ public class DocumentsActivity extends Activity { } } - public static class State implements android.os.Parcelable { - public int action; - public String[] acceptMimes; - - /** Explicit user choice */ - public int userMode = MODE_UNKNOWN; - /** Derived after loader */ - public int derivedMode = MODE_LIST; - - /** Explicit user choice */ - public int userSortOrder = SORT_ORDER_UNKNOWN; - /** Derived after loader */ - public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME; - - public boolean allowMultiple = false; - public boolean showSize = false; - public boolean localOnly = false; - public boolean forceAdvanced = false; - public boolean showAdvanced = false; - public boolean stackTouched = false; - public boolean restored = false; - - /** Current user navigation stack; empty implies recents. */ - public DocumentStack stack = new DocumentStack(); - /** Currently active search, overriding any stack. */ - public String currentSearch; - - /** Instance state for every shown directory */ - public HashMap<String, SparseArray<Parcelable>> dirState = Maps.newHashMap(); - - public static final int ACTION_OPEN = 1; - public static final int ACTION_CREATE = 2; - public static final int ACTION_GET_CONTENT = 3; - public static final int ACTION_OPEN_TREE = 4; - public static final int ACTION_MANAGE = 5; - - public static final int MODE_UNKNOWN = 0; - public static final int MODE_LIST = 1; - public static final int MODE_GRID = 2; - - public static final int SORT_ORDER_UNKNOWN = 0; - public static final int SORT_ORDER_DISPLAY_NAME = 1; - public static final int SORT_ORDER_LAST_MODIFIED = 2; - public static final int SORT_ORDER_SIZE = 3; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(action); - out.writeInt(userMode); - out.writeStringArray(acceptMimes); - out.writeInt(userSortOrder); - out.writeInt(allowMultiple ? 1 : 0); - out.writeInt(showSize ? 1 : 0); - out.writeInt(localOnly ? 1 : 0); - out.writeInt(forceAdvanced ? 1 : 0); - out.writeInt(showAdvanced ? 1 : 0); - out.writeInt(stackTouched ? 1 : 0); - out.writeInt(restored ? 1 : 0); - DurableUtils.writeToParcel(out, stack); - out.writeString(currentSearch); - out.writeMap(dirState); - } - - public static final Creator<State> CREATOR = new Creator<State>() { - @Override - public State createFromParcel(Parcel in) { - final State state = new State(); - state.action = in.readInt(); - state.userMode = in.readInt(); - state.acceptMimes = in.readStringArray(); - state.userSortOrder = in.readInt(); - state.allowMultiple = in.readInt() != 0; - state.showSize = in.readInt() != 0; - state.localOnly = in.readInt() != 0; - state.forceAdvanced = in.readInt() != 0; - state.showAdvanced = in.readInt() != 0; - state.stackTouched = in.readInt() != 0; - state.restored = in.readInt() != 0; - DurableUtils.readFromParcel(in, state.stack); - state.currentSearch = in.readString(); - in.readMap(state.dirState, null); - return state; - } - - @Override - public State[] newArray(int size) { - return new State[size]; - } - }; - } - private void dumpStack() { Log.d(TAG, "Current stack: "); Log.d(TAG, " * " + mState.stack.root); @@ -1291,8 +1200,4 @@ public class DocumentsActivity extends Activity { Log.d(TAG, " +-- " + doc); } } - - public static DocumentsActivity get(Fragment fragment) { - return (DocumentsActivity) fragment.getActivity(); - } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java index 5112c92..4b008ca 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java @@ -69,7 +69,7 @@ public class PickFragment extends Fragment { private View.OnClickListener mPickListener = new View.OnClickListener() { @Override public void onClick(View v) { - final DocumentsActivity activity = DocumentsActivity.get(PickFragment.this); + final BaseActivity activity = BaseActivity.get(PickFragment.this); activity.onPickRequested(mPickTarget); } }; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java index 34ce42d..f5908c5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java @@ -17,7 +17,7 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; -import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; +import static com.android.documentsui.BaseActivity.State.SORT_ORDER_LAST_MODIFIED; import android.app.ActivityManager; import android.content.AsyncTaskLoader; @@ -34,7 +34,7 @@ import android.provider.DocumentsContract.Root; import android.text.format.DateUtils; import android.util.Log; -import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.BaseActivity.State; import com.android.documentsui.model.RootInfo; import com.google.android.collect.Maps; import com.google.common.collect.Lists; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index dd75dbd..26aecc5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -45,7 +45,7 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.BaseActivity.State; import com.android.documentsui.RecentsProvider.RecentColumns; import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.DurableUtils; @@ -95,7 +95,7 @@ public class RecentsCreateFragment extends Fragment { mListView.setAdapter(mAdapter); final RootsCache roots = DocumentsApplication.getRootsCache(context); - final State state = ((DocumentsActivity) getActivity()).getDisplayState(); + final State state = ((BaseActivity) getActivity()).getDisplayState(); mCallbacks = new LoaderCallbacks<List<DocumentStack>>() { @Override @@ -110,7 +110,7 @@ public class RecentsCreateFragment extends Fragment { // When launched into empty recents, show drawer if (mAdapter.isEmpty() && !state.stackTouched) { - ((DocumentsActivity) context).setRootsDrawerOpen(true); + ((BaseActivity) context).setRootsDrawerOpen(true); } } @@ -139,7 +139,7 @@ public class RecentsCreateFragment extends Fragment { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final DocumentStack stack = mAdapter.getItem(position); - ((DocumentsActivity) getActivity()).onStackPicked(stack); + ((BaseActivity) getActivity()).onStackPicked(stack); } }; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index d72db1d..ec71a04 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -36,7 +36,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Root; import android.util.Log; -import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.BaseActivity.State; import com.android.documentsui.model.RootInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 884cf31..ed5e123 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -16,8 +16,6 @@ package com.android.documentsui; -import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT; - import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; @@ -43,7 +41,7 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.BaseActivity.State; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; import com.google.common.collect.Lists; @@ -101,7 +99,7 @@ public class RootsFragment extends Fragment { final Context context = getActivity(); final RootsCache roots = DocumentsApplication.getRootsCache(context); - final State state = ((DocumentsActivity) context).getDisplayState(); + final State state = ((BaseActivity) context).getDisplayState(); mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() { @Override @@ -138,9 +136,9 @@ public class RootsFragment extends Fragment { public void onDisplayStateChanged() { final Context context = getActivity(); - final State state = ((DocumentsActivity) context).getDisplayState(); + final State state = ((BaseActivity) context).getDisplayState(); - if (state.action == ACTION_GET_CONTENT) { + if (state.action == State.ACTION_GET_CONTENT) { mList.setOnItemLongClickListener(mItemLongClickListener); } else { mList.setOnItemLongClickListener(null); @@ -153,7 +151,7 @@ public class RootsFragment extends Fragment { public void onCurrentRootChanged() { if (mAdapter == null) return; - final RootInfo root = ((DocumentsActivity) getActivity()).getCurrentRoot(); + final RootInfo root = ((BaseActivity) getActivity()).getCurrentRoot(); for (int i = 0; i < mAdapter.getCount(); i++) { final Object item = mAdapter.getItem(i); if (item instanceof RootItem) { @@ -176,7 +174,7 @@ public class RootsFragment extends Fragment { private OnItemClickListener mItemListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this); + final BaseActivity activity = BaseActivity.get(RootsFragment.this); final Item item = mAdapter.getItem(position); if (item instanceof RootItem) { activity.onRootPicked(((RootItem) item).root, true); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java index 8d37cdf..49651b4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java @@ -19,7 +19,7 @@ package com.android.documentsui; import android.content.AsyncTaskLoader; import android.content.Context; -import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.BaseActivity.State; import com.android.documentsui.model.RootInfo; import java.util.Collection; diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java index ce98db2..a13fccc 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java @@ -113,7 +113,7 @@ public class SaveFragment extends Fragment { private View.OnClickListener mSaveListener = new View.OnClickListener() { @Override public void onClick(View v) { - final DocumentsActivity activity = DocumentsActivity.get(SaveFragment.this); + final BaseActivity activity = BaseActivity.get(SaveFragment.this); if (mReplaceTarget != null) { activity.onSaveRequested(mReplaceTarget); } else { diff --git a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java index 6c8ca20..3ec3d1c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java @@ -16,9 +16,9 @@ package com.android.documentsui; -import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME; -import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; -import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE; +import static com.android.documentsui.BaseActivity.State.SORT_ORDER_DISPLAY_NAME; +import static com.android.documentsui.BaseActivity.State.SORT_ORDER_LAST_MODIFIED; +import static com.android.documentsui.BaseActivity.State.SORT_ORDER_SIZE; import static com.android.documentsui.model.DocumentInfo.getCursorLong; import static com.android.documentsui.model.DocumentInfo.getCursorString; diff --git a/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java b/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java new file mode 100644 index 0000000..e01328d --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java @@ -0,0 +1,952 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui; + + +import static com.android.documentsui.DirectoryFragment.ANIM_DOWN; +import static com.android.documentsui.DirectoryFragment.ANIM_NONE; +import static com.android.documentsui.DirectoryFragment.ANIM_SIDE; +import static com.android.documentsui.DirectoryFragment.ANIM_UP; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.ActivityNotFoundException; +import android.content.ClipData; +import android.content.ComponentName; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Point; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Debug; +import android.provider.DocumentsContract; +import android.support.v4.app.ActionBarDrawerToggle; +import android.support.v4.widget.DrawerLayout; +import android.support.v4.widget.DrawerLayout.DrawerListener; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnActionExpandListener; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.SearchView; +import android.widget.SearchView.OnQueryTextListener; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.Toolbar; + +import com.android.documentsui.RecentsProvider.ResumeColumns; +import com.android.documentsui.model.DocumentInfo; +import com.android.documentsui.model.DocumentStack; +import com.android.documentsui.model.DurableUtils; +import com.android.documentsui.model.RootInfo; + +import libcore.io.IoUtils; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; + +public class StandaloneActivity extends BaseActivity { + public static final String TAG = "StandaloneFileManagement"; + + private static final String EXTRA_STATE = "state"; + + private static final int CODE_FORWARD = 42; + + private SearchView mSearchView; + + private Toolbar mToolbar; + private Spinner mToolbarStack; + + private Toolbar mRootsToolbar; + + private ActionBarDrawerToggle mDrawerToggle; + + private DirectoryContainerView mDirectoryContainer; + + private boolean mIgnoreNextNavigation; + private boolean mIgnoreNextClose; + private boolean mIgnoreNextCollapse; + + private boolean mSearchExpanded; + + private RootsCache mRoots; + private State mState; + + @Override + public void onCreate(Bundle icicle) { + // Debug.waitForDebugger(); + super.onCreate(icicle); + + mRoots = DocumentsApplication.getRootsCache(this); + + setResult(Activity.RESULT_CANCELED); + setContentView(R.layout.activity); + + final Context context = this; + final Resources res = getResources(); + + // Strongly define our horizontal dimension; we leave vertical as + final WindowManager.LayoutParams a = getWindow().getAttributes(); + + final Point size = new Point(); + getWindowManager().getDefaultDisplay().getSize(size); + // a.width = (int) res.getFraction(R.dimen.dialog_width, size.x, size.x); + + getWindow().setAttributes(a); + + mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory); + + if (icicle != null) { + mState = icicle.getParcelable(EXTRA_STATE); + } else { + buildDefaultState(); + } + + mToolbar = (Toolbar) findViewById(R.id.toolbar); + mToolbar.setTitleTextAppearance(context, + android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title); + + mToolbarStack = (Spinner) findViewById(R.id.stack); + mToolbarStack.setOnItemSelectedListener(mStackListener); + + mRootsToolbar = (Toolbar) findViewById(R.id.roots_toolbar); + if (mRootsToolbar != null) { + mRootsToolbar.setTitleTextAppearance(context, + android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title); + } + + setActionBar(mToolbar); + + RootsFragment.show(getFragmentManager(), null); + if (!mState.restored) { + new RestoreStackTask().execute(); + } else { + onCurrentDirectoryChanged(ANIM_NONE); + } + } + + private void buildDefaultState() { + mState = new State(); + + final Intent intent = getIntent(); + mState.action = State.ACTION_MANAGE_ALL; + mState.acceptMimes = new String[] { "*/*" }; + mState.allowMultiple = true; + mState.acceptMimes = new String[] { intent.getType() }; + mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); + mState.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false); + mState.showAdvanced = mState.forceAdvanced + | LocalPreferences.getDisplayAdvancedDevices(this); + mState.showSize = true; + } + + private class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> { + private Uri mRootUri; + + public RestoreRootTask(Uri rootUri) { + mRootUri = rootUri; + } + + @Override + protected RootInfo doInBackground(Void... params) { + final String rootId = DocumentsContract.getRootId(mRootUri); + return mRoots.getRootOneshot(mRootUri.getAuthority(), rootId); + } + + @Override + protected void onPostExecute(RootInfo root) { + if (isDestroyed()) return; + mState.restored = true; + + if (root != null) { + onRootPicked(root, true); + } else { + Log.w(TAG, "Failed to find root: " + mRootUri); + finish(); + } + } + } + + private class RestoreStackTask extends AsyncTask<Void, Void, Void> { + private volatile boolean mRestoredStack; + private volatile boolean mExternal; + + @Override + protected Void doInBackground(Void... params) { + // Restore last stack for calling package + final String packageName = getCallingPackageMaybeExtra(); + final Cursor cursor = getContentResolver() + .query(RecentsProvider.buildResume(packageName), null, null, null, null); + try { + if (cursor.moveToFirst()) { + mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0; + final byte[] rawStack = cursor.getBlob( + cursor.getColumnIndex(ResumeColumns.STACK)); + DurableUtils.readFromArray(rawStack, mState.stack); + mRestoredStack = true; + } + } catch (IOException e) { + Log.w(TAG, "Failed to resume: " + e); + } finally { + IoUtils.closeQuietly(cursor); + } + + if (mRestoredStack) { + // Update the restored stack to ensure we have freshest data + final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState); + try { + mState.stack.updateRoot(matchingRoots); + mState.stack.updateDocuments(getContentResolver()); + } catch (FileNotFoundException e) { + Log.w(TAG, "Failed to restore stack: " + e); + mState.stack.reset(); + mRestoredStack = false; + } + } + + return null; + } + + @Override + protected void onPostExecute(Void result) { + if (isDestroyed()) return; + mState.restored = true; + onCurrentDirectoryChanged(ANIM_NONE); + } + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + if (mDrawerToggle != null) { + mDrawerToggle.syncState(); + } + updateActionBar(); + } + + @Override + public void setRootsDrawerOpen(boolean open) { + Log.w(TAG, "Trying to change state of roots drawer to > " + (open ? "open" : "closed")); + // throw new UnsupportedOperationException(); + } + + public void updateActionBar() { + final RootInfo root = getCurrentRoot(); + mToolbar.setNavigationIcon( + root != null ? root.loadToolbarIcon(mToolbar.getContext()) : null); + mToolbar.setNavigationContentDescription(R.string.drawer_open); + mToolbar.setNavigationOnClickListener(null); + + if (mSearchExpanded) { + mToolbar.setTitle(null); + mToolbarStack.setVisibility(View.GONE); + mToolbarStack.setAdapter(null); + } else { + if (mState.stack.size() <= 1) { + mToolbar.setTitle(root.title); + mToolbarStack.setVisibility(View.GONE); + mToolbarStack.setAdapter(null); + } else { + mToolbar.setTitle(null); + mToolbarStack.setVisibility(View.VISIBLE); + mToolbarStack.setAdapter(mStackAdapter); + + mIgnoreNextNavigation = true; + mToolbarStack.setSelection(mStackAdapter.getCount() - 1); + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.activity, menu); + + for (int i = 0; i < menu.size(); i++) { + final MenuItem item = menu.getItem(i); + switch (item.getItemId()) { + case R.id.menu_advanced: + case R.id.menu_file_size: + break; + default: + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } + } + + final MenuItem searchMenu = menu.findItem(R.id.menu_search); + mSearchView = (SearchView) searchMenu.getActionView(); + mSearchView.setOnQueryTextListener(new OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + mSearchExpanded = true; + mState.currentSearch = query; + mSearchView.clearFocus(); + onCurrentDirectoryChanged(ANIM_NONE); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + return false; + } + }); + + searchMenu.setOnActionExpandListener(new OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + mSearchExpanded = true; + updateActionBar(); + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + mSearchExpanded = false; + if (mIgnoreNextCollapse) { + mIgnoreNextCollapse = false; + return true; + } + + mState.currentSearch = null; + onCurrentDirectoryChanged(ANIM_NONE); + return true; + } + }); + + mSearchView.setOnCloseListener(new SearchView.OnCloseListener() { + @Override + public boolean onClose() { + mSearchExpanded = false; + if (mIgnoreNextClose) { + mIgnoreNextClose = false; + return false; + } + + mState.currentSearch = null; + onCurrentDirectoryChanged(ANIM_NONE); + return false; + } + }); + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + final FragmentManager fm = getFragmentManager(); + + final RootInfo root = getCurrentRoot(); + final DocumentInfo cwd = getCurrentDirectory(); + + final MenuItem createDir = menu.findItem(R.id.menu_create_dir); + final MenuItem search = menu.findItem(R.id.menu_search); + final MenuItem sort = menu.findItem(R.id.menu_sort); + final MenuItem sortSize = menu.findItem(R.id.menu_sort_size); + final MenuItem grid = menu.findItem(R.id.menu_grid); + final MenuItem list = menu.findItem(R.id.menu_list); + final MenuItem advanced = menu.findItem(R.id.menu_advanced); + final MenuItem fileSize = menu.findItem(R.id.menu_file_size); + + sort.setVisible(cwd != null); + grid.setVisible(mState.derivedMode != State.MODE_GRID); + list.setVisible(mState.derivedMode != State.MODE_LIST); + + if (mState.currentSearch != null) { + // Search uses backend ranking; no sorting + sort.setVisible(false); + + search.expandActionView(); + + mSearchView.setIconified(false); + mSearchView.clearFocus(); + mSearchView.setQuery(mState.currentSearch, false); + } else { + mIgnoreNextClose = true; + mSearchView.setIconified(true); + mSearchView.clearFocus(); + + mIgnoreNextCollapse = true; + search.collapseActionView(); + } + + // Only sort by size when visible + sortSize.setVisible(mState.showSize); + + fileSize.setVisible(true); + search.setVisible(true); + createDir.setVisible(true); + advanced.setVisible(true); + + advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this) + ? R.string.menu_advanced_hide : R.string.menu_advanced_show); + fileSize.setTitle(LocalPreferences.getDisplayFileSize(this) + ? R.string.menu_file_size_hide : R.string.menu_file_size_show); + + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) { + return true; + } + + final int id = item.getItemId(); + if (id == android.R.id.home) { + onBackPressed(); + return true; + } else if (id == R.id.menu_create_dir) { + CreateDirectoryFragment.show(getFragmentManager()); + return true; + } else if (id == R.id.menu_search) { + return false; + } else if (id == R.id.menu_sort_name) { + setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME); + return true; + } else if (id == R.id.menu_sort_date) { + setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED); + return true; + } else if (id == R.id.menu_sort_size) { + setUserSortOrder(State.SORT_ORDER_SIZE); + return true; + } else if (id == R.id.menu_grid) { + setUserMode(State.MODE_GRID); + return true; + } else if (id == R.id.menu_list) { + setUserMode(State.MODE_LIST); + return true; + } else if (id == R.id.menu_advanced) { + setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this)); + return true; + } else if (id == R.id.menu_file_size) { + setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this)); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + private void setDisplayAdvancedDevices(boolean display) { + LocalPreferences.setDisplayAdvancedDevices(this, display); + mState.showAdvanced = mState.forceAdvanced | display; + RootsFragment.get(getFragmentManager()).onDisplayStateChanged(); + invalidateOptionsMenu(); + } + + private void setDisplayFileSize(boolean display) { + LocalPreferences.setDisplayFileSize(this, display); + mState.showSize = display; + DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged(); + invalidateOptionsMenu(); + } + + @Override + public void onStateChanged() { + invalidateOptionsMenu(); + } + + /** + * Set state sort order based on explicit user action. + */ + private void setUserSortOrder(int sortOrder) { + mState.userSortOrder = sortOrder; + DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged(); + } + + /** + * Set state mode based on explicit user action. + */ + private void setUserMode(int mode) { + mState.userMode = mode; + DirectoryFragment.get(getFragmentManager()).onUserModeChanged(); + } + + @Override + public void setPending(boolean pending) { + final SaveFragment save = SaveFragment.get(getFragmentManager()); + if (save != null) { + save.setPending(pending); + } + } + + @Override + public void onBackPressed() { + if (!mState.stackTouched) { + super.onBackPressed(); + return; + } + + final int size = mState.stack.size(); + if (size > 1) { + mState.stack.pop(); + onCurrentDirectoryChanged(ANIM_UP); + } else { + super.onBackPressed(); + } + } + + @Override + protected void onSaveInstanceState(Bundle state) { + super.onSaveInstanceState(state); + state.putParcelable(EXTRA_STATE, mState); + } + + @Override + protected void onRestoreInstanceState(Bundle state) { + super.onRestoreInstanceState(state); + } + + private BaseAdapter mStackAdapter = new BaseAdapter() { + @Override + public int getCount() { + return mState.stack.size(); + } + + @Override + public DocumentInfo getItem(int position) { + return mState.stack.get(mState.stack.size() - position - 1); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_subdir_title, parent, false); + } + + final TextView title = (TextView) convertView.findViewById(android.R.id.title); + final DocumentInfo doc = getItem(position); + + if (position == 0) { + final RootInfo root = getCurrentRoot(); + title.setText(root.title); + } else { + title.setText(doc.displayName); + } + + return convertView; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_subdir, parent, false); + } + + final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir); + final TextView title = (TextView) convertView.findViewById(android.R.id.title); + final DocumentInfo doc = getItem(position); + + if (position == 0) { + final RootInfo root = getCurrentRoot(); + title.setText(root.title); + subdir.setVisibility(View.GONE); + } else { + title.setText(doc.displayName); + subdir.setVisibility(View.VISIBLE); + } + + return convertView; + } + }; + + private OnItemSelectedListener mStackListener = new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + if (mIgnoreNextNavigation) { + mIgnoreNextNavigation = false; + return; + } + + while (mState.stack.size() > position + 1) { + mState.stackTouched = true; + mState.stack.pop(); + } + onCurrentDirectoryChanged(ANIM_UP); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // Ignored + } + }; + + @Override + public RootInfo getCurrentRoot() { + if (mState.stack.root != null) { + return mState.stack.root; + } else { + return mRoots.getRecentsRoot(); + } + } + + public DocumentInfo getCurrentDirectory() { + return mState.stack.peek(); + } + + private String getCallingPackageMaybeExtra() { + final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME); + return (extra != null) ? extra : getCallingPackage(); + } + + public Executor getCurrentExecutor() { + final DocumentInfo cwd = getCurrentDirectory(); + if (cwd != null && cwd.authority != null) { + return ProviderExecutor.forAuthority(cwd.authority); + } else { + return AsyncTask.THREAD_POOL_EXECUTOR; + } + } + + @Override + public State getDisplayState() { + return mState; + } + + private void onCurrentDirectoryChanged(int anim) { + final FragmentManager fm = getFragmentManager(); + final RootInfo root = getCurrentRoot(); + final DocumentInfo cwd = getCurrentDirectory(); + + mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN); + + if (cwd == null) { + DirectoryFragment.showRecentsOpen(fm, anim); + + // Start recents in grid when requesting visual things + final boolean visualMimes = MimePredicate.mimeMatches( + MimePredicate.VISUAL_MIMES, mState.acceptMimes); + mState.userMode = visualMimes ? State.MODE_GRID : State.MODE_LIST; + mState.derivedMode = mState.userMode; + } else { + if (mState.currentSearch != null) { + // Ongoing search + DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim); + } else { + // Normal boring directory + DirectoryFragment.showNormal(fm, root, cwd, anim); + } + } + + final RootsFragment roots = RootsFragment.get(fm); + if (roots != null) { + roots.onCurrentRootChanged(); + } + + updateActionBar(); + invalidateOptionsMenu(); + dumpStack(); + } + + @Override + public void onStackPicked(DocumentStack stack) { + try { + // Update the restored stack to ensure we have freshest data + stack.updateDocuments(getContentResolver()); + + mState.stack = stack; + mState.stackTouched = true; + onCurrentDirectoryChanged(ANIM_SIDE); + + } catch (FileNotFoundException e) { + Log.w(TAG, "Failed to restore stack: " + e); + } + } + + @Override + public void onRootPicked(RootInfo root, boolean closeDrawer) { + // Clear entire backstack and start in new root + mState.stack.root = root; + mState.stack.clear(); + mState.stackTouched = true; + + if (!mRoots.isRecentsRoot(root)) { + new PickRootTask(root).executeOnExecutor(getCurrentExecutor()); + } else { + onCurrentDirectoryChanged(ANIM_SIDE); + } + } + + private class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> { + private RootInfo mRoot; + + public PickRootTask(RootInfo root) { + mRoot = root; + } + + @Override + protected DocumentInfo doInBackground(Void... params) { + try { + final Uri uri = DocumentsContract.buildDocumentUri( + mRoot.authority, mRoot.documentId); + return DocumentInfo.fromUri(getContentResolver(), uri); + } catch (FileNotFoundException e) { + Log.w(TAG, "Failed to find root", e); + return null; + } + } + + @Override + protected void onPostExecute(DocumentInfo result) { + if (result != null) { + mState.stack.push(result); + mState.stackTouched = true; + onCurrentDirectoryChanged(ANIM_SIDE); + } + } + } + + @Override + public void onAppPicked(ResolveInfo info) { + final Intent intent = new Intent(getIntent()); + intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT); + intent.setComponent(new ComponentName( + info.activityInfo.applicationInfo.packageName, info.activityInfo.name)); + startActivityForResult(intent, CODE_FORWARD); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.d(TAG, "onActivityResult() code=" + resultCode); + + // Only relay back results when not canceled; otherwise stick around to + // let the user pick another app/backend. + if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) { + + // Remember that we last picked via external app + final String packageName = getCallingPackageMaybeExtra(); + final ContentValues values = new ContentValues(); + values.put(ResumeColumns.EXTERNAL, 1); + getContentResolver().insert(RecentsProvider.buildResume(packageName), values); + + // Pass back result to original caller + setResult(resultCode, data); + finish(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onDocumentPicked(DocumentInfo doc) { + final FragmentManager fm = getFragmentManager(); + if (doc.isDirectory()) { + mState.stack.push(doc); + mState.stackTouched = true; + onCurrentDirectoryChanged(ANIM_DOWN); + } else { + // Fall back to viewing + final Intent view = new Intent(Intent.ACTION_VIEW); + view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + view.setData(doc.derivedUri); + + try { + startActivity(view); + } catch (ActivityNotFoundException ex2) { + Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show(); + } + } + } + + public void onDocumentsPicked(List<DocumentInfo> docs) { + // TODO + } + + @Override + public void onSaveRequested(DocumentInfo replaceTarget) { + new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getCurrentExecutor()); + } + + @Override + public void onSaveRequested(String mimeType, String displayName) { + new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor()); + } + + @Override + public void onPickRequested(DocumentInfo pickTarget) { + final Uri viaUri = DocumentsContract.buildTreeDocumentUri(pickTarget.authority, + pickTarget.documentId); + new PickFinishTask(viaUri).executeOnExecutor(getCurrentExecutor()); + } + + private void saveStackBlocking() { + final ContentResolver resolver = getContentResolver(); + final ContentValues values = new ContentValues(); + + final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack); + + // Remember location for next app launch + final String packageName = getCallingPackageMaybeExtra(); + values.clear(); + values.put(ResumeColumns.STACK, rawStack); + values.put(ResumeColumns.EXTERNAL, 0); + resolver.insert(RecentsProvider.buildResume(packageName), values); + } + + private void onFinished(Uri... uris) { + Log.d(TAG, "onFinished() " + Arrays.toString(uris)); + + final Intent intent = new Intent(); + if (uris.length == 1) { + intent.setData(uris[0]); + } else if (uris.length > 1) { + final ClipData clipData = new ClipData( + null, mState.acceptMimes, new ClipData.Item(uris[0])); + for (int i = 1; i < uris.length; i++) { + clipData.addItem(new ClipData.Item(uris[i])); + } + intent.setClipData(clipData); + } + + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + + setResult(Activity.RESULT_OK, intent); + finish(); + } + + private class CreateFinishTask extends AsyncTask<Void, Void, Uri> { + private final String mMimeType; + private final String mDisplayName; + + public CreateFinishTask(String mimeType, String displayName) { + mMimeType = mimeType; + mDisplayName = displayName; + } + + @Override + protected void onPreExecute() { + setPending(true); + } + + @Override + protected Uri doInBackground(Void... params) { + final ContentResolver resolver = getContentResolver(); + final DocumentInfo cwd = getCurrentDirectory(); + + ContentProviderClient client = null; + Uri childUri = null; + try { + client = DocumentsApplication.acquireUnstableProviderOrThrow( + resolver, cwd.derivedUri.getAuthority()); + childUri = DocumentsContract.createDocument( + client, cwd.derivedUri, mMimeType, mDisplayName); + } catch (Exception e) { + Log.w(TAG, "Failed to create document", e); + } finally { + ContentProviderClient.releaseQuietly(client); + } + + if (childUri != null) { + saveStackBlocking(); + } + + return childUri; + } + + @Override + protected void onPostExecute(Uri result) { + if (result != null) { + onFinished(result); + } else { + Toast.makeText(StandaloneActivity.this, R.string.save_error, Toast.LENGTH_SHORT) + .show(); + } + + setPending(false); + } + } + + private class ExistingFinishTask extends AsyncTask<Void, Void, Void> { + private final Uri[] mUris; + + public ExistingFinishTask(Uri... uris) { + mUris = uris; + } + + @Override + protected Void doInBackground(Void... params) { + saveStackBlocking(); + return null; + } + + @Override + protected void onPostExecute(Void result) { + onFinished(mUris); + } + } + + private class PickFinishTask extends AsyncTask<Void, Void, Void> { + private final Uri mUri; + + public PickFinishTask(Uri uri) { + mUri = uri; + } + + @Override + protected Void doInBackground(Void... params) { + saveStackBlocking(); + return null; + } + + @Override + protected void onPostExecute(Void result) { + onFinished(mUri); + } + } + + private void dumpStack() { + Log.d(TAG, "Current stack: "); + Log.d(TAG, " * " + mState.stack.root); + for (DocumentInfo doc : mState.stack) { + Log.d(TAG, " +-- " + doc); + } + } + + public static BaseActivity get(Fragment fragment) { + return (BaseActivity) fragment.getActivity(); + } +} |