Merge "Instance state, fix sharing, Durable objects." into klp-dev

This commit is contained in:
Jeff Sharkey
2013-09-03 03:50:10 +00:00
committed by Android (Google) Code Review
12 changed files with 499 additions and 210 deletions

View File

@@ -11,10 +11,7 @@
<!-- TODO: allow rotation when state saving is in better shape -->
<activity
android:name=".DocumentsActivity"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.Holo.Light"
android:screenOrientation="nosensor">
android:theme="@android:style/Theme.Holo.Light">
<intent-filter android:priority="100">
<action android:name="android.intent.action.OPEN_DOCUMENT" />
<category android:name="android.intent.category.DEFAULT" />
@@ -37,7 +34,7 @@
<intent-filter>
<action android:name="android.provider.action.MANAGE_DOCUMENTS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.doc/dir" />
<data android:mimeType="vnd.android.document/directory" />
</intent-filter>
</activity>

View File

@@ -63,4 +63,6 @@
<string name="more">More</string>
<string name="loading">Loading\u2026</string>
<string name="share_via">Share via</string>
</resources>

View File

@@ -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);
final Intent intent;
if (uris.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("*/*");
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));
intent.setType(doc.mimeType);
intent.putExtra(Intent.EXTRA_STREAM, doc.uri);
} 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);
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 {
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];
}
}

View File

@@ -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;

View File

@@ -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,59 +90,9 @@ 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;
}
// TODO: unify action in single place
mDisplayState.action = mAction;
if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) {
mDisplayState.allowMultiple = intent.getBooleanExtra(
Intent.EXTRA_ALLOW_MULTIPLE, false);
}
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);
} else {
mDisplayState.acceptMimes = new String[] { intent.getType() };
}
mDisplayState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
setResult(Activity.RESULT_CANCELED);
setContentView(R.layout.activity);
if (mAction == 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) {
final Intent moreApps = new Intent(getIntent());
moreApps.setComponent(null);
moreApps.setPackage(null);
RootsFragment.show(getFragmentManager(), moreApps);
} else if (mAction == ACTION_OPEN || mAction == ACTION_CREATE) {
RootsFragment.show(getFragmentManager(), null);
}
if (mAction == ACTION_MANAGE) {
mDisplayState.sortOrder = SORT_ORDER_LAST_MODIFIED;
}
mRootsContainer = findViewById(R.id.container_roots);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
@@ -156,9 +103,70 @@ public class DocumentsActivity extends Activity {
mDrawerLayout.setDrawerListener(mDrawerListener);
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
if (mAction == ACTION_MANAGE) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
if (icicle != null) {
mState = icicle.getParcelable(EXTRA_STATE);
} else {
buildDefaultState();
}
if (mState.action == ACTION_MANAGE) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
}
if (mState.action == ACTION_CREATE) {
final String mimeType = getIntent().getType();
final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
SaveFragment.show(getFragmentManager(), mimeType, title);
}
if (mState.action == ACTION_GET_CONTENT) {
final Intent moreApps = new Intent(getIntent());
moreApps.setComponent(null);
moreApps.setPackage(null);
RootsFragment.show(getFragmentManager(), moreApps);
} else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE) {
RootsFragment.show(getFragmentManager(), null);
}
onCurrentDirectoryChanged();
}
private void buildDefaultState() {
mState = new State();
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;
}
if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
mState.allowMultiple = intent.getBooleanExtra(
Intent.EXTRA_ALLOW_MULTIPLE, false);
}
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() };
}
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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}