Home | History | Annotate | Download | only in documentsui
      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 
     17 package com.android.documentsui;
     18 
     19 import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
     20 import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
     21 import static com.android.documentsui.DirectoryFragment.ANIM_SIDE;
     22 import static com.android.documentsui.DirectoryFragment.ANIM_UP;
     23 import static com.android.documentsui.DocumentsActivity.State.ACTION_CREATE;
     24 import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT;
     25 import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
     26 import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN;
     27 import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN_TREE;
     28 import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
     29 import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
     30 
     31 import android.app.Activity;
     32 import android.app.Fragment;
     33 import android.app.FragmentManager;
     34 import android.content.ActivityNotFoundException;
     35 import android.content.ClipData;
     36 import android.content.ComponentName;
     37 import android.content.ContentProviderClient;
     38 import android.content.ContentResolver;
     39 import android.content.ContentValues;
     40 import android.content.Context;
     41 import android.content.Intent;
     42 import android.content.pm.ResolveInfo;
     43 import android.content.res.Resources;
     44 import android.database.Cursor;
     45 import android.graphics.Point;
     46 import android.net.Uri;
     47 import android.os.AsyncTask;
     48 import android.os.Bundle;
     49 import android.os.Parcel;
     50 import android.os.Parcelable;
     51 import android.provider.DocumentsContract;
     52 import android.provider.DocumentsContract.Root;
     53 import android.support.v4.app.ActionBarDrawerToggle;
     54 import android.support.v4.widget.DrawerLayout;
     55 import android.support.v4.widget.DrawerLayout.DrawerListener;
     56 import android.util.Log;
     57 import android.util.SparseArray;
     58 import android.view.LayoutInflater;
     59 import android.view.Menu;
     60 import android.view.MenuItem;
     61 import android.view.MenuItem.OnActionExpandListener;
     62 import android.view.View;
     63 import android.view.ViewGroup;
     64 import android.view.WindowManager;
     65 import android.widget.AdapterView;
     66 import android.widget.AdapterView.OnItemSelectedListener;
     67 import android.widget.BaseAdapter;
     68 import android.widget.ImageView;
     69 import android.widget.SearchView;
     70 import android.widget.SearchView.OnQueryTextListener;
     71 import android.widget.Spinner;
     72 import android.widget.TextView;
     73 import android.widget.Toast;
     74 import android.widget.Toolbar;
     75 
     76 import com.android.documentsui.RecentsProvider.RecentColumns;
     77 import com.android.documentsui.RecentsProvider.ResumeColumns;
     78 import com.android.documentsui.model.DocumentInfo;
     79 import com.android.documentsui.model.DocumentStack;
     80 import com.android.documentsui.model.DurableUtils;
     81 import com.android.documentsui.model.RootInfo;
     82 import com.google.common.collect.Maps;
     83 
     84 import libcore.io.IoUtils;
     85 
     86 import java.io.FileNotFoundException;
     87 import java.io.IOException;
     88 import java.util.Arrays;
     89 import java.util.Collection;
     90 import java.util.HashMap;
     91 import java.util.List;
     92 import java.util.concurrent.Executor;
     93 
     94 public class DocumentsActivity extends Activity {
     95     public static final String TAG = "Documents";
     96 
     97     private static final String EXTRA_STATE = "state";
     98 
     99     private static final int CODE_FORWARD = 42;
    100 
    101     private boolean mShowAsDialog;
    102 
    103     private SearchView mSearchView;
    104 
    105     private Toolbar mToolbar;
    106     private Spinner mToolbarStack;
    107 
    108     private Toolbar mRootsToolbar;
    109 
    110     private DrawerLayout mDrawerLayout;
    111     private ActionBarDrawerToggle mDrawerToggle;
    112     private View mRootsDrawer;
    113 
    114     private DirectoryContainerView mDirectoryContainer;
    115 
    116     private boolean mIgnoreNextNavigation;
    117     private boolean mIgnoreNextClose;
    118     private boolean mIgnoreNextCollapse;
    119 
    120     private boolean mSearchExpanded;
    121 
    122     private RootsCache mRoots;
    123     private State mState;
    124 
    125     @Override
    126     public void onCreate(Bundle icicle) {
    127         super.onCreate(icicle);
    128 
    129         mRoots = DocumentsApplication.getRootsCache(this);
    130 
    131         setResult(Activity.RESULT_CANCELED);
    132         setContentView(R.layout.activity);
    133 
    134         final Context context = this;
    135         final Resources res = getResources();
    136         mShowAsDialog = res.getBoolean(R.bool.show_as_dialog);
    137 
    138         if (mShowAsDialog) {
    139             // Strongly define our horizontal dimension; we leave vertical as
    140             // WRAP_CONTENT so that system resizes us when IME is showing.
    141             final WindowManager.LayoutParams a = getWindow().getAttributes();
    142 
    143             final Point size = new Point();
    144             getWindowManager().getDefaultDisplay().getSize(size);
    145             a.width = (int) res.getFraction(R.dimen.dialog_width, size.x, size.x);
    146 
    147             getWindow().setAttributes(a);
    148 
    149         } else {
    150             // Non-dialog means we have a drawer
    151             mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
    152 
    153             mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
    154                     R.drawable.ic_hamburger, R.string.drawer_open, R.string.drawer_close);
    155 
    156             mDrawerLayout.setDrawerListener(mDrawerListener);
    157 
    158             mRootsDrawer = findViewById(R.id.drawer_roots);
    159         }
    160 
    161         mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory);
    162 
    163         if (icicle != null) {
    164             mState = icicle.getParcelable(EXTRA_STATE);
    165         } else {
    166             buildDefaultState();
    167         }
    168 
    169         mToolbar = (Toolbar) findViewById(R.id.toolbar);
    170         mToolbar.setTitleTextAppearance(context,
    171                 android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
    172 
    173         mToolbarStack = (Spinner) findViewById(R.id.stack);
    174         mToolbarStack.setOnItemSelectedListener(mStackListener);
    175 
    176         mRootsToolbar = (Toolbar) findViewById(R.id.roots_toolbar);
    177         if (mRootsToolbar != null) {
    178             mRootsToolbar.setTitleTextAppearance(context,
    179                     android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
    180         }
    181 
    182         setActionBar(mToolbar);
    183 
    184         // Hide roots when we're managing a specific root
    185         if (mState.action == ACTION_MANAGE) {
    186             if (mShowAsDialog) {
    187                 findViewById(R.id.container_roots).setVisibility(View.GONE);
    188             } else {
    189                 mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
    190             }
    191         }
    192 
    193         if (mState.action == ACTION_CREATE) {
    194             final String mimeType = getIntent().getType();
    195             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
    196             SaveFragment.show(getFragmentManager(), mimeType, title);
    197         } else if (mState.action == ACTION_OPEN_TREE) {
    198             PickFragment.show(getFragmentManager());
    199         }
    200 
    201         if (mState.action == ACTION_GET_CONTENT) {
    202             final Intent moreApps = new Intent(getIntent());
    203             moreApps.setComponent(null);
    204             moreApps.setPackage(null);
    205             RootsFragment.show(getFragmentManager(), moreApps);
    206         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE
    207                 || mState.action == ACTION_OPEN_TREE) {
    208             RootsFragment.show(getFragmentManager(), null);
    209         }
    210 
    211         if (!mState.restored) {
    212             if (mState.action == ACTION_MANAGE) {
    213                 final Uri rootUri = getIntent().getData();
    214                 new RestoreRootTask(rootUri).executeOnExecutor(getCurrentExecutor());
    215             } else {
    216                 new RestoreStackTask().execute();
    217             }
    218         } else {
    219             onCurrentDirectoryChanged(ANIM_NONE);
    220         }
    221     }
    222 
    223     private void buildDefaultState() {
    224         mState = new State();
    225 
    226         final Intent intent = getIntent();
    227         final String action = intent.getAction();
    228         if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
    229             mState.action = ACTION_OPEN;
    230         } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
    231             mState.action = ACTION_CREATE;
    232         } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
    233             mState.action = ACTION_GET_CONTENT;
    234         } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
    235             mState.action = ACTION_OPEN_TREE;
    236         } else if (DocumentsContract.ACTION_MANAGE_ROOT.equals(action)) {
    237             mState.action = ACTION_MANAGE;
    238         }
    239 
    240         if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
    241             mState.allowMultiple = intent.getBooleanExtra(
    242                     Intent.EXTRA_ALLOW_MULTIPLE, false);
    243         }
    244 
    245         if (mState.action == ACTION_MANAGE) {
    246             mState.acceptMimes = new String[] { "*/*" };
    247             mState.allowMultiple = true;
    248         } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
    249             mState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
    250         } else {
    251             mState.acceptMimes = new String[] { intent.getType() };
    252         }
    253 
    254         mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
    255         mState.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
    256         mState.showAdvanced = mState.forceAdvanced
    257                 | LocalPreferences.getDisplayAdvancedDevices(this);
    258 
    259         if (mState.action == ACTION_MANAGE) {
    260             mState.showSize = true;
    261         } else {
    262             mState.showSize = LocalPreferences.getDisplayFileSize(this);
    263         }
    264     }
    265 
    266     private class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> {
    267         private Uri mRootUri;
    268 
    269         public RestoreRootTask(Uri rootUri) {
    270             mRootUri = rootUri;
    271         }
    272 
    273         @Override
    274         protected RootInfo doInBackground(Void... params) {
    275             final String rootId = DocumentsContract.getRootId(mRootUri);
    276             return mRoots.getRootOneshot(mRootUri.getAuthority(), rootId);
    277         }
    278 
    279         @Override
    280         protected void onPostExecute(RootInfo root) {
    281             if (isDestroyed()) return;
    282             mState.restored = true;
    283 
    284             if (root != null) {
    285                 onRootPicked(root, true);
    286             } else {
    287                 Log.w(TAG, "Failed to find root: " + mRootUri);
    288                 finish();
    289             }
    290         }
    291     }
    292 
    293     private class RestoreStackTask extends AsyncTask<Void, Void, Void> {
    294         private volatile boolean mRestoredStack;
    295         private volatile boolean mExternal;
    296 
    297         @Override
    298         protected Void doInBackground(Void... params) {
    299             // Restore last stack for calling package
    300             final String packageName = getCallingPackageMaybeExtra();
    301             final Cursor cursor = getContentResolver()
    302                     .query(RecentsProvider.buildResume(packageName), null, null, null, null);
    303             try {
    304                 if (cursor.moveToFirst()) {
    305                     mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
    306                     final byte[] rawStack = cursor.getBlob(
    307                             cursor.getColumnIndex(ResumeColumns.STACK));
    308                     DurableUtils.readFromArray(rawStack, mState.stack);
    309                     mRestoredStack = true;
    310                 }
    311             } catch (IOException e) {
    312                 Log.w(TAG, "Failed to resume: " + e);
    313             } finally {
    314                 IoUtils.closeQuietly(cursor);
    315             }
    316 
    317             if (mRestoredStack) {
    318                 // Update the restored stack to ensure we have freshest data
    319                 final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState);
    320                 try {
    321                     mState.stack.updateRoot(matchingRoots);
    322                     mState.stack.updateDocuments(getContentResolver());
    323                 } catch (FileNotFoundException e) {
    324                     Log.w(TAG, "Failed to restore stack: " + e);
    325                     mState.stack.reset();
    326                     mRestoredStack = false;
    327                 }
    328             }
    329 
    330             return null;
    331         }
    332 
    333         @Override
    334         protected void onPostExecute(Void result) {
    335             if (isDestroyed()) return;
    336             mState.restored = true;
    337 
    338             // Show drawer when no stack restored, but only when requesting
    339             // non-visual content. However, if we last used an external app,
    340             // drawer is always shown.
    341 
    342             boolean showDrawer = false;
    343             if (!mRestoredStack) {
    344                 showDrawer = true;
    345             }
    346             if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
    347                 showDrawer = false;
    348             }
    349             if (mExternal && mState.action == ACTION_GET_CONTENT) {
    350                 showDrawer = true;
    351             }
    352 
    353             if (showDrawer) {
    354                 setRootsDrawerOpen(true);
    355             }
    356 
    357             onCurrentDirectoryChanged(ANIM_NONE);
    358         }
    359     }
    360 
    361     private DrawerListener mDrawerListener = new DrawerListener() {
    362         @Override
    363         public void onDrawerSlide(View drawerView, float slideOffset) {
    364             mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
    365         }
    366 
    367         @Override
    368         public void onDrawerOpened(View drawerView) {
    369             mDrawerToggle.onDrawerOpened(drawerView);
    370         }
    371 
    372         @Override
    373         public void onDrawerClosed(View drawerView) {
    374             mDrawerToggle.onDrawerClosed(drawerView);
    375         }
    376 
    377         @Override
    378         public void onDrawerStateChanged(int newState) {
    379             mDrawerToggle.onDrawerStateChanged(newState);
    380         }
    381     };
    382 
    383     @Override
    384     protected void onPostCreate(Bundle savedInstanceState) {
    385         super.onPostCreate(savedInstanceState);
    386         if (mDrawerToggle != null) {
    387             mDrawerToggle.syncState();
    388         }
    389     }
    390 
    391     public void setRootsDrawerOpen(boolean open) {
    392         if (!mShowAsDialog) {
    393             if (open) {
    394                 mDrawerLayout.openDrawer(mRootsDrawer);
    395             } else {
    396                 mDrawerLayout.closeDrawer(mRootsDrawer);
    397             }
    398         }
    399     }
    400 
    401     private boolean isRootsDrawerOpen() {
    402         if (mShowAsDialog) {
    403             return false;
    404         } else {
    405             return mDrawerLayout.isDrawerOpen(mRootsDrawer);
    406         }
    407     }
    408 
    409     public void updateActionBar() {
    410         if (mRootsToolbar != null) {
    411             if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT
    412                     || mState.action == ACTION_OPEN_TREE) {
    413                 mRootsToolbar.setTitle(R.string.title_open);
    414             } else if (mState.action == ACTION_CREATE) {
    415                 mRootsToolbar.setTitle(R.string.title_save);
    416             }
    417         }
    418 
    419         final RootInfo root = getCurrentRoot();
    420         final boolean showRootIcon = mShowAsDialog || (mState.action == ACTION_MANAGE);
    421         if (showRootIcon) {
    422             mToolbar.setNavigationIcon(
    423                     root != null ? root.loadToolbarIcon(mToolbar.getContext()) : null);
    424             mToolbar.setNavigationContentDescription(R.string.drawer_open);
    425             mToolbar.setNavigationOnClickListener(null);
    426         } else {
    427             mToolbar.setNavigationIcon(R.drawable.ic_hamburger);
    428             mToolbar.setNavigationContentDescription(R.string.drawer_open);
    429             mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
    430                 @Override
    431                 public void onClick(View v) {
    432                     setRootsDrawerOpen(true);
    433                 }
    434             });
    435         }
    436 
    437         if (mSearchExpanded) {
    438             mToolbar.setTitle(null);
    439             mToolbarStack.setVisibility(View.GONE);
    440             mToolbarStack.setAdapter(null);
    441         } else {
    442             if (mState.stack.size() <= 1) {
    443                 mToolbar.setTitle(root.title);
    444                 mToolbarStack.setVisibility(View.GONE);
    445                 mToolbarStack.setAdapter(null);
    446             } else {
    447                 mToolbar.setTitle(null);
    448                 mToolbarStack.setVisibility(View.VISIBLE);
    449                 mToolbarStack.setAdapter(mStackAdapter);
    450 
    451                 mIgnoreNextNavigation = true;
    452                 mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
    453             }
    454         }
    455     }
    456 
    457     @Override
    458     public boolean onCreateOptionsMenu(Menu menu) {
    459         super.onCreateOptionsMenu(menu);
    460         getMenuInflater().inflate(R.menu.activity, menu);
    461 
    462         // Most actions are visible when showing as dialog
    463         if (mShowAsDialog) {
    464             for (int i = 0; i < menu.size(); i++) {
    465                 final MenuItem item = menu.getItem(i);
    466                 switch (item.getItemId()) {
    467                     case R.id.menu_advanced:
    468                     case R.id.menu_file_size:
    469                         break;
    470                     default:
    471                         item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
    472                 }
    473             }
    474         }
    475 
    476         final MenuItem searchMenu = menu.findItem(R.id.menu_search);
    477         mSearchView = (SearchView) searchMenu.getActionView();
    478         mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
    479             @Override
    480             public boolean onQueryTextSubmit(String query) {
    481                 mSearchExpanded = true;
    482                 mState.currentSearch = query;
    483                 mSearchView.clearFocus();
    484                 onCurrentDirectoryChanged(ANIM_NONE);
    485                 return true;
    486             }
    487 
    488             @Override
    489             public boolean onQueryTextChange(String newText) {
    490                 return false;
    491             }
    492         });
    493 
    494         searchMenu.setOnActionExpandListener(new OnActionExpandListener() {
    495             @Override
    496             public boolean onMenuItemActionExpand(MenuItem item) {
    497                 mSearchExpanded = true;
    498                 updateActionBar();
    499                 return true;
    500             }
    501 
    502             @Override
    503             public boolean onMenuItemActionCollapse(MenuItem item) {
    504                 mSearchExpanded = false;
    505                 if (mIgnoreNextCollapse) {
    506                     mIgnoreNextCollapse = false;
    507                     return true;
    508                 }
    509 
    510                 mState.currentSearch = null;
    511                 onCurrentDirectoryChanged(ANIM_NONE);
    512                 return true;
    513             }
    514         });
    515 
    516         mSearchView.setOnCloseListener(new SearchView.OnCloseListener() {
    517             @Override
    518             public boolean onClose() {
    519                 mSearchExpanded = false;
    520                 if (mIgnoreNextClose) {
    521                     mIgnoreNextClose = false;
    522                     return false;
    523                 }
    524 
    525                 mState.currentSearch = null;
    526                 onCurrentDirectoryChanged(ANIM_NONE);
    527                 return false;
    528             }
    529         });
    530 
    531         return true;
    532     }
    533 
    534     @Override
    535     public boolean onPrepareOptionsMenu(Menu menu) {
    536         super.onPrepareOptionsMenu(menu);
    537 
    538         final FragmentManager fm = getFragmentManager();
    539 
    540         final RootInfo root = getCurrentRoot();
    541         final DocumentInfo cwd = getCurrentDirectory();
    542 
    543         final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
    544         final MenuItem search = menu.findItem(R.id.menu_search);
    545         final MenuItem sort = menu.findItem(R.id.menu_sort);
    546         final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
    547         final MenuItem grid = menu.findItem(R.id.menu_grid);
    548         final MenuItem list = menu.findItem(R.id.menu_list);
    549         final MenuItem advanced = menu.findItem(R.id.menu_advanced);
    550         final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
    551 
    552         sort.setVisible(cwd != null);
    553         grid.setVisible(mState.derivedMode != MODE_GRID);
    554         list.setVisible(mState.derivedMode != MODE_LIST);
    555 
    556         if (mState.currentSearch != null) {
    557             // Search uses backend ranking; no sorting
    558             sort.setVisible(false);
    559 
    560             search.expandActionView();
    561 
    562             mSearchView.setIconified(false);
    563             mSearchView.clearFocus();
    564             mSearchView.setQuery(mState.currentSearch, false);
    565         } else {
    566             mIgnoreNextClose = true;
    567             mSearchView.setIconified(true);
    568             mSearchView.clearFocus();
    569 
    570             mIgnoreNextCollapse = true;
    571             search.collapseActionView();
    572         }
    573 
    574         // Only sort by size when visible
    575         sortSize.setVisible(mState.showSize);
    576 
    577         final boolean searchVisible;
    578         if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) {
    579             createDir.setVisible(cwd != null && cwd.isCreateSupported());
    580             searchVisible = false;
    581 
    582             // No display options in recent directories
    583             if (cwd == null) {
    584                 grid.setVisible(false);
    585                 list.setVisible(false);
    586             }
    587 
    588             if (mState.action == ACTION_CREATE) {
    589                 SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
    590             }
    591         } else {
    592             createDir.setVisible(false);
    593 
    594             searchVisible = root != null
    595                     && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0);
    596         }
    597 
    598         // TODO: close any search in-progress when hiding
    599         search.setVisible(searchVisible);
    600 
    601         advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
    602                 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
    603         fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
    604                 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
    605 
    606         advanced.setVisible(mState.action != ACTION_MANAGE);
    607         fileSize.setVisible(mState.action != ACTION_MANAGE);
    608 
    609         return true;
    610     }
    611 
    612     @Override
    613     public boolean onOptionsItemSelected(MenuItem item) {
    614         if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
    615             return true;
    616         }
    617 
    618         final int id = item.getItemId();
    619         if (id == android.R.id.home) {
    620             onBackPressed();
    621             return true;
    622         } else if (id == R.id.menu_create_dir) {
    623             CreateDirectoryFragment.show(getFragmentManager());
    624             return true;
    625         } else if (id == R.id.menu_search) {
    626             return false;
    627         } else if (id == R.id.menu_sort_name) {
    628             setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
    629             return true;
    630         } else if (id == R.id.menu_sort_date) {
    631             setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
    632             return true;
    633         } else if (id == R.id.menu_sort_size) {
    634             setUserSortOrder(State.SORT_ORDER_SIZE);
    635             return true;
    636         } else if (id == R.id.menu_grid) {
    637             setUserMode(State.MODE_GRID);
    638             return true;
    639         } else if (id == R.id.menu_list) {
    640             setUserMode(State.MODE_LIST);
    641             return true;
    642         } else if (id == R.id.menu_advanced) {
    643             setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this));
    644             return true;
    645         } else if (id == R.id.menu_file_size) {
    646             setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
    647             return true;
    648         } else {
    649             return super.onOptionsItemSelected(item);
    650         }
    651     }
    652 
    653     private void setDisplayAdvancedDevices(boolean display) {
    654         LocalPreferences.setDisplayAdvancedDevices(this, display);
    655         mState.showAdvanced = mState.forceAdvanced | display;
    656         RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
    657         invalidateOptionsMenu();
    658     }
    659 
    660     private void setDisplayFileSize(boolean display) {
    661         LocalPreferences.setDisplayFileSize(this, display);
    662         mState.showSize = display;
    663         DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
    664         invalidateOptionsMenu();
    665     }
    666 
    667     /**
    668      * Update UI to reflect internal state changes not from user.
    669      */
    670     public void onStateChanged() {
    671         invalidateOptionsMenu();
    672     }
    673 
    674     /**
    675      * Set state sort order based on explicit user action.
    676      */
    677     private void setUserSortOrder(int sortOrder) {
    678         mState.userSortOrder = sortOrder;
    679         DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
    680     }
    681 
    682     /**
    683      * Set state mode based on explicit user action.
    684      */
    685     private void setUserMode(int mode) {
    686         mState.userMode = mode;
    687         DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
    688     }
    689 
    690     public void setPending(boolean pending) {
    691         final SaveFragment save = SaveFragment.get(getFragmentManager());
    692         if (save != null) {
    693             save.setPending(pending);
    694         }
    695     }
    696 
    697     @Override
    698     public void onBackPressed() {
    699         if (!mState.stackTouched) {
    700             super.onBackPressed();
    701             return;
    702         }
    703 
    704         final int size = mState.stack.size();
    705         if (size > 1) {
    706             mState.stack.pop();
    707             onCurrentDirectoryChanged(ANIM_UP);
    708         } else if (size == 1 && !isRootsDrawerOpen()) {
    709             // TODO: open root drawer once we can capture back key
    710             super.onBackPressed();
    711         } else {
    712             super.onBackPressed();
    713         }
    714     }
    715 
    716     @Override
    717     protected void onSaveInstanceState(Bundle state) {
    718         super.onSaveInstanceState(state);
    719         state.putParcelable(EXTRA_STATE, mState);
    720     }
    721 
    722     @Override
    723     protected void onRestoreInstanceState(Bundle state) {
    724         super.onRestoreInstanceState(state);
    725         updateActionBar();
    726     }
    727 
    728     private BaseAdapter mStackAdapter = new BaseAdapter() {
    729         @Override
    730         public int getCount() {
    731             return mState.stack.size();
    732         }
    733 
    734         @Override
    735         public DocumentInfo getItem(int position) {
    736             return mState.stack.get(mState.stack.size() - position - 1);
    737         }
    738 
    739         @Override
    740         public long getItemId(int position) {
    741             return position;
    742         }
    743 
    744         @Override
    745         public View getView(int position, View convertView, ViewGroup parent) {
    746             if (convertView == null) {
    747                 convertView = LayoutInflater.from(parent.getContext())
    748                         .inflate(R.layout.item_subdir_title, parent, false);
    749             }
    750 
    751             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
    752             final DocumentInfo doc = getItem(position);
    753 
    754             if (position == 0) {
    755                 final RootInfo root = getCurrentRoot();
    756                 title.setText(root.title);
    757             } else {
    758                 title.setText(doc.displayName);
    759             }
    760 
    761             return convertView;
    762         }
    763 
    764         @Override
    765         public View getDropDownView(int position, View convertView, ViewGroup parent) {
    766             if (convertView == null) {
    767                 convertView = LayoutInflater.from(parent.getContext())
    768                         .inflate(R.layout.item_subdir, parent, false);
    769             }
    770 
    771             final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
    772             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
    773             final DocumentInfo doc = getItem(position);
    774 
    775             if (position == 0) {
    776                 final RootInfo root = getCurrentRoot();
    777                 title.setText(root.title);
    778                 subdir.setVisibility(View.GONE);
    779             } else {
    780                 title.setText(doc.displayName);
    781                 subdir.setVisibility(View.VISIBLE);
    782             }
    783 
    784             return convertView;
    785         }
    786     };
    787 
    788     private OnItemSelectedListener mStackListener = new OnItemSelectedListener() {
    789         @Override
    790         public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    791             if (mIgnoreNextNavigation) {
    792                 mIgnoreNextNavigation = false;
    793                 return;
    794             }
    795 
    796             while (mState.stack.size() > position + 1) {
    797                 mState.stackTouched = true;
    798                 mState.stack.pop();
    799             }
    800             onCurrentDirectoryChanged(ANIM_UP);
    801         }
    802 
    803         @Override
    804         public void onNothingSelected(AdapterView<?> parent) {
    805             // Ignored
    806         }
    807     };
    808 
    809     public RootInfo getCurrentRoot() {
    810         if (mState.stack.root != null) {
    811             return mState.stack.root;
    812         } else {
    813             return mRoots.getRecentsRoot();
    814         }
    815     }
    816 
    817     public DocumentInfo getCurrentDirectory() {
    818         return mState.stack.peek();
    819     }
    820 
    821     private String getCallingPackageMaybeExtra() {
    822         final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
    823         return (extra != null) ? extra : getCallingPackage();
    824     }
    825 
    826     public Executor getCurrentExecutor() {
    827         final DocumentInfo cwd = getCurrentDirectory();
    828         if (cwd != null && cwd.authority != null) {
    829             return ProviderExecutor.forAuthority(cwd.authority);
    830         } else {
    831             return AsyncTask.THREAD_POOL_EXECUTOR;
    832         }
    833     }
    834 
    835     public State getDisplayState() {
    836         return mState;
    837     }
    838 
    839     private void onCurrentDirectoryChanged(int anim) {
    840         final FragmentManager fm = getFragmentManager();
    841         final RootInfo root = getCurrentRoot();
    842         final DocumentInfo cwd = getCurrentDirectory();
    843 
    844         mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN);
    845 
    846         if (cwd == null) {
    847             // No directory means recents
    848             if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) {
    849                 RecentsCreateFragment.show(fm);
    850             } else {
    851                 DirectoryFragment.showRecentsOpen(fm, anim);
    852 
    853                 // Start recents in grid when requesting visual things
    854                 final boolean visualMimes = MimePredicate.mimeMatches(
    855                         MimePredicate.VISUAL_MIMES, mState.acceptMimes);
    856                 mState.userMode = visualMimes ? MODE_GRID : MODE_LIST;
    857                 mState.derivedMode = mState.userMode;
    858             }
    859         } else {
    860             if (mState.currentSearch != null) {
    861                 // Ongoing search
    862                 DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
    863             } else {
    864                 // Normal boring directory
    865                 DirectoryFragment.showNormal(fm, root, cwd, anim);
    866             }
    867         }
    868 
    869         // Forget any replacement target
    870         if (mState.action == ACTION_CREATE) {
    871             final SaveFragment save = SaveFragment.get(fm);
    872             if (save != null) {
    873                 save.setReplaceTarget(null);
    874             }
    875         }
    876 
    877         if (mState.action == ACTION_OPEN_TREE) {
    878             final PickFragment pick = PickFragment.get(fm);
    879             if (pick != null) {
    880                 final CharSequence displayName = (mState.stack.size() <= 1) ? root.title
    881                         : cwd.displayName;
    882                 pick.setPickTarget(cwd, displayName);
    883             }
    884         }
    885 
    886         final RootsFragment roots = RootsFragment.get(fm);
    887         if (roots != null) {
    888             roots.onCurrentRootChanged();
    889         }
    890 
    891         updateActionBar();
    892         invalidateOptionsMenu();
    893         dumpStack();
    894     }
    895 
    896     public void onStackPicked(DocumentStack stack) {
    897         try {
    898             // Update the restored stack to ensure we have freshest data
    899             stack.updateDocuments(getContentResolver());
    900 
    901             mState.stack = stack;
    902             mState.stackTouched = true;
    903             onCurrentDirectoryChanged(ANIM_SIDE);
    904 
    905         } catch (FileNotFoundException e) {
    906             Log.w(TAG, "Failed to restore stack: " + e);
    907         }
    908     }
    909 
    910     public void onRootPicked(RootInfo root, boolean closeDrawer) {
    911         // Clear entire backstack and start in new root
    912         mState.stack.root = root;
    913         mState.stack.clear();
    914         mState.stackTouched = true;
    915 
    916         if (!mRoots.isRecentsRoot(root)) {
    917             new PickRootTask(root).executeOnExecutor(getCurrentExecutor());
    918         } else {
    919             onCurrentDirectoryChanged(ANIM_SIDE);
    920         }
    921 
    922         if (closeDrawer) {
    923             setRootsDrawerOpen(false);
    924         }
    925     }
    926 
    927     private class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
    928         private RootInfo mRoot;
    929 
    930         public PickRootTask(RootInfo root) {
    931             mRoot = root;
    932         }
    933 
    934         @Override
    935         protected DocumentInfo doInBackground(Void... params) {
    936             try {
    937                 final Uri uri = DocumentsContract.buildDocumentUri(
    938                         mRoot.authority, mRoot.documentId);
    939                 return DocumentInfo.fromUri(getContentResolver(), uri);
    940             } catch (FileNotFoundException e) {
    941                 Log.w(TAG, "Failed to find root", e);
    942                 return null;
    943             }
    944         }
    945 
    946         @Override
    947         protected void onPostExecute(DocumentInfo result) {
    948             if (result != null) {
    949                 mState.stack.push(result);
    950                 mState.stackTouched = true;
    951                 onCurrentDirectoryChanged(ANIM_SIDE);
    952             }
    953         }
    954     }
    955 
    956     public void onAppPicked(ResolveInfo info) {
    957         final Intent intent = new Intent(getIntent());
    958         intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    959         intent.setComponent(new ComponentName(
    960                 info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
    961         startActivityForResult(intent, CODE_FORWARD);
    962     }
    963 
    964     @Override
    965     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    966         Log.d(TAG, "onActivityResult() code=" + resultCode);
    967 
    968         // Only relay back results when not canceled; otherwise stick around to
    969         // let the user pick another app/backend.
    970         if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
    971 
    972             // Remember that we last picked via external app
    973             final String packageName = getCallingPackageMaybeExtra();
    974             final ContentValues values = new ContentValues();
    975             values.put(ResumeColumns.EXTERNAL, 1);
    976             getContentResolver().insert(RecentsProvider.buildResume(packageName), values);
    977 
    978             // Pass back result to original caller
    979             setResult(resultCode, data);
    980             finish();
    981         } else {
    982             super.onActivityResult(requestCode, resultCode, data);
    983         }
    984     }
    985 
    986     public void onDocumentPicked(DocumentInfo doc) {
    987         final FragmentManager fm = getFragmentManager();
    988         if (doc.isDirectory()) {
    989             mState.stack.push(doc);
    990             mState.stackTouched = true;
    991             onCurrentDirectoryChanged(ANIM_DOWN);
    992         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
    993             // Explicit file picked, return
    994             new ExistingFinishTask(doc.derivedUri).executeOnExecutor(getCurrentExecutor());
    995         } else if (mState.action == ACTION_CREATE) {
    996             // Replace selected file
    997             SaveFragment.get(fm).setReplaceTarget(doc);
    998         } else if (mState.action == ACTION_MANAGE) {
    999             // First try managing the document; we expect manager to filter
   1000             // based on authority, so we don't grant.
   1001             final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
   1002             manage.setData(doc.derivedUri);
   1003 
   1004             try {
   1005                 startActivity(manage);
   1006             } catch (ActivityNotFoundException ex) {
   1007                 // Fall back to viewing
   1008                 final Intent view = new Intent(Intent.ACTION_VIEW);
   1009                 view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
   1010                 view.setData(doc.derivedUri);
   1011 
   1012                 try {
   1013                     startActivity(view);
   1014                 } catch (ActivityNotFoundException ex2) {
   1015                     Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show();
   1016                 }
   1017             }
   1018         }
   1019     }
   1020 
   1021     public void onDocumentsPicked(List<DocumentInfo> docs) {
   1022         if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
   1023             final int size = docs.size();
   1024             final Uri[] uris = new Uri[size];
   1025             for (int i = 0; i < size; i++) {
   1026                 uris[i] = docs.get(i).derivedUri;
   1027             }
   1028             new ExistingFinishTask(uris).executeOnExecutor(getCurrentExecutor());
   1029         }
   1030     }
   1031 
   1032     public void onSaveRequested(DocumentInfo replaceTarget) {
   1033         new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getCurrentExecutor());
   1034     }
   1035 
   1036     public void onSaveRequested(String mimeType, String displayName) {
   1037         new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor());
   1038     }
   1039 
   1040     public void onPickRequested(DocumentInfo pickTarget) {
   1041         final Uri viaUri = DocumentsContract.buildTreeDocumentUri(pickTarget.authority,
   1042                 pickTarget.documentId);
   1043         new PickFinishTask(viaUri).executeOnExecutor(getCurrentExecutor());
   1044     }
   1045 
   1046     private void saveStackBlocking() {
   1047         final ContentResolver resolver = getContentResolver();
   1048         final ContentValues values = new ContentValues();
   1049 
   1050         final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
   1051         if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) {
   1052             // Remember stack for last create
   1053             values.clear();
   1054             values.put(RecentColumns.KEY, mState.stack.buildKey());
   1055             values.put(RecentColumns.STACK, rawStack);
   1056             resolver.insert(RecentsProvider.buildRecent(), values);
   1057         }
   1058 
   1059         // Remember location for next app launch
   1060         final String packageName = getCallingPackageMaybeExtra();
   1061         values.clear();
   1062         values.put(ResumeColumns.STACK, rawStack);
   1063         values.put(ResumeColumns.EXTERNAL, 0);
   1064         resolver.insert(RecentsProvider.buildResume(packageName), values);
   1065     }
   1066 
   1067     private void onFinished(Uri... uris) {
   1068         Log.d(TAG, "onFinished() " + Arrays.toString(uris));
   1069 
   1070         final Intent intent = new Intent();
   1071         if (uris.length == 1) {
   1072             intent.setData(uris[0]);
   1073         } else if (uris.length > 1) {
   1074             final ClipData clipData = new ClipData(
   1075                     null, mState.acceptMimes, new ClipData.Item(uris[0]));
   1076             for (int i = 1; i < uris.length; i++) {
   1077                 clipData.addItem(new ClipData.Item(uris[i]));
   1078             }
   1079             intent.setClipData(clipData);
   1080         }
   1081 
   1082         if (mState.action == ACTION_GET_CONTENT) {
   1083             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
   1084         } else if (mState.action == ACTION_OPEN_TREE) {
   1085             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
   1086                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
   1087                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
   1088                     | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
   1089         } else {
   1090             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
   1091                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
   1092                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
   1093         }
   1094 
   1095         setResult(Activity.RESULT_OK, intent);
   1096         finish();
   1097     }
   1098 
   1099     private class CreateFinishTask extends AsyncTask<Void, Void, Uri> {
   1100         private final String mMimeType;
   1101         private final String mDisplayName;
   1102 
   1103         public CreateFinishTask(String mimeType, String displayName) {
   1104             mMimeType = mimeType;
   1105             mDisplayName = displayName;
   1106         }
   1107 
   1108         @Override
   1109         protected void onPreExecute() {
   1110             setPending(true);
   1111         }
   1112 
   1113         @Override
   1114         protected Uri doInBackground(Void... params) {
   1115             final ContentResolver resolver = getContentResolver();
   1116             final DocumentInfo cwd = getCurrentDirectory();
   1117 
   1118             ContentProviderClient client = null;
   1119             Uri childUri = null;
   1120             try {
   1121                 client = DocumentsApplication.acquireUnstableProviderOrThrow(
   1122                         resolver, cwd.derivedUri.getAuthority());
   1123                 childUri = DocumentsContract.createDocument(
   1124                         client, cwd.derivedUri, mMimeType, mDisplayName);
   1125             } catch (Exception e) {
   1126                 Log.w(TAG, "Failed to create document", e);
   1127             } finally {
   1128                 ContentProviderClient.releaseQuietly(client);
   1129             }
   1130 
   1131             if (childUri != null) {
   1132                 saveStackBlocking();
   1133             }
   1134 
   1135             return childUri;
   1136         }
   1137 
   1138         @Override
   1139         protected void onPostExecute(Uri result) {
   1140             if (result != null) {
   1141                 onFinished(result);
   1142             } else {
   1143                 Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT)
   1144                         .show();
   1145             }
   1146 
   1147             setPending(false);
   1148         }
   1149     }
   1150 
   1151     private class ExistingFinishTask extends AsyncTask<Void, Void, Void> {
   1152         private final Uri[] mUris;
   1153 
   1154         public ExistingFinishTask(Uri... uris) {
   1155             mUris = uris;
   1156         }
   1157 
   1158         @Override
   1159         protected Void doInBackground(Void... params) {
   1160             saveStackBlocking();
   1161             return null;
   1162         }
   1163 
   1164         @Override
   1165         protected void onPostExecute(Void result) {
   1166             onFinished(mUris);
   1167         }
   1168     }
   1169 
   1170     private class PickFinishTask extends AsyncTask<Void, Void, Void> {
   1171         private final Uri mUri;
   1172 
   1173         public PickFinishTask(Uri uri) {
   1174             mUri = uri;
   1175         }
   1176 
   1177         @Override
   1178         protected Void doInBackground(Void... params) {
   1179             saveStackBlocking();
   1180             return null;
   1181         }
   1182 
   1183         @Override
   1184         protected void onPostExecute(Void result) {
   1185             onFinished(mUri);
   1186         }
   1187     }
   1188 
   1189     public static class State implements android.os.Parcelable {
   1190         public int action;
   1191         public String[] acceptMimes;
   1192 
   1193         /** Explicit user choice */
   1194         public int userMode = MODE_UNKNOWN;
   1195         /** Derived after loader */
   1196         public int derivedMode = MODE_LIST;
   1197 
   1198         /** Explicit user choice */
   1199         public int userSortOrder = SORT_ORDER_UNKNOWN;
   1200         /** Derived after loader */
   1201         public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME;
   1202 
   1203         public boolean allowMultiple = false;
   1204         public boolean showSize = false;
   1205         public boolean localOnly = false;
   1206         public boolean forceAdvanced = false;
   1207         public boolean showAdvanced = false;
   1208         public boolean stackTouched = false;
   1209         public boolean restored = false;
   1210 
   1211         /** Current user navigation stack; empty implies recents. */
   1212         public DocumentStack stack = new DocumentStack();
   1213         /** Currently active search, overriding any stack. */
   1214         public String currentSearch;
   1215 
   1216         /** Instance state for every shown directory */
   1217         public HashMap<String, SparseArray<Parcelable>> dirState = Maps.newHashMap();
   1218 
   1219         public static final int ACTION_OPEN = 1;
   1220         public static final int ACTION_CREATE = 2;
   1221         public static final int ACTION_GET_CONTENT = 3;
   1222         public static final int ACTION_OPEN_TREE = 4;
   1223         public static final int ACTION_MANAGE = 5;
   1224 
   1225         public static final int MODE_UNKNOWN = 0;
   1226         public static final int MODE_LIST = 1;
   1227         public static final int MODE_GRID = 2;
   1228 
   1229         public static final int SORT_ORDER_UNKNOWN = 0;
   1230         public static final int SORT_ORDER_DISPLAY_NAME = 1;
   1231         public static final int SORT_ORDER_LAST_MODIFIED = 2;
   1232         public static final int SORT_ORDER_SIZE = 3;
   1233 
   1234         @Override
   1235         public int describeContents() {
   1236             return 0;
   1237         }
   1238 
   1239         @Override
   1240         public void writeToParcel(Parcel out, int flags) {
   1241             out.writeInt(action);
   1242             out.writeInt(userMode);
   1243             out.writeStringArray(acceptMimes);
   1244             out.writeInt(userSortOrder);
   1245             out.writeInt(allowMultiple ? 1 : 0);
   1246             out.writeInt(showSize ? 1 : 0);
   1247             out.writeInt(localOnly ? 1 : 0);
   1248             out.writeInt(forceAdvanced ? 1 : 0);
   1249             out.writeInt(showAdvanced ? 1 : 0);
   1250             out.writeInt(stackTouched ? 1 : 0);
   1251             out.writeInt(restored ? 1 : 0);
   1252             DurableUtils.writeToParcel(out, stack);
   1253             out.writeString(currentSearch);
   1254             out.writeMap(dirState);
   1255         }
   1256 
   1257         public static final Creator<State> CREATOR = new Creator<State>() {
   1258             @Override
   1259             public State createFromParcel(Parcel in) {
   1260                 final State state = new State();
   1261                 state.action = in.readInt();
   1262                 state.userMode = in.readInt();
   1263                 state.acceptMimes = in.readStringArray();
   1264                 state.userSortOrder = in.readInt();
   1265                 state.allowMultiple = in.readInt() != 0;
   1266                 state.showSize = in.readInt() != 0;
   1267                 state.localOnly = in.readInt() != 0;
   1268                 state.forceAdvanced = in.readInt() != 0;
   1269                 state.showAdvanced = in.readInt() != 0;
   1270                 state.stackTouched = in.readInt() != 0;
   1271                 state.restored = in.readInt() != 0;
   1272                 DurableUtils.readFromParcel(in, state.stack);
   1273                 state.currentSearch = in.readString();
   1274                 in.readMap(state.dirState, null);
   1275                 return state;
   1276             }
   1277 
   1278             @Override
   1279             public State[] newArray(int size) {
   1280                 return new State[size];
   1281             }
   1282         };
   1283     }
   1284 
   1285     private void dumpStack() {
   1286         Log.d(TAG, "Current stack: ");
   1287         Log.d(TAG, " * " + mState.stack.root);
   1288         for (DocumentInfo doc : mState.stack) {
   1289             Log.d(TAG, " +-- " + doc);
   1290         }
   1291     }
   1292 
   1293     public static DocumentsActivity get(Fragment fragment) {
   1294         return (DocumentsActivity) fragment.getActivity();
   1295     }
   1296 }
   1297