summaryrefslogtreecommitdiffstats
path: root/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java')
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java537
1 files changed, 525 insertions, 12 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 8039b71..efe71d4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -16,17 +16,47 @@
package com.android.documentsui;
+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 java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.Executor;
+import libcore.io.IoUtils;
import android.app.Activity;
import android.app.Fragment;
-import android.content.pm.ResolveInfo;
+import android.content.Intent;
+import android.database.Cursor;
+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.util.Log;
import android.util.SparseArray;
+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.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.TextView;
+import com.android.documentsui.RecentsProvider.ResumeColumns;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DurableUtils;
@@ -34,20 +64,125 @@ import com.android.documentsui.model.RootInfo;
import com.google.common.collect.Maps;
abstract class BaseActivity extends Activity {
+
+ static final String EXTRA_STATE = "state";
+
+ private final String mTag;
+ RootsCache mRoots;
+
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);
+ abstract void onTaskFinished(Uri... uris);
+ abstract void onDirectoryChanged(int anim);
+ abstract void updateActionBar();
+ abstract void saveStackBlocking();
+
+ public BaseActivity(String tag) {
+ mTag = tag;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mRoots = DocumentsApplication.getRootsCache(this);
+ }
+
+ void onStackRestored(boolean restored, boolean external) {}
+
+ void onRootPicked(RootInfo root) {
+ State state = getDisplayState();
+
+ // Clear entire backstack and start in new root
+ state.stack.root = root;
+ state.stack.clear();
+ state.stackTouched = true;
+
+ if (!mRoots.isRecentsRoot(root)) {
+ new PickRootTask(root).executeOnExecutor(getCurrentExecutor());
+ } else {
+ onCurrentDirectoryChanged(ANIM_SIDE);
+ }
+ }
+
+ void expandMenus(Menu 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);
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ 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 if (id == R.id.menu_settings) {
+ final RootInfo root = getCurrentRoot();
+ final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
+ intent.setDataAndType(DocumentsContract.buildRootUri(root.authority, root.rootId),
+ DocumentsContract.Root.MIME_TYPE_ITEM);
+ startActivity(intent);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Call this when directory changes. Prior to root fragment update
+ * the (abstract) directoryChanged method will be called.
+ * @param anim
+ */
+ final void onCurrentDirectoryChanged(int anim) {
+ onDirectoryChanged(anim);
+
+ final RootsFragment roots = RootsFragment.get(getFragmentManager());
+ if (roots != null) {
+ roots.onCurrentRootChanged();
+ }
+
+ updateActionBar();
+ invalidateOptionsMenu();
+ }
+
+ final String getCallingPackageMaybeExtra() {
+ final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
+ return (extra != null) ? extra : getCallingPackage();
+ }
public static BaseActivity get(Fragment fragment) {
return (BaseActivity) fragment.getActivity();
@@ -169,4 +304,382 @@ abstract class BaseActivity extends Activity {
}
};
}
+
+ void setDisplayAdvancedDevices(boolean display) {
+ State state = getDisplayState();
+ LocalPreferences.setDisplayAdvancedDevices(this, display);
+ state.showAdvanced = state.forceAdvanced | display;
+ RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
+ invalidateOptionsMenu();
+ }
+
+ void setDisplayFileSize(boolean display) {
+ LocalPreferences.setDisplayFileSize(this, display);
+ getDisplayState().showSize = display;
+ DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
+ invalidateOptionsMenu();
+ }
+
+ void onStateChanged() {
+ invalidateOptionsMenu();
+ }
+
+ /**
+ * Set state sort order based on explicit user action.
+ */
+ void setUserSortOrder(int sortOrder) {
+ getDisplayState().userSortOrder = sortOrder;
+ DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
+ }
+
+ /**
+ * Set state mode based on explicit user action.
+ */
+ void setUserMode(int mode) {
+ getDisplayState().userMode = mode;
+ DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
+ }
+
+ void setPending(boolean pending) {
+ final SaveFragment save = SaveFragment.get(getFragmentManager());
+ if (save != null) {
+ save.setPending(pending);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle state) {
+ super.onSaveInstanceState(state);
+ state.putParcelable(EXTRA_STATE, getDisplayState());
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle state) {
+ super.onRestoreInstanceState(state);
+ }
+
+ RootInfo getCurrentRoot() {
+ State state = getDisplayState();
+ if (state.stack.root != null) {
+ return state.stack.root;
+ } else {
+ return mRoots.getRecentsRoot();
+ }
+ }
+
+ public DocumentInfo getCurrentDirectory() {
+ return getDisplayState().stack.peek();
+ }
+
+ public Executor getCurrentExecutor() {
+ final DocumentInfo cwd = getCurrentDirectory();
+ if (cwd != null && cwd.authority != null) {
+ return ProviderExecutor.forAuthority(cwd.authority);
+ } else {
+ return AsyncTask.THREAD_POOL_EXECUTOR;
+ }
+ }
+
+ public void onStackPicked(DocumentStack stack) {
+ try {
+ // Update the restored stack to ensure we have freshest data
+ stack.updateDocuments(getContentResolver());
+
+ State state = getDisplayState();
+ state.stack = stack;
+ state.stackTouched = true;
+ onCurrentDirectoryChanged(ANIM_SIDE);
+
+ } catch (FileNotFoundException e) {
+ Log.w(mTag, "Failed to restore stack: " + e);
+ }
+ }
+
+ final 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(mTag, "Failed to find root", e);
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(DocumentInfo result) {
+ if (result != null) {
+ State state = getDisplayState();
+ state.stack.push(result);
+ state.stackTouched = true;
+ onCurrentDirectoryChanged(ANIM_SIDE);
+ }
+ }
+ }
+
+ final class RestoreStackTask extends AsyncTask<Void, Void, Void> {
+ private volatile boolean mRestoredStack;
+ private volatile boolean mExternal;
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ State state = getDisplayState();
+ RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);
+
+ // 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, state.stack);
+ mRestoredStack = true;
+ }
+ } catch (IOException e) {
+ Log.w(mTag, "Failed to resume: " + e);
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+
+ if (mRestoredStack) {
+ // Update the restored stack to ensure we have freshest data
+ final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(state);
+ try {
+ state.stack.updateRoot(matchingRoots);
+ state.stack.updateDocuments(getContentResolver());
+ } catch (FileNotFoundException e) {
+ Log.w(mTag, "Failed to restore stack: " + e);
+ state.stack.reset();
+ mRestoredStack = false;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (isDestroyed()) return;
+ getDisplayState().restored = true;
+ onCurrentDirectoryChanged(ANIM_NONE);
+
+ onStackRestored(mRestoredStack, mExternal);
+
+ getDisplayState().restored = true;
+ onCurrentDirectoryChanged(ANIM_NONE);
+ }
+ }
+
+ final class ItemSelectedListener implements OnItemSelectedListener {
+
+ boolean mIgnoreNextNavigation;
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ if (mIgnoreNextNavigation) {
+ mIgnoreNextNavigation = false;
+ return;
+ }
+
+ State state = getDisplayState();
+ while (state.stack.size() > position + 1) {
+ state.stackTouched = true;
+ state.stack.pop();
+ }
+ onCurrentDirectoryChanged(ANIM_UP);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Ignored
+ }
+ }
+
+ /**
+ * Class providing toolbar with runtime access to useful activity data.
+ */
+ final class StackAdapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return getDisplayState().stack.size();
+ }
+
+ @Override
+ public DocumentInfo getItem(int position) {
+ State state = getDisplayState();
+ return state.stack.get(state.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;
+ }
+ }
+
+ /**
+ * Facade over the various search parts in the menu.
+ */
+ final class SearchManager implements
+ SearchView.OnCloseListener, OnActionExpandListener, OnQueryTextListener {
+
+ protected boolean mSearchExpanded;
+ protected boolean mIgnoreNextClose;
+ protected boolean mIgnoreNextCollapse;
+
+ private MenuItem mMenu;
+ private SearchView mView;
+
+ public void install(MenuItem menu) {
+ assert(mMenu == null);
+ mMenu = menu;
+ mView = (SearchView) menu.getActionView();
+
+ mMenu.setOnActionExpandListener(this);
+ mView.setOnQueryTextListener(this);
+ mView.setOnCloseListener(this);
+ }
+
+ /**
+ * @param root Info about the current directory.
+ */
+ void update(RootInfo root) {
+ if (mMenu == null) {
+ Log.d(mTag, "showMenu called before Search MenuItem installed.");
+ return;
+ }
+ State state = getDisplayState();
+ if (state.currentSearch != null) {
+ mMenu.expandActionView();
+
+ mView.setIconified(false);
+ mView.clearFocus();
+ mView.setQuery(state.currentSearch, false);
+ } else {
+ mIgnoreNextClose = true;
+ mView.setIconified(true);
+ mView.clearFocus();
+
+ mIgnoreNextCollapse = true;
+ mMenu.collapseActionView();
+ }
+
+ showMenu(root != null
+ && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0));
+ }
+
+ void showMenu(boolean visible) {
+ if (mMenu == null) {
+ Log.d(mTag, "showMenu called before Search MenuItem installed.");
+ return;
+ }
+ mMenu.setVisible(visible);
+ }
+
+ boolean isSearching() {
+ return getDisplayState().currentSearch != null;
+ }
+
+ boolean isExpanded() {
+ return mSearchExpanded;
+ }
+
+ @Override
+ public boolean onClose() {
+ mSearchExpanded = false;
+ if (mIgnoreNextClose) {
+ mIgnoreNextClose = false;
+ return false;
+ }
+
+ getDisplayState().currentSearch = null;
+ onCurrentDirectoryChanged(ANIM_NONE);
+ return false;
+ }
+ @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;
+ }
+
+ getDisplayState().currentSearch = null;
+ onCurrentDirectoryChanged(ANIM_NONE);
+ return true;
+ }
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ mSearchExpanded = true;
+ getDisplayState().currentSearch = query;
+ mView.clearFocus();
+ onCurrentDirectoryChanged(ANIM_NONE);
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ return false;
+ }
+ }
}