diff options
Diffstat (limited to 'packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java')
| -rw-r--r-- | packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java | 537 |
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; + } + } } |
