DocumentsActivity.java revision b51331116eb2ebbc41aaf69142916f9af6dffdd5
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui;
18
19import static com.android.documentsui.DocumentsActivity.State.ACTION_CREATE;
20import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT;
21import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
22import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN;
23import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
24import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
25import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
26
27import android.app.ActionBar;
28import android.app.ActionBar.OnNavigationListener;
29import android.app.Activity;
30import android.app.Fragment;
31import android.app.FragmentManager;
32import android.content.ActivityNotFoundException;
33import android.content.ClipData;
34import android.content.ComponentName;
35import android.content.ContentProviderClient;
36import android.content.ContentResolver;
37import android.content.ContentValues;
38import android.content.Intent;
39import android.content.pm.ResolveInfo;
40import android.database.Cursor;
41import android.graphics.drawable.ColorDrawable;
42import android.net.Uri;
43import android.os.Bundle;
44import android.os.Parcel;
45import android.provider.DocumentsContract;
46import android.support.v4.app.ActionBarDrawerToggle;
47import android.support.v4.view.GravityCompat;
48import android.support.v4.widget.DrawerLayout;
49import android.support.v4.widget.DrawerLayout.DrawerListener;
50import android.util.Log;
51import android.view.LayoutInflater;
52import android.view.Menu;
53import android.view.MenuItem;
54import android.view.View;
55import android.view.ViewGroup;
56import android.widget.BaseAdapter;
57import android.widget.SearchView;
58import android.widget.SearchView.OnCloseListener;
59import android.widget.SearchView.OnQueryTextListener;
60import android.widget.TextView;
61import android.widget.Toast;
62
63import com.android.documentsui.model.DocumentInfo;
64import com.android.documentsui.model.DocumentStack;
65import com.android.documentsui.model.DurableUtils;
66import com.android.documentsui.model.RootInfo;
67
68import java.io.FileNotFoundException;
69import java.io.IOException;
70import java.util.Arrays;
71import java.util.List;
72
73public class DocumentsActivity extends Activity {
74    public static final String TAG = "Documents";
75
76    private SearchView mSearchView;
77
78    private View mRootsContainer;
79    private DrawerLayout mDrawerLayout;
80    private ActionBarDrawerToggle mDrawerToggle;
81
82    private static final String EXTRA_STATE = "state";
83
84    private RootsCache mRoots;
85    private State mState;
86
87    @Override
88    public void onCreate(Bundle icicle) {
89        super.onCreate(icicle);
90
91        mRoots = DocumentsApplication.getRootsCache(this);
92
93        setResult(Activity.RESULT_CANCELED);
94        setContentView(R.layout.activity);
95
96        mRootsContainer = findViewById(R.id.container_roots);
97
98        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
99
100        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
101                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
102
103        mDrawerLayout.setDrawerListener(mDrawerListener);
104        mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
105
106        if (icicle != null) {
107            mState = icicle.getParcelable(EXTRA_STATE);
108        } else {
109            buildDefaultState();
110        }
111
112        if (mState.action == ACTION_MANAGE) {
113            mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
114        }
115
116        if (mState.action == ACTION_CREATE) {
117            final String mimeType = getIntent().getType();
118            final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
119            SaveFragment.show(getFragmentManager(), mimeType, title);
120        }
121
122        if (mState.action == ACTION_GET_CONTENT) {
123            final Intent moreApps = new Intent(getIntent());
124            moreApps.setComponent(null);
125            moreApps.setPackage(null);
126            RootsFragment.show(getFragmentManager(), moreApps);
127        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE) {
128            RootsFragment.show(getFragmentManager(), null);
129        }
130
131        onCurrentDirectoryChanged();
132    }
133
134    private void buildDefaultState() {
135        mState = new State();
136
137        final Intent intent = getIntent();
138        final String action = intent.getAction();
139        if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
140            mState.action = ACTION_OPEN;
141        } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
142            mState.action = ACTION_CREATE;
143        } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
144            mState.action = ACTION_GET_CONTENT;
145        } else if (DocumentsContract.ACTION_MANAGE_DOCUMENTS.equals(action)) {
146            mState.action = ACTION_MANAGE;
147        }
148
149        if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
150            mState.allowMultiple = intent.getBooleanExtra(
151                    Intent.EXTRA_ALLOW_MULTIPLE, false);
152        }
153
154        if (mState.action == ACTION_MANAGE) {
155            mState.acceptMimes = new String[] { "*/*" };
156            mState.allowMultiple = true;
157        } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
158            mState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
159        } else {
160            mState.acceptMimes = new String[] { intent.getType() };
161        }
162
163        mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
164
165        if (mState.action == ACTION_MANAGE) {
166            mState.sortOrder = SORT_ORDER_LAST_MODIFIED;
167        }
168
169        if (mState.action == ACTION_MANAGE) {
170            final Uri rootUri = intent.getData();
171            final RootInfo root = mRoots.findRoot(rootUri);
172            if (root != null) {
173                onRootPicked(root, true);
174            } else {
175                Log.w(TAG, "Failed to find root: " + rootUri);
176                finish();
177            }
178
179        } else {
180            // Restore last stack for calling package
181            // TODO: move into async loader
182            final String packageName = getCallingPackage();
183            final Cursor cursor = getContentResolver()
184                    .query(RecentsProvider.buildResume(packageName), null, null, null, null);
185            try {
186                if (cursor.moveToFirst()) {
187                    final byte[] rawStack = cursor.getBlob(
188                            cursor.getColumnIndex(RecentsProvider.COL_PATH));
189                    DurableUtils.readFromArray(rawStack, mState.stack);
190                }
191            } catch (IOException e) {
192                Log.w(TAG, "Failed to resume", e);
193            } finally {
194                cursor.close();
195            }
196
197            mDrawerLayout.openDrawer(mRootsContainer);
198        }
199    }
200
201    @Override
202    public void onStart() {
203        super.onStart();
204
205        if (mState.action == ACTION_MANAGE) {
206            mState.showSize = true;
207        } else {
208            mState.showSize = SettingsActivity.getDisplayFileSize(this);
209        }
210    }
211
212    private DrawerListener mDrawerListener = new DrawerListener() {
213        @Override
214        public void onDrawerSlide(View drawerView, float slideOffset) {
215            mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
216        }
217
218        @Override
219        public void onDrawerOpened(View drawerView) {
220            mDrawerToggle.onDrawerOpened(drawerView);
221            updateActionBar();
222        }
223
224        @Override
225        public void onDrawerClosed(View drawerView) {
226            mDrawerToggle.onDrawerClosed(drawerView);
227            updateActionBar();
228        }
229
230        @Override
231        public void onDrawerStateChanged(int newState) {
232            mDrawerToggle.onDrawerStateChanged(newState);
233        }
234    };
235
236    @Override
237    protected void onPostCreate(Bundle savedInstanceState) {
238        super.onPostCreate(savedInstanceState);
239        mDrawerToggle.syncState();
240    }
241
242    public void updateActionBar() {
243        final ActionBar actionBar = getActionBar();
244
245        actionBar.setDisplayShowHomeEnabled(true);
246
247        if (mDrawerLayout.isDrawerOpen(mRootsContainer)) {
248            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
249            actionBar.setIcon(new ColorDrawable());
250
251            if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
252                actionBar.setTitle(R.string.title_open);
253            } else if (mState.action == ACTION_CREATE) {
254                actionBar.setTitle(R.string.title_save);
255            }
256
257            actionBar.setDisplayHomeAsUpEnabled(true);
258            mDrawerToggle.setDrawerIndicatorEnabled(true);
259
260        } else {
261            final RootInfo root = getCurrentRoot();
262            actionBar.setIcon(root != null ? root.loadIcon(this) : null);
263
264            if (mRoots.isRecentsRoot(root)) {
265                actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
266                actionBar.setTitle(root.title);
267            } else {
268                actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
269                actionBar.setTitle(null);
270                actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener);
271                actionBar.setSelectedNavigationItem(mState.sortOrder);
272            }
273
274            if (mState.stack.size() > 1) {
275                actionBar.setDisplayHomeAsUpEnabled(true);
276                mDrawerToggle.setDrawerIndicatorEnabled(false);
277            } else if (mState.action == ACTION_MANAGE) {
278                actionBar.setDisplayHomeAsUpEnabled(false);
279                mDrawerToggle.setDrawerIndicatorEnabled(false);
280            } else {
281                actionBar.setDisplayHomeAsUpEnabled(true);
282                mDrawerToggle.setDrawerIndicatorEnabled(true);
283            }
284        }
285    }
286
287    @Override
288    public boolean onCreateOptionsMenu(Menu menu) {
289        super.onCreateOptionsMenu(menu);
290        getMenuInflater().inflate(R.menu.activity, menu);
291
292        final MenuItem searchMenu = menu.findItem(R.id.menu_search);
293        mSearchView = (SearchView) searchMenu.getActionView();
294        mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
295            @Override
296            public boolean onQueryTextSubmit(String query) {
297                mState.currentSearch = query;
298                onCurrentDirectoryChanged();
299                mSearchView.setIconified(true);
300                return true;
301            }
302
303            @Override
304            public boolean onQueryTextChange(String newText) {
305                return false;
306            }
307        });
308
309        mSearchView.setOnCloseListener(new OnCloseListener() {
310            @Override
311            public boolean onClose() {
312                mState.currentSearch = null;
313                onCurrentDirectoryChanged();
314                return false;
315            }
316        });
317
318        return true;
319    }
320
321    @Override
322    public boolean onPrepareOptionsMenu(Menu menu) {
323        super.onPrepareOptionsMenu(menu);
324
325        final FragmentManager fm = getFragmentManager();
326        final DocumentInfo cwd = getCurrentDirectory();
327
328        final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
329        final MenuItem search = menu.findItem(R.id.menu_search);
330        final MenuItem grid =  menu.findItem(R.id.menu_grid);
331        final MenuItem list = menu.findItem(R.id.menu_list);
332        final MenuItem settings = menu.findItem(R.id.menu_settings);
333
334        grid.setVisible(mState.mode != MODE_GRID);
335        list.setVisible(mState.mode != MODE_LIST);
336
337        final boolean searchVisible;
338        if (mState.action == ACTION_CREATE) {
339            createDir.setVisible(cwd != null && cwd.isCreateSupported());
340            searchVisible = false;
341
342            // No display options in recent directories
343            if (cwd == null) {
344                grid.setVisible(false);
345                list.setVisible(false);
346            }
347
348            SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
349        } else {
350            createDir.setVisible(false);
351            searchVisible = cwd != null && cwd.isSearchSupported();
352        }
353
354        // TODO: close any search in-progress when hiding
355        search.setVisible(searchVisible);
356
357        settings.setVisible(mState.action != ACTION_MANAGE);
358
359        return true;
360    }
361
362    @Override
363    public boolean onOptionsItemSelected(MenuItem item) {
364        if (mDrawerToggle.onOptionsItemSelected(item)) {
365            return true;
366        }
367
368        final int id = item.getItemId();
369        if (id == android.R.id.home) {
370            onBackPressed();
371            return true;
372        } else if (id == R.id.menu_create_dir) {
373            CreateDirectoryFragment.show(getFragmentManager());
374            return true;
375        } else if (id == R.id.menu_search) {
376            return false;
377        } else if (id == R.id.menu_grid) {
378            // TODO: persist explicit user mode for cwd
379            mState.mode = MODE_GRID;
380            updateDisplayState();
381            invalidateOptionsMenu();
382            return true;
383        } else if (id == R.id.menu_list) {
384            // TODO: persist explicit user mode for cwd
385            mState.mode = MODE_LIST;
386            updateDisplayState();
387            invalidateOptionsMenu();
388            return true;
389        } else if (id == R.id.menu_settings) {
390            startActivity(new Intent(this, SettingsActivity.class));
391            return true;
392        } else {
393            return super.onOptionsItemSelected(item);
394        }
395    }
396
397    @Override
398    public void onBackPressed() {
399        final int size = mState.stack.size();
400        if (size > 1) {
401            mState.stack.pop();
402            onCurrentDirectoryChanged();
403        } else if (size == 1 && !mDrawerLayout.isDrawerOpen(mRootsContainer)) {
404            // TODO: open root drawer once we can capture back key
405            super.onBackPressed();
406        } else {
407            super.onBackPressed();
408        }
409    }
410
411    @Override
412    protected void onSaveInstanceState(Bundle state) {
413        super.onSaveInstanceState(state);
414        state.putParcelable(EXTRA_STATE, mState);
415    }
416
417    @Override
418    protected void onRestoreInstanceState(Bundle state) {
419        super.onRestoreInstanceState(state);
420        updateActionBar();
421    }
422
423    // TODO: support additional sort orders
424    private BaseAdapter mSortAdapter = new BaseAdapter() {
425        @Override
426        public int getCount() {
427            return mState.showSize ? 3 : 2;
428        }
429
430        @Override
431        public Object getItem(int position) {
432            switch (position) {
433                case 0:
434                    return getText(R.string.sort_name);
435                case 1:
436                    return getText(R.string.sort_date);
437                case 2:
438                    return getText(R.string.sort_size);
439                default:
440                    return null;
441            }
442        }
443
444        @Override
445        public long getItemId(int position) {
446            return position;
447        }
448
449        @Override
450        public View getView(int position, View convertView, ViewGroup parent) {
451            if (convertView == null) {
452                convertView = LayoutInflater.from(parent.getContext())
453                        .inflate(R.layout.item_title, parent, false);
454            }
455
456            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
457            final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
458
459            if (mState.stack.size() > 0) {
460                title.setText(mState.stack.getTitle(mRoots));
461            } else {
462                // No directory means recents
463                title.setText(R.string.root_recent);
464            }
465
466            summary.setText((String) getItem(position));
467
468            return convertView;
469        }
470
471        @Override
472        public View getDropDownView(int position, View convertView, ViewGroup parent) {
473            if (convertView == null) {
474                convertView = LayoutInflater.from(parent.getContext())
475                        .inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
476            }
477
478            final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
479            text1.setText((String) getItem(position));
480
481            return convertView;
482        }
483    };
484
485    private OnNavigationListener mSortListener = new OnNavigationListener() {
486        @Override
487        public boolean onNavigationItemSelected(int itemPosition, long itemId) {
488            mState.sortOrder = itemPosition;
489            updateDisplayState();
490            return true;
491        }
492    };
493
494    public RootInfo getCurrentRoot() {
495        if (mState.stack.size() > 0) {
496            return mState.stack.getRoot(mRoots);
497        } else {
498            return mRoots.getRecentsRoot();
499        }
500    }
501
502    public DocumentInfo getCurrentDirectory() {
503        return mState.stack.peek();
504    }
505
506    public State getDisplayState() {
507        return mState;
508    }
509
510    private void onCurrentDirectoryChanged() {
511        final FragmentManager fm = getFragmentManager();
512        final DocumentInfo cwd = getCurrentDirectory();
513
514        if (cwd == null) {
515            // No directory means recents
516            if (mState.action == ACTION_CREATE) {
517                RecentsCreateFragment.show(fm);
518            } else {
519                DirectoryFragment.showRecentsOpen(fm);
520            }
521        } else {
522            if (mState.currentSearch != null) {
523                // Ongoing search
524                DirectoryFragment.showSearch(fm, cwd.uri, mState.currentSearch);
525            } else {
526                // Normal boring directory
527                DirectoryFragment.showNormal(fm, cwd.uri);
528            }
529        }
530
531        // Forget any replacement target
532        if (mState.action == ACTION_CREATE) {
533            final SaveFragment save = SaveFragment.get(fm);
534            if (save != null) {
535                save.setReplaceTarget(null);
536            }
537        }
538
539        updateActionBar();
540        invalidateOptionsMenu();
541        dumpStack();
542    }
543
544    private void updateDisplayState() {
545        // TODO: handle multiple directory stacks on tablets
546        DirectoryFragment.get(getFragmentManager()).updateDisplayState();
547    }
548
549    public void onStackPicked(DocumentStack stack) {
550        mState.stack = stack;
551        onCurrentDirectoryChanged();
552    }
553
554    public void onRootPicked(RootInfo root, boolean closeDrawer) {
555        // Clear entire backstack and start in new root
556        mState.stack.clear();
557
558        if (!mRoots.isRecentsRoot(root)) {
559            try {
560                final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId);
561                onDocumentPicked(DocumentInfo.fromUri(getContentResolver(), uri));
562            } catch (FileNotFoundException e) {
563            }
564        } else {
565            onCurrentDirectoryChanged();
566        }
567
568        if (closeDrawer) {
569            mDrawerLayout.closeDrawers();
570        }
571    }
572
573    public void onAppPicked(ResolveInfo info) {
574        final Intent intent = new Intent(getIntent());
575        intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
576        intent.setComponent(new ComponentName(
577                info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
578        startActivity(intent);
579        finish();
580    }
581
582    public void onDocumentPicked(DocumentInfo doc) {
583        final FragmentManager fm = getFragmentManager();
584        if (doc.isDirectory()) {
585            // TODO: query display mode user preference for this dir
586            if (doc.isGridPreferred()) {
587                mState.mode = MODE_GRID;
588            } else {
589                mState.mode = MODE_LIST;
590            }
591            mState.stack.push(doc);
592            onCurrentDirectoryChanged();
593        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
594            // Explicit file picked, return
595            onFinished(doc.uri);
596        } else if (mState.action == ACTION_CREATE) {
597            // Replace selected file
598            SaveFragment.get(fm).setReplaceTarget(doc);
599        } else if (mState.action == ACTION_MANAGE) {
600            // Open the document
601            final Intent intent = new Intent(Intent.ACTION_VIEW);
602            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
603            intent.setData(doc.uri);
604            try {
605                startActivity(intent);
606            } catch (ActivityNotFoundException ex) {
607                Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show();
608            }
609        }
610    }
611
612    public void onDocumentsPicked(List<DocumentInfo> docs) {
613        if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
614            final int size = docs.size();
615            final Uri[] uris = new Uri[size];
616            for (int i = 0; i < size; i++) {
617                uris[i] = docs.get(i).uri;
618            }
619            onFinished(uris);
620        }
621    }
622
623    public void onSaveRequested(DocumentInfo replaceTarget) {
624        onFinished(replaceTarget.uri);
625    }
626
627    public void onSaveRequested(String mimeType, String displayName) {
628        final DocumentInfo cwd = getCurrentDirectory();
629        final String authority = cwd.uri.getAuthority();
630
631        final ContentProviderClient client = getContentResolver()
632                .acquireUnstableContentProviderClient(authority);
633        try {
634            final Uri childUri = DocumentsContract.createDocument(
635                    getContentResolver(), cwd.uri, mimeType, displayName);
636            onFinished(childUri);
637        } catch (Exception e) {
638            Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
639        } finally {
640            ContentProviderClient.closeQuietly(client);
641        }
642    }
643
644    private void onFinished(Uri... uris) {
645        Log.d(TAG, "onFinished() " + Arrays.toString(uris));
646
647        final ContentResolver resolver = getContentResolver();
648        final ContentValues values = new ContentValues();
649
650        final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
651        if (mState.action == ACTION_CREATE) {
652            // Remember stack for last create
653            values.clear();
654            values.put(RecentsProvider.COL_PATH, rawStack);
655            resolver.insert(RecentsProvider.buildRecentCreate(), values);
656
657        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
658            // Remember opened items
659            for (Uri uri : uris) {
660                values.clear();
661                values.put(RecentsProvider.COL_URI, uri.toString());
662                resolver.insert(RecentsProvider.buildRecentOpen(), values);
663            }
664        }
665
666        // Remember location for next app launch
667        final String packageName = getCallingPackage();
668        values.clear();
669        values.put(RecentsProvider.COL_PATH, rawStack);
670        resolver.insert(RecentsProvider.buildResume(packageName), values);
671
672        final Intent intent = new Intent();
673        if (uris.length == 1) {
674            intent.setData(uris[0]);
675        } else if (uris.length > 1) {
676            final ClipData clipData = new ClipData(
677                    null, mState.acceptMimes, new ClipData.Item(uris[0]));
678            for (int i = 1; i < uris.length; i++) {
679                clipData.addItem(new ClipData.Item(uris[i]));
680            }
681            intent.setClipData(clipData);
682        }
683
684        if (mState.action == ACTION_GET_CONTENT) {
685            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
686        } else {
687            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
688                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
689                    | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
690        }
691
692        setResult(Activity.RESULT_OK, intent);
693        finish();
694    }
695
696    public static class State implements android.os.Parcelable {
697        public int action;
698        public int mode = MODE_LIST;
699        public String[] acceptMimes;
700        public int sortOrder = SORT_ORDER_DISPLAY_NAME;
701        public boolean allowMultiple = false;
702        public boolean showSize = false;
703        public boolean localOnly = false;
704
705        /** Current user navigation stack; empty implies recents. */
706        public DocumentStack stack = new DocumentStack();
707        /** Currently active search, overriding any stack. */
708        public String currentSearch;
709
710        public static final int ACTION_OPEN = 1;
711        public static final int ACTION_CREATE = 2;
712        public static final int ACTION_GET_CONTENT = 3;
713        public static final int ACTION_MANAGE = 4;
714
715        public static final int MODE_LIST = 0;
716        public static final int MODE_GRID = 1;
717
718        public static final int SORT_ORDER_DISPLAY_NAME = 0;
719        public static final int SORT_ORDER_LAST_MODIFIED = 1;
720        public static final int SORT_ORDER_SIZE = 2;
721
722        @Override
723        public int describeContents() {
724            return 0;
725        }
726
727        @Override
728        public void writeToParcel(Parcel out, int flags) {
729            out.writeInt(action);
730            out.writeInt(mode);
731            out.writeStringArray(acceptMimes);
732            out.writeInt(sortOrder);
733            out.writeInt(allowMultiple ? 1 : 0);
734            out.writeInt(showSize ? 1 : 0);
735            out.writeInt(localOnly ? 1 : 0);
736            DurableUtils.writeToParcel(out, stack);
737            out.writeString(currentSearch);
738        }
739
740        public static final Creator<State> CREATOR = new Creator<State>() {
741            @Override
742            public State createFromParcel(Parcel in) {
743                final State state = new State();
744                state.action = in.readInt();
745                state.mode = in.readInt();
746                state.acceptMimes = in.readStringArray();
747                state.sortOrder = in.readInt();
748                state.allowMultiple = in.readInt() != 0;
749                state.showSize = in.readInt() != 0;
750                state.localOnly = in.readInt() != 0;
751                DurableUtils.readFromParcel(in, state.stack);
752                state.currentSearch = in.readString();
753                return state;
754            }
755
756            @Override
757            public State[] newArray(int size) {
758                return new State[size];
759            }
760        };
761    }
762
763    private void dumpStack() {
764        Log.d(TAG, "Current stack:");
765        for (DocumentInfo doc : mState.stack) {
766            Log.d(TAG, "--> " + doc);
767        }
768    }
769
770    public static DocumentsActivity get(Fragment fragment) {
771        return (DocumentsActivity) fragment.getActivity();
772    }
773}
774