diff options
Diffstat (limited to 'packages/DocumentsUI/src/com/android/documentsui')
10 files changed, 482 insertions, 192 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 79f846a..549e196 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -17,9 +17,9 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE; -import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID; -import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST; +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.model.DocumentInfo.getCursorInt; import static com.android.documentsui.model.DocumentInfo.getCursorLong; import static com.android.documentsui.model.DocumentInfo.getCursorString; @@ -62,7 +62,7 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; -import com.android.documentsui.DocumentsActivity.DisplayState; +import com.android.documentsui.DocumentsActivity.State; import com.android.documentsui.model.DocumentInfo; import com.android.internal.util.Predicate; import com.google.android.collect.Lists; @@ -168,7 +168,7 @@ public class DirectoryFragment extends Fragment { mCallbacks = new LoaderCallbacks<DirectoryResult>() { @Override public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) { - final DisplayState state = getDisplayState(DirectoryFragment.this); + final State state = getDisplayState(DirectoryFragment.this); Uri contentsUri; if (mType == TYPE_NORMAL) { @@ -196,7 +196,7 @@ public class DirectoryFragment extends Fragment { } public void updateDisplayState() { - final DisplayState state = getDisplayState(this); + final State state = getDisplayState(this); if (mLastSortOrder != state.sortOrder) { getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); @@ -263,7 +263,7 @@ public class DirectoryFragment extends Fragment { @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - final DisplayState state = getDisplayState(DirectoryFragment.this); + final State state = getDisplayState(DirectoryFragment.this); final MenuItem open = menu.findItem(R.id.menu_open); final MenuItem share = menu.findItem(R.id.menu_share); @@ -294,14 +294,17 @@ public class DirectoryFragment extends Fragment { final int id = item.getItemId(); if (id == R.id.menu_open) { DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); + mode.finish(); return true; } else if (id == R.id.menu_share) { onShareDocuments(docs); + mode.finish(); return true; } else if (id == R.id.menu_delete) { onDeleteDocuments(docs); + mode.finish(); return true; } else { @@ -332,26 +335,36 @@ public class DirectoryFragment extends Fragment { }; private void onShareDocuments(List<DocumentInfo> docs) { - final ArrayList<Uri> uris = Lists.newArrayList(); - for (DocumentInfo doc : docs) { - uris.add(doc.uri); - } + Intent intent; + if (docs.size() == 1) { + final DocumentInfo doc = docs.get(0); + + intent = new Intent(Intent.ACTION_SEND); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setType(doc.mimeType); + intent.putExtra(Intent.EXTRA_STREAM, doc.uri); - final Intent intent; - if (uris.size() > 1) { + } else if (docs.size() > 1) { intent = new Intent(Intent.ACTION_SEND_MULTIPLE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addCategory(Intent.CATEGORY_DEFAULT); - // TODO: find common mimetype - intent.setType("*/*"); + + final ArrayList<String> mimeTypes = Lists.newArrayList(); + final ArrayList<Uri> uris = Lists.newArrayList(); + for (DocumentInfo doc : docs) { + mimeTypes.add(doc.mimeType); + uris.add(doc.uri); + } + + intent.setType(findCommonMimeType(mimeTypes)); intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + } else { - intent = new Intent(Intent.ACTION_SEND); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setData(uris.get(0)); + return; } + intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via)); startActivity(intent); } @@ -383,7 +396,7 @@ public class DirectoryFragment extends Fragment { } } - private static DisplayState getDisplayState(Fragment fragment) { + private static State getDisplayState(Fragment fragment) { return ((DocumentsActivity) fragment.getActivity()).getDisplayState(); } @@ -411,7 +424,7 @@ public class DirectoryFragment extends Fragment { @Override public View getView(int position, View convertView, ViewGroup parent) { final Context context = parent.getContext(); - final DisplayState state = getDisplayState(DirectoryFragment.this); + final State state = getDisplayState(DirectoryFragment.this); final RootsCache roots = DocumentsApplication.getRootsCache(context); final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( @@ -586,4 +599,28 @@ public class DirectoryFragment extends Fragment { return DateUtils.formatDateTime(context, when, flags); } + + private String findCommonMimeType(List<String> mimeTypes) { + String[] commonType = mimeTypes.get(0).split("/"); + if (commonType.length != 2) { + return "*/*"; + } + + for (int i = 1; i < mimeTypes.size(); i++) { + String[] type = mimeTypes.get(i).split("/"); + if (type.length != 2) continue; + + if (!commonType[1].equals(type[1])) { + commonType[1] = "*"; + } + + if (!commonType[0].equals(type[0])) { + commonType[0] = "*"; + commonType[1] = "*"; + break; + } + } + + return commonType[0] + "/" + commonType[1]; + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index b2be11b..fa674d5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -16,6 +16,10 @@ 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 android.content.AsyncTaskLoader; import android.content.ContentProviderClient; import android.content.Context; @@ -25,8 +29,6 @@ import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.provider.DocumentsContract.Document; -import com.android.documentsui.DocumentsActivity.DisplayState; - import libcore.io.IoUtils; class DirectoryResult implements AutoCloseable { @@ -149,11 +151,11 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { private String getQuerySortOrder() { switch (mSortOrder) { - case DisplayState.SORT_ORDER_DISPLAY_NAME: + case SORT_ORDER_DISPLAY_NAME: return Document.COLUMN_DISPLAY_NAME + " ASC"; - case DisplayState.SORT_ORDER_LAST_MODIFIED: + case SORT_ORDER_LAST_MODIFIED: return Document.COLUMN_LAST_MODIFIED + " DESC"; - case DisplayState.SORT_ORDER_SIZE: + case SORT_ORDER_SIZE: return Document.COLUMN_SIZE + " DESC"; default: return null; diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 912d6dc..da790cc 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -16,13 +16,13 @@ package com.android.documentsui; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_CREATE; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_GET_CONTENT; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE; -import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_OPEN; -import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID; -import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST; -import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED; +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.MODE_GRID; +import static com.android.documentsui.DocumentsActivity.State.MODE_LIST; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; import android.app.ActionBar; import android.app.ActionBar.OnNavigationListener; @@ -41,6 +41,7 @@ import android.database.Cursor; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; +import android.os.Parcel; import android.provider.DocumentsContract; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.view.GravityCompat; @@ -61,31 +62,27 @@ import android.widget.Toast; 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 java.io.FileNotFoundException; +import java.io.IOException; import java.util.Arrays; import java.util.List; public class DocumentsActivity extends Activity { public static final String TAG = "Documents"; - private int mAction; - private SearchView mSearchView; private View mRootsContainer; private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; - private final DisplayState mDisplayState = new DisplayState(); + private static final String EXTRA_STATE = "state"; private RootsCache mRoots; - - /** Current user navigation stack; empty implies recents. */ - private DocumentStack mStack = new DocumentStack(); - /** Currently active search, overriding any stack. */ - private String mCurrentSearch; + private State mState; @Override public void onCreate(Bundle icicle) { @@ -93,72 +90,83 @@ public class DocumentsActivity extends Activity { mRoots = DocumentsApplication.getRootsCache(this); - final Intent intent = getIntent(); - final String action = intent.getAction(); - if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { - mAction = ACTION_OPEN; - } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { - mAction = ACTION_CREATE; - } else if (Intent.ACTION_GET_CONTENT.equals(action)) { - mAction = ACTION_GET_CONTENT; - } else if (DocumentsContract.ACTION_MANAGE_DOCUMENTS.equals(action)) { - mAction = ACTION_MANAGE; - } + setResult(Activity.RESULT_CANCELED); + setContentView(R.layout.activity); - // TODO: unify action in single place - mDisplayState.action = mAction; + mRootsContainer = findViewById(R.id.container_roots); - if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { - mDisplayState.allowMultiple = intent.getBooleanExtra( - Intent.EXTRA_ALLOW_MULTIPLE, false); - } + mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - if (mAction == ACTION_MANAGE) { - mDisplayState.acceptMimes = new String[] { "*/*" }; - mDisplayState.allowMultiple = true; - } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { - mDisplayState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); + mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, + R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close); + + mDrawerLayout.setDrawerListener(mDrawerListener); + mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + + if (icicle != null) { + mState = icicle.getParcelable(EXTRA_STATE); } else { - mDisplayState.acceptMimes = new String[] { intent.getType() }; + buildDefaultState(); } - mDisplayState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); - - setResult(Activity.RESULT_CANCELED); - setContentView(R.layout.activity); + if (mState.action == ACTION_MANAGE) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + } - if (mAction == ACTION_CREATE) { + if (mState.action == ACTION_CREATE) { final String mimeType = getIntent().getType(); final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE); SaveFragment.show(getFragmentManager(), mimeType, title); } - if (mAction == ACTION_GET_CONTENT) { + if (mState.action == ACTION_GET_CONTENT) { final Intent moreApps = new Intent(getIntent()); moreApps.setComponent(null); moreApps.setPackage(null); RootsFragment.show(getFragmentManager(), moreApps); - } else if (mAction == ACTION_OPEN || mAction == ACTION_CREATE) { + } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE) { RootsFragment.show(getFragmentManager(), null); } - if (mAction == ACTION_MANAGE) { - mDisplayState.sortOrder = SORT_ORDER_LAST_MODIFIED; - } + onCurrentDirectoryChanged(); + } - mRootsContainer = findViewById(R.id.container_roots); + private void buildDefaultState() { + mState = new State(); - mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + final Intent intent = getIntent(); + final String action = intent.getAction(); + if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { + mState.action = ACTION_OPEN; + } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { + mState.action = ACTION_CREATE; + } else if (Intent.ACTION_GET_CONTENT.equals(action)) { + mState.action = ACTION_GET_CONTENT; + } else if (DocumentsContract.ACTION_MANAGE_DOCUMENTS.equals(action)) { + mState.action = ACTION_MANAGE; + } - mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, - R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close); + if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { + mState.allowMultiple = intent.getBooleanExtra( + Intent.EXTRA_ALLOW_MULTIPLE, false); + } - mDrawerLayout.setDrawerListener(mDrawerListener); - mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + if (mState.action == ACTION_MANAGE) { + mState.acceptMimes = new String[] { "*/*" }; + mState.allowMultiple = true; + } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { + mState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); + } else { + mState.acceptMimes = new String[] { intent.getType() }; + } - if (mAction == ACTION_MANAGE) { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); + if (mState.action == ACTION_MANAGE) { + mState.sortOrder = SORT_ORDER_LAST_MODIFIED; + } + + if (mState.action == ACTION_MANAGE) { final Uri rootUri = intent.getData(); final RootInfo root = mRoots.findRoot(rootUri); if (root != null) { @@ -169,8 +177,6 @@ public class DocumentsActivity extends Activity { } } else { - mDrawerLayout.openDrawer(mRootsContainer); - // Restore last stack for calling package // TODO: move into async loader final String packageName = getCallingPackage(); @@ -178,17 +184,17 @@ public class DocumentsActivity extends Activity { .query(RecentsProvider.buildResume(packageName), null, null, null, null); try { if (cursor.moveToFirst()) { - final String raw = cursor.getString( + final byte[] rawStack = cursor.getBlob( cursor.getColumnIndex(RecentsProvider.COL_PATH)); - mStack = DocumentStack.deserialize(getContentResolver(), raw); + DurableUtils.readFromArray(rawStack, mState.stack); } - } catch (FileNotFoundException e) { + } catch (IOException e) { Log.w(TAG, "Failed to resume", e); } finally { cursor.close(); } - onCurrentDirectoryChanged(); + mDrawerLayout.openDrawer(mRootsContainer); } } @@ -196,10 +202,10 @@ public class DocumentsActivity extends Activity { public void onStart() { super.onStart(); - if (mAction == ACTION_MANAGE) { - mDisplayState.showSize = true; + if (mState.action == ACTION_MANAGE) { + mState.showSize = true; } else { - mDisplayState.showSize = SettingsActivity.getDisplayFileSize(this); + mState.showSize = SettingsActivity.getDisplayFileSize(this); } } @@ -242,9 +248,9 @@ public class DocumentsActivity extends Activity { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); actionBar.setIcon(new ColorDrawable()); - if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { actionBar.setTitle(R.string.title_open); - } else if (mAction == ACTION_CREATE) { + } else if (mState.action == ACTION_CREATE) { actionBar.setTitle(R.string.title_save); } @@ -262,13 +268,13 @@ public class DocumentsActivity extends Activity { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); actionBar.setTitle(null); actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener); - actionBar.setSelectedNavigationItem(mDisplayState.sortOrder); + actionBar.setSelectedNavigationItem(mState.sortOrder); } - if (mStack.size() > 1) { + if (mState.stack.size() > 1) { actionBar.setDisplayHomeAsUpEnabled(true); mDrawerToggle.setDrawerIndicatorEnabled(false); - } else if (mAction == ACTION_MANAGE) { + } else if (mState.action == ACTION_MANAGE) { actionBar.setDisplayHomeAsUpEnabled(false); mDrawerToggle.setDrawerIndicatorEnabled(false); } else { @@ -288,7 +294,7 @@ public class DocumentsActivity extends Activity { mSearchView.setOnQueryTextListener(new OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - mCurrentSearch = query; + mState.currentSearch = query; onCurrentDirectoryChanged(); mSearchView.setIconified(true); return true; @@ -303,7 +309,7 @@ public class DocumentsActivity extends Activity { mSearchView.setOnCloseListener(new OnCloseListener() { @Override public boolean onClose() { - mCurrentSearch = null; + mState.currentSearch = null; onCurrentDirectoryChanged(); return false; } @@ -325,11 +331,11 @@ public class DocumentsActivity extends Activity { final MenuItem list = menu.findItem(R.id.menu_list); final MenuItem settings = menu.findItem(R.id.menu_settings); - grid.setVisible(mDisplayState.mode != MODE_GRID); - list.setVisible(mDisplayState.mode != MODE_LIST); + grid.setVisible(mState.mode != MODE_GRID); + list.setVisible(mState.mode != MODE_LIST); final boolean searchVisible; - if (mAction == ACTION_CREATE) { + if (mState.action == ACTION_CREATE) { createDir.setVisible(cwd != null && cwd.isCreateSupported()); searchVisible = false; @@ -348,7 +354,7 @@ public class DocumentsActivity extends Activity { // TODO: close any search in-progress when hiding search.setVisible(searchVisible); - settings.setVisible(mAction != ACTION_MANAGE); + settings.setVisible(mState.action != ACTION_MANAGE); return true; } @@ -370,13 +376,13 @@ public class DocumentsActivity extends Activity { return false; } else if (id == R.id.menu_grid) { // TODO: persist explicit user mode for cwd - mDisplayState.mode = MODE_GRID; + mState.mode = MODE_GRID; updateDisplayState(); invalidateOptionsMenu(); return true; } else if (id == R.id.menu_list) { // TODO: persist explicit user mode for cwd - mDisplayState.mode = MODE_LIST; + mState.mode = MODE_LIST; updateDisplayState(); invalidateOptionsMenu(); return true; @@ -390,9 +396,9 @@ public class DocumentsActivity extends Activity { @Override public void onBackPressed() { - final int size = mStack.size(); + final int size = mState.stack.size(); if (size > 1) { - mStack.pop(); + mState.stack.pop(); onCurrentDirectoryChanged(); } else if (size == 1 && !mDrawerLayout.isDrawerOpen(mRootsContainer)) { // TODO: open root drawer once we can capture back key @@ -402,11 +408,23 @@ public class DocumentsActivity extends Activity { } } + @Override + protected void onSaveInstanceState(Bundle state) { + super.onSaveInstanceState(state); + state.putParcelable(EXTRA_STATE, mState); + } + + @Override + protected void onRestoreInstanceState(Bundle state) { + super.onRestoreInstanceState(state); + updateActionBar(); + } + // TODO: support additional sort orders private BaseAdapter mSortAdapter = new BaseAdapter() { @Override public int getCount() { - return mDisplayState.showSize ? 3 : 2; + return mState.showSize ? 3 : 2; } @Override @@ -438,8 +456,8 @@ public class DocumentsActivity extends Activity { final TextView title = (TextView) convertView.findViewById(android.R.id.title); final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); - if (mStack.size() > 0) { - title.setText(mStack.getTitle(mRoots)); + if (mState.stack.size() > 0) { + title.setText(mState.stack.getTitle(mRoots)); } else { // No directory means recents title.setText(R.string.root_recent); @@ -467,26 +485,26 @@ public class DocumentsActivity extends Activity { private OnNavigationListener mSortListener = new OnNavigationListener() { @Override public boolean onNavigationItemSelected(int itemPosition, long itemId) { - mDisplayState.sortOrder = itemPosition; + mState.sortOrder = itemPosition; updateDisplayState(); return true; } }; public RootInfo getCurrentRoot() { - if (mStack.size() > 0) { - return mStack.getRoot(mRoots); + if (mState.stack.size() > 0) { + return mState.stack.getRoot(mRoots); } else { return mRoots.getRecentsRoot(); } } public DocumentInfo getCurrentDirectory() { - return mStack.peek(); + return mState.stack.peek(); } - public DisplayState getDisplayState() { - return mDisplayState; + public State getDisplayState() { + return mState; } private void onCurrentDirectoryChanged() { @@ -495,15 +513,15 @@ public class DocumentsActivity extends Activity { if (cwd == null) { // No directory means recents - if (mAction == ACTION_CREATE) { + if (mState.action == ACTION_CREATE) { RecentsCreateFragment.show(fm); } else { DirectoryFragment.showRecentsOpen(fm); } } else { - if (mCurrentSearch != null) { + if (mState.currentSearch != null) { // Ongoing search - DirectoryFragment.showSearch(fm, cwd.uri, mCurrentSearch); + DirectoryFragment.showSearch(fm, cwd.uri, mState.currentSearch); } else { // Normal boring directory DirectoryFragment.showNormal(fm, cwd.uri); @@ -511,7 +529,7 @@ public class DocumentsActivity extends Activity { } // Forget any replacement target - if (mAction == ACTION_CREATE) { + if (mState.action == ACTION_CREATE) { final SaveFragment save = SaveFragment.get(fm); if (save != null) { save.setReplaceTarget(null); @@ -529,13 +547,13 @@ public class DocumentsActivity extends Activity { } public void onStackPicked(DocumentStack stack) { - mStack = stack; + mState.stack = stack; onCurrentDirectoryChanged(); } public void onRootPicked(RootInfo root, boolean closeDrawer) { // Clear entire backstack and start in new root - mStack.clear(); + mState.stack.clear(); if (!mRoots.isRecentsRoot(root)) { try { @@ -566,19 +584,19 @@ public class DocumentsActivity extends Activity { if (doc.isDirectory()) { // TODO: query display mode user preference for this dir if (doc.isGridPreferred()) { - mDisplayState.mode = MODE_GRID; + mState.mode = MODE_GRID; } else { - mDisplayState.mode = MODE_LIST; + mState.mode = MODE_LIST; } - mStack.push(doc); + mState.stack.push(doc); onCurrentDirectoryChanged(); - } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { // Explicit file picked, return onFinished(doc.uri); - } else if (mAction == ACTION_CREATE) { + } else if (mState.action == ACTION_CREATE) { // Replace selected file SaveFragment.get(fm).setReplaceTarget(doc); - } else if (mAction == ACTION_MANAGE) { + } else if (mState.action == ACTION_MANAGE) { // Open the document final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -592,7 +610,7 @@ public class DocumentsActivity extends Activity { } public void onDocumentsPicked(List<DocumentInfo> docs) { - if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { final int size = docs.size(); final Uri[] uris = new Uri[size]; for (int i = 0; i < size; i++) { @@ -629,14 +647,14 @@ public class DocumentsActivity extends Activity { final ContentResolver resolver = getContentResolver(); final ContentValues values = new ContentValues(); - final String rawStack = DocumentStack.serialize(mStack); - if (mAction == ACTION_CREATE) { + final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack); + if (mState.action == ACTION_CREATE) { // Remember stack for last create values.clear(); values.put(RecentsProvider.COL_PATH, rawStack); resolver.insert(RecentsProvider.buildRecentCreate(), values); - } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { + } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { // Remember opened items for (Uri uri : uris) { values.clear(); @@ -656,14 +674,14 @@ public class DocumentsActivity extends Activity { intent.setData(uris[0]); } else if (uris.length > 1) { final ClipData clipData = new ClipData( - null, mDisplayState.acceptMimes, new ClipData.Item(uris[0])); + 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); } - if (mAction == ACTION_GET_CONTENT) { + if (mState.action == ACTION_GET_CONTENT) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } else { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -675,7 +693,7 @@ public class DocumentsActivity extends Activity { finish(); } - public static class DisplayState { + public static class State implements android.os.Parcelable { public int action; public int mode = MODE_LIST; public String[] acceptMimes; @@ -684,6 +702,11 @@ public class DocumentsActivity extends Activity { public boolean showSize = false; public boolean localOnly = false; + /** Current user navigation stack; empty implies recents. */ + public DocumentStack stack = new DocumentStack(); + /** Currently active search, overriding any stack. */ + public String currentSearch; + public static final int ACTION_OPEN = 1; public static final int ACTION_CREATE = 2; public static final int ACTION_GET_CONTENT = 3; @@ -695,11 +718,51 @@ public class DocumentsActivity extends Activity { public static final int SORT_ORDER_DISPLAY_NAME = 0; public static final int SORT_ORDER_LAST_MODIFIED = 1; public static final int SORT_ORDER_SIZE = 2; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(action); + out.writeInt(mode); + out.writeStringArray(acceptMimes); + out.writeInt(sortOrder); + out.writeInt(allowMultiple ? 1 : 0); + out.writeInt(showSize ? 1 : 0); + out.writeInt(localOnly ? 1 : 0); + DurableUtils.writeToParcel(out, stack); + out.writeString(currentSearch); + } + + 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.mode = in.readInt(); + state.acceptMimes = in.readStringArray(); + state.sortOrder = in.readInt(); + state.allowMultiple = in.readInt() != 0; + state.showSize = in.readInt() != 0; + state.localOnly = in.readInt() != 0; + DurableUtils.readFromParcel(in, state.stack); + state.currentSearch = in.readString(); + return state; + } + + @Override + public State[] newArray(int size) { + return new State[size]; + } + }; } private void dumpStack() { Log.d(TAG, "Current stack:"); - for (DocumentInfo doc : mStack) { + for (DocumentInfo doc : mState.stack) { Log.d(TAG, "--> " + doc); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index f5d87a7..fd7293d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -47,7 +47,9 @@ import com.google.android.collect.Lists; import libcore.io.IoUtils; -import java.io.FileNotFoundException; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -138,12 +140,13 @@ public class RecentsCreateFragment extends Fragment { uri, null, null, null, RecentsProvider.COL_TIMESTAMP + " DESC", signal); try { while (cursor != null && cursor.moveToNext()) { - final String rawStack = cursor.getString( + final byte[] raw = cursor.getBlob( cursor.getColumnIndex(RecentsProvider.COL_PATH)); try { - final DocumentStack stack = DocumentStack.deserialize(resolver, rawStack); + final DocumentStack stack = new DocumentStack(); + stack.read(new DataInputStream(new ByteArrayInputStream(raw))); result.add(stack); - } catch (FileNotFoundException e) { + } catch (IOException e) { Log.w(TAG, "Failed to resolve stack: " + e); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index 880a92b..f67c309 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -50,6 +50,8 @@ public class RootsCache { // TODO: cache roots in local provider to avoid spinning up backends // TODO: root updates should trigger UI refresh + private static final boolean RECENTS_ENABLED = false; + private final Context mContext; public List<RootInfo> mRoots = Lists.newArrayList(); @@ -68,7 +70,7 @@ public class RootsCache { public void update() { mRoots.clear(); - { + if (RECENTS_ENABLED) { // Create special root for recents final RootInfo root = new RootInfo(); root.rootType = Root.ROOT_TYPE_SHORTCUT; diff --git a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java index 2d73732..257c106 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java @@ -16,12 +16,14 @@ 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 android.database.AbstractCursor; import android.database.Cursor; import android.provider.DocumentsContract.Document; -import com.android.documentsui.DocumentsActivity.DisplayState; - /** * Cursor wrapper that presents a sorted view of the underlying cursor. Handles * common {@link Document} sorting modes, such as ordering directories first. @@ -39,12 +41,12 @@ public class SortingCursorWrapper extends AbstractCursor { final int count = cursor.getCount(); mPosition = new int[count]; switch (sortOrder) { - case DisplayState.SORT_ORDER_DISPLAY_NAME: + case SORT_ORDER_DISPLAY_NAME: mValueString = new String[count]; mValueLong = null; break; - case DisplayState.SORT_ORDER_LAST_MODIFIED: - case DisplayState.SORT_ORDER_SIZE: + case SORT_ORDER_LAST_MODIFIED: + case SORT_ORDER_SIZE: mValueString = null; mValueLong = new long[count]; break; @@ -63,7 +65,7 @@ public class SortingCursorWrapper extends AbstractCursor { mPosition[i] = i; switch (sortOrder) { - case DisplayState.SORT_ORDER_DISPLAY_NAME: + case SORT_ORDER_DISPLAY_NAME: final String mimeType = cursor.getString(mimeTypeIndex); final String displayName = cursor.getString(displayNameIndex); if (Document.MIME_TYPE_DIR.equals(mimeType)) { @@ -72,24 +74,24 @@ public class SortingCursorWrapper extends AbstractCursor { mValueString[i] = displayName; } break; - case DisplayState.SORT_ORDER_LAST_MODIFIED: + case SORT_ORDER_LAST_MODIFIED: mValueLong[i] = cursor.getLong(lastModifiedIndex); break; - case DisplayState.SORT_ORDER_SIZE: + case SORT_ORDER_SIZE: mValueLong[i] = cursor.getLong(sizeIndex); break; } } switch (sortOrder) { - case DisplayState.SORT_ORDER_DISPLAY_NAME: + case SORT_ORDER_DISPLAY_NAME: synchronized (SortingCursorWrapper.class) { binarySort(mPosition, mValueString); } break; - case DisplayState.SORT_ORDER_LAST_MODIFIED: - case DisplayState.SORT_ORDER_SIZE: + case SORT_ORDER_LAST_MODIFIED: + case SORT_ORDER_SIZE: binarySort(mPosition, mValueLong); break; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java index d571971..feccadc 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java @@ -30,13 +30,19 @@ import com.android.documentsui.RecentsProvider; import libcore.io.IoUtils; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.ProtocolException; import java.util.Comparator; /** * Representation of a {@link Document}. */ -public class DocumentInfo { +public class DocumentInfo implements Durable { + private static final int VERSION_INIT = 1; + public Uri uri; public String mimeType; public String displayName; @@ -46,6 +52,55 @@ public class DocumentInfo { public long size; public int icon; + public DocumentInfo() { + reset(); + } + + @Override + public void reset() { + uri = null; + mimeType = null; + displayName = null; + lastModified = -1; + flags = 0; + summary = null; + size = -1; + icon = 0; + } + + @Override + public void read(DataInputStream in) throws IOException { + final int version = in.readInt(); + switch (version) { + case VERSION_INIT: + final String rawUri = DurableUtils.readNullableString(in); + uri = rawUri != null ? Uri.parse(rawUri) : null; + mimeType = DurableUtils.readNullableString(in); + displayName = DurableUtils.readNullableString(in); + lastModified = in.readLong(); + flags = in.readInt(); + summary = DurableUtils.readNullableString(in); + size = in.readLong(); + icon = in.readInt(); + break; + default: + throw new ProtocolException("Unknown version " + version); + } + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(VERSION_INIT); + DurableUtils.writeNullableString(out, uri.toString()); + DurableUtils.writeNullableString(out, mimeType); + DurableUtils.writeNullableString(out, displayName); + out.writeLong(lastModified); + out.writeInt(flags); + DurableUtils.writeNullableString(out, summary); + out.writeLong(size); + out.writeInt(icon); + } + public static DocumentInfo fromDirectoryCursor(Uri parent, Cursor cursor) { final DocumentInfo doc = new DocumentInfo(); final String authority = parent.getAuthority(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java index b123a46..64631ab 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java @@ -16,54 +16,20 @@ package com.android.documentsui.model; -import static com.android.documentsui.DocumentsActivity.TAG; -import static com.android.documentsui.model.DocumentInfo.asFileNotFoundException; - -import android.content.ContentResolver; -import android.net.Uri; -import android.util.Log; - import com.android.documentsui.RootsCache; -import org.json.JSONArray; -import org.json.JSONException; - -import java.io.FileNotFoundException; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ProtocolException; import java.util.LinkedList; /** * Representation of a stack of {@link DocumentInfo}, usually the result of a * user-driven traversal. */ -public class DocumentStack extends LinkedList<DocumentInfo> { - - public static String serialize(DocumentStack stack) { - final JSONArray json = new JSONArray(); - for (int i = 0; i < stack.size(); i++) { - json.put(stack.get(i).uri); - } - return json.toString(); - } - - public static DocumentStack deserialize(ContentResolver resolver, String raw) - throws FileNotFoundException { - Log.d(TAG, "deserialize: " + raw); - - final DocumentStack stack = new DocumentStack(); - try { - final JSONArray json = new JSONArray(raw); - for (int i = 0; i < json.length(); i++) { - final Uri uri = Uri.parse(json.getString(i)); - final DocumentInfo doc = DocumentInfo.fromUri(resolver, uri); - stack.add(doc); - } - } catch (JSONException e) { - throw asFileNotFoundException(e); - } - - // TODO: handle roots that have gone missing - return stack; - } +public class DocumentStack extends LinkedList<DocumentInfo> implements Durable { + private static final int VERSION_INIT = 1; public RootInfo getRoot(RootsCache roots) { return roots.findRoot(getLast().uri); @@ -78,4 +44,37 @@ public class DocumentStack extends LinkedList<DocumentInfo> { return null; } } + + @Override + public void reset() { + clear(); + } + + @Override + public void read(DataInputStream in) throws IOException { + final int version = in.readInt(); + switch (version) { + case VERSION_INIT: + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final DocumentInfo doc = new DocumentInfo(); + doc.read(in); + add(doc); + } + break; + default: + throw new ProtocolException("Unknown version " + version); + } + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(VERSION_INIT); + final int size = size(); + out.writeInt(size); + for (int i = 0; i < size; i++) { + final DocumentInfo doc = get(i); + doc.write(out); + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Durable.java b/packages/DocumentsUI/src/com/android/documentsui/model/Durable.java new file mode 100644 index 0000000..01633ed --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Durable.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2013 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.model; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public interface Durable { + public void reset(); + public void read(DataInputStream in) throws IOException; + public void write(DataOutputStream out) throws IOException; +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DurableUtils.java b/packages/DocumentsUI/src/com/android/documentsui/model/DurableUtils.java new file mode 100644 index 0000000..214fb14 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DurableUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2013 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.model; + +import static com.android.documentsui.DocumentsActivity.TAG; + +import android.os.BadParcelableException; +import android.os.Parcel; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class DurableUtils { + public static <D extends Durable> byte[] writeToArray(D d) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + d.write(new DataOutputStream(out)); + return out.toByteArray(); + } + + public static <D extends Durable> D readFromArray(byte[] data, D d) throws IOException { + final ByteArrayInputStream in = new ByteArrayInputStream(data); + d.reset(); + try { + d.read(new DataInputStream(in)); + } catch (IOException e) { + d.reset(); + throw e; + } + return d; + } + + public static <D extends Durable> byte[] writeToArrayOrNull(D d) { + try { + return writeToArray(d); + } catch (IOException e) { + Log.w(TAG, "Failed to write", e); + return null; + } + } + + public static <D extends Durable> D readFromArrayOrNull(byte[] data, D d) { + try { + return readFromArray(data, d); + } catch (IOException e) { + Log.w(TAG, "Failed to read", e); + return null; + } + } + + public static <D extends Durable> void writeToParcel(Parcel parcel, D d) { + try { + parcel.writeByteArray(writeToArray(d)); + } catch (IOException e) { + throw new BadParcelableException(e); + } + } + + public static <D extends Durable> D readFromParcel(Parcel parcel, D d) { + try { + return readFromArray(parcel.createByteArray(), d); + } catch (IOException e) { + throw new BadParcelableException(e); + } + } + + public static void writeNullableString(DataOutputStream out, String value) throws IOException { + if (value != null) { + out.write(1); + out.writeUTF(value); + } else { + out.write(0); + } + } + + public static String readNullableString(DataInputStream in) throws IOException { + if (in.read() != 0) { + return in.readUTF(); + } else { + return null; + } + } +} |