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         updateActionBar();
    390     }
    391 
    392     public void setRootsDrawerOpen(boolean open) {
    393         if (!mShowAsDialog) {
    394             if (open) {
    395                 mDrawerLayout.openDrawer(mRootsDrawer);
    396             } else {
    397                 mDrawerLayout.closeDrawer(mRootsDrawer);
    398             }
    399         }
    400     }
    401 
    402     private boolean isRootsDrawerOpen() {
    403         if (mShowAsDialog) {
    404             return false;
    405         } else {
    406             return mDrawerLayout.isDrawerOpen(mRootsDrawer);
    407         }
    408     }
    409 
    410     public void updateActionBar() {
    411         if (mRootsToolbar != null) {
    412             if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT
    413                     || mState.action == ACTION_OPEN_TREE) {
    414                 mRootsToolbar.setTitle(R.string.title_open);
    415             } else if (mState.action == ACTION_CREATE) {
    416                 mRootsToolbar.setTitle(R.string.title_save);
    417             }
    418         }
    419 
    420         final RootInfo root = getCurrentRoot();
    421         final boolean showRootIcon = mShowAsDialog || (mState.action == ACTION_MANAGE);
    422         if (showRootIcon) {
    423             mToolbar.setNavigationIcon(
    424                     root != null ? root.loadToolbarIcon(mToolbar.getContext()) : null);
    425             mToolbar.setNavigationContentDescription(R.string.drawer_open);
    426             mToolbar.setNavigationOnClickListener(null);
    427         } else {
    428             mToolbar.setNavigationIcon(R.drawable.ic_hamburger);
    429             mToolbar.setNavigationContentDescription(R.string.drawer_open);
    430             mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
    431                 @Override
    432                 public void onClick(View v) {
    433                     setRootsDrawerOpen(true);
    434                 }
    435             });
    436         }
    437 
    438         if (mSearchExpanded) {
    439             mToolbar.setTitle(null);
    440             mToolbarStack.setVisibility(View.GONE);
    441             mToolbarStack.setAdapter(null);
    442         } else {
    443             if (mState.stack.size() <= 1) {
    444                 mToolbar.setTitle(root.title);
    445                 mToolbarStack.setVisibility(View.GONE);
    446                 mToolbarStack.setAdapter(null);
    447             } else {
    448                 mToolbar.setTitle(null);
    449                 mToolbarStack.setVisibility(View.VISIBLE);
    450                 mToolbarStack.setAdapter(mStackAdapter);
    451 
    452                 mIgnoreNextNavigation = true;
    453                 mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
    454             }
    455         }
    456     }
    457 
    458     @Override
    459     public boolean onCreateOptionsMenu(Menu menu) {
    460         super.onCreateOptionsMenu(menu);
    461         getMenuInflater().inflate(R.menu.activity, menu);
    462 
    463         // Most actions are visible when showing as dialog
    464         if (mShowAsDialog) {
    465             for (int i = 0; i < menu.size(); i++) {
    466                 final MenuItem item = menu.getItem(i);
    467                 switch (item.getItemId()) {
    468                     case R.id.menu_advanced:
    469                     case R.id.menu_file_size:
    470                         break;
    471                     default:
    472                         item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
    473                 }
    474             }
    475         }
    476 
    477         final MenuItem searchMenu = menu.findItem(R.id.menu_search);
    478         mSearchView = (SearchView) searchMenu.getActionView();
    479         mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
    480             @Override
    481             public boolean onQueryTextSubmit(String query) {
    482                 mSearchExpanded = true;
    483                 mState.currentSearch = query;
    484                 mSearchView.clearFocus();
    485                 onCurrentDirectoryChanged(ANIM_NONE);
    486                 return true;
    487             }
    488 
    489             @Override
    490             public boolean onQueryTextChange(String newText) {
    491                 return false;
    492             }
    493         });
    494 
    495         searchMenu.setOnActionExpandListener(new OnActionExpandListener() {
    496             @Override
    497             public boolean onMenuItemActionExpand(MenuItem item) {
    498                 mSearchExpanded = true;
    499                 updateActionBar();
    500                 return true;
    501             }
    502 
    503             @Override
    504             public boolean onMenuItemActionCollapse(MenuItem item) {
    505                 mSearchExpanded = false;
    506                 if (mIgnoreNextCollapse) {
    507                     mIgnoreNextCollapse = false;
    508                     return true;
    509                 }
    510 
    511                 mState.currentSearch = null;
    512                 onCurrentDirectoryChanged(ANIM_NONE);
    513                 return true;
    514             }
    515         });
    516 
    517         mSearchView.setOnCloseListener(new SearchView.OnCloseListener() {
    518             @Override
    519             public boolean onClose() {
    520                 mSearchExpanded = false;
    521                 if (mIgnoreNextClose) {
    522                     mIgnoreNextClose = false;
    523                     return false;
    524                 }
    525 
    526                 mState.currentSearch = null;
    527                 onCurrentDirectoryChanged(ANIM_NONE);
    528                 return false;
    529             }
    530         });
    531 
    532         return true;
    533     }
    534 
    535     @Override
    536     public boolean onPrepareOptionsMenu(Menu menu) {
    537         super.onPrepareOptionsMenu(menu);
    538 
    539         final FragmentManager fm = getFragmentManager();
    540 
    541         final RootInfo root = getCurrentRoot();
    542         final DocumentInfo cwd = getCurrentDirectory();
    543 
    544         final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
    545         final MenuItem search = menu.findItem(R.id.menu_search);
    546         final MenuItem sort = menu.findItem(R.id.menu_sort);
    547         final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
    548         final MenuItem grid = menu.findItem(R.id.menu_grid);
    549         final MenuItem list = menu.findItem(R.id.menu_list);
    550         final MenuItem advanced = menu.findItem(R.id.menu_advanced);
    551         final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
    552 
    553         sort.setVisible(cwd != null);
    554         grid.setVisible(mState.derivedMode != MODE_GRID);
    555         list.setVisible(mState.derivedMode != MODE_LIST);
    556 
    557         if (mState.currentSearch != null) {
    558             // Search uses backend ranking; no sorting
    559             sort.setVisible(false);
    560 
    561             search.expandActionView();
    562 
    563             mSearchView.setIconified(false);
    564             mSearchView.clearFocus();
    565             mSearchView.setQuery(mState.currentSearch, false);
    566         } else {
    567             mIgnoreNextClose = true;
    568             mSearchView.setIconified(true);
    569             mSearchView.clearFocus();
    570 
    571             mIgnoreNextCollapse = true;
    572             search.collapseActionView();
    573         }
    574 
    575         // Only sort by size when visible
    576         sortSize.setVisible(mState.showSize);
    577 
    578         boolean searchVisible;
    579         boolean fileSizeVisible = mState.action != ACTION_MANAGE;
    580         if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) {
    581             createDir.setVisible(cwd != null && cwd.isCreateSupported());
    582             searchVisible = false;
    583 
    584             // No display options in recent directories
    585             if (cwd == null) {
    586                 grid.setVisible(false);
    587                 list.setVisible(false);
    588                 fileSizeVisible = false;
    589             }
    590 
    591             if (mState.action == ACTION_CREATE) {
    592                 SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
    593             }
    594         } else {
    595             createDir.setVisible(false);
    596 
    597             searchVisible = root != null
    598                     && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0);
    599         }
    600 
    601         // TODO: close any search in-progress when hiding
    602         search.setVisible(searchVisible);
    603 
    604         advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
    605                 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
    606         fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
    607                 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
    608 
    609         advanced.setVisible(mState.action != ACTION_MANAGE);
    610         fileSize.setVisible(fileSizeVisible);
    611 
    612         return true;
    613     }
    614 
    615     @Override
    616     public boolean onOptionsItemSelected(MenuItem item) {
    617         if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
    618             return true;
    619         }
    620 
    621         final int id = item.getItemId();
    622         if (id == android.R.id.home) {
    623             onBackPressed();
    624             return true;
    625         } else if (id == R.id.menu_create_dir) {
    626             CreateDirectoryFragment.show(getFragmentManager());
    627             return true;
    628         } else if (id == R.id.menu_search) {
    629             return false;
    630         } else if (id == R.id.menu_sort_name) {
    631             setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
    632             return true;
    633         } else if (id == R.id.menu_sort_date) {
    634             setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
    635             return true;
    636         } else if (id == R.id.menu_sort_size) {
    637             setUserSortOrder(State.SORT_ORDER_SIZE);
    638             return true;
    639         } else if (id == R.id.menu_grid) {
    640             setUserMode(State.MODE_GRID);
    641             return true;
    642         } else if (id == R.id.menu_list) {
    643             setUserMode(State.MODE_LIST);
    644             return true;
    645         } else if (id == R.id.menu_advanced) {
    646             setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this));
    647             return true;
    648         } else if (id == R.id.menu_file_size) {
    649             setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
    650             return true;
    651         } else {
    652             return super.onOptionsItemSelected(item);
    653         }
    654     }
    655 
    656     private void setDisplayAdvancedDevices(boolean display) {
    657         LocalPreferences.setDisplayAdvancedDevices(this, display);
    658         mState.showAdvanced = mState.forceAdvanced | display;
    659         RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
    660         invalidateOptionsMenu();
    661     }
    662 
    663     private void setDisplayFileSize(boolean display) {
    664         LocalPreferences.setDisplayFileSize(this, display);
    665         mState.showSize = display;
    666         DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
    667         invalidateOptionsMenu();
    668     }
    669 
    670     /**
    671      * Update UI to reflect internal state changes not from user.
    672      */
    673     public void onStateChanged() {
    674         invalidateOptionsMenu();
    675     }
    676 
    677     /**
    678      * Set state sort order based on explicit user action.
    679      */
    680     private void setUserSortOrder(int sortOrder) {
    681         mState.userSortOrder = sortOrder;
    682         DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
    683     }
    684 
    685     /**
    686      * Set state mode based on explicit user action.
    687      */
    688     private void setUserMode(int mode) {
    689         mState.userMode = mode;
    690         DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
    691     }
    692 
    693     public void setPending(boolean pending) {
    694         final SaveFragment save = SaveFragment.get(getFragmentManager());
    695         if (save != null) {
    696             save.setPending(pending);
    697         }
    698     }
    699 
    700     @Override
    701     public void onBackPressed() {
    702         if (!mState.stackTouched) {
    703             super.onBackPressed();
    704             return;
    705         }
    706 
    707         final int size = mState.stack.size();
    708         if (size > 1) {
    709             mState.stack.pop();
    710             onCurrentDirectoryChanged(ANIM_UP);
    711         } else if (size == 1 && !isRootsDrawerOpen()) {
    712             // TODO: open root drawer once we can capture back key
    713             super.onBackPressed();
    714         } else {
    715             super.onBackPressed();
    716         }
    717     }
    718 
    719     @Override
    720     protected void onSaveInstanceState(Bundle state) {
    721         super.onSaveInstanceState(state);
    722         state.putParcelable(EXTRA_STATE, mState);
    723     }
    724 
    725     @Override
    726     protected void onRestoreInstanceState(Bundle state) {
    727         super.onRestoreInstanceState(state);
    728     }
    729 
    730     private BaseAdapter mStackAdapter = new BaseAdapter() {
    731         @Override
    732         public int getCount() {
    733             return mState.stack.size();
    734         }
    735 
    736         @Override
    737         public DocumentInfo getItem(int position) {
    738             return mState.stack.get(mState.stack.size() - position - 1);
    739         }
    740 
    741         @Override
    742         public long getItemId(int position) {
    743             return position;
    744         }
    745 
    746         @Override
    747         public View getView(int position, View convertView, ViewGroup parent) {
    748             if (convertView == null) {
    749                 convertView = LayoutInflater.from(parent.getContext())
    750                         .inflate(R.layout.item_subdir_title, parent, false);
    751             }
    752 
    753             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
    754             final DocumentInfo doc = getItem(position);
    755 
    756             if (position == 0) {
    757                 final RootInfo root = getCurrentRoot();
    758                 title.setText(root.title);
    759             } else {
    760                 title.setText(doc.displayName);
    761             }
    762 
    763             return convertView;
    764         }
    765 
    766         @Override
    767         public View getDropDownView(int position, View convertView, ViewGroup parent) {
    768             if (convertView == null) {
    769                 convertView = LayoutInflater.from(parent.getContext())
    770                         .inflate(R.layout.item_subdir, parent, false);
    771             }
    772 
    773             final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
    774             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
    775             final DocumentInfo doc = getItem(position);
    776 
    777             if (position == 0) {
    778                 final RootInfo root = getCurrentRoot();
    779                 title.setText(root.title);
    780                 subdir.setVisibility(View.GONE);
    781             } else {
    782                 title.setText(doc.displayName);
    783                 subdir.setVisibility(View.VISIBLE);
    784             }
    785 
    786             return convertView;
    787         }
    788     };
    789 
    790     private OnItemSelectedListener mStackListener = new OnItemSelectedListener() {
    791         @Override
    792         public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    793             if (mIgnoreNextNavigation) {
    794                 mIgnoreNextNavigation = false;
    795                 return;
    796             }
    797 
    798             while (mState.stack.size() > position + 1) {
    799                 mState.stackTouched = true;
    800                 mState.stack.pop();
    801             }
    802             onCurrentDirectoryChanged(ANIM_UP);
    803         }
    804 
    805         @Override
    806         public void onNothingSelected(AdapterView<?> parent) {
    807             // Ignored
    808         }
    809     };
    810 
    811     public RootInfo getCurrentRoot() {
    812         if (mState.stack.root != null) {
    813             return mState.stack.root;
    814         } else {
    815             return mRoots.getRecentsRoot();
    816         }
    817     }
    818 
    819     public DocumentInfo getCurrentDirectory() {
    820         return mState.stack.peek();
    821     }
    822 
    823     private String getCallingPackageMaybeExtra() {
    824         final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
    825         return (extra != null) ? extra : getCallingPackage();
    826     }
    827 
    828     public Executor getCurrentExecutor() {
    829         final DocumentInfo cwd = getCurrentDirectory();
    830         if (cwd != null && cwd.authority != null) {
    831             return ProviderExecutor.forAuthority(cwd.authority);
    832         } else {
    833             return AsyncTask.THREAD_POOL_EXECUTOR;
    834         }
    835     }
    836 
    837     public State getDisplayState() {
    838         return mState;
    839     }
    840 
    841     private void onCurrentDirectoryChanged(int anim) {
    842         final FragmentManager fm = getFragmentManager();
    843         final RootInfo root = getCurrentRoot();
    844         final DocumentInfo cwd = getCurrentDirectory();
    845 
    846         mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN);
    847 
    848         if (cwd == null) {
    849             // No directory means recents
    850             if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) {
    851                 RecentsCreateFragment.show(fm);
    852             } else {
    853                 DirectoryFragment.showRecentsOpen(fm, anim);
    854 
    855                 // Start recents in grid when requesting visual things
    856                 final boolean visualMimes = MimePredicate.mimeMatches(
    857                         MimePredicate.VISUAL_MIMES, mState.acceptMimes);
    858                 mState.userMode = visualMimes ? MODE_GRID : MODE_LIST;
    859                 mState.derivedMode = mState.userMode;
    860             }
    861         } else {
    862             if (mState.currentSearch != null) {
    863                 // Ongoing search
    864                 DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
    865             } else {
    866                 // Normal boring directory
    867                 DirectoryFragment.showNormal(fm, root, cwd, anim);
    868             }
    869         }
    870 
    871         // Forget any replacement target
    872         if (mState.action == ACTION_CREATE) {
    873             final SaveFragment save = SaveFragment.get(fm);
    874             if (save != null) {
    875                 save.setReplaceTarget(null);
    876             }
    877         }
    878 
    879         if (mState.action == ACTION_OPEN_TREE) {
    880             final PickFragment pick = PickFragment.get(fm);
    881             if (pick != null) {
    882                 final CharSequence displayName = (mState.stack.size() <= 1) ? root.title
    883                         : cwd.displayName;
    884                 pick.setPickTarget(cwd, displayName);
    885             }
    886         }
    887 
    888         final RootsFragment roots = RootsFragment.get(fm);
    889         if (roots != null) {
    890             roots.onCurrentRootChanged();
    891         }
    892 
    893         updateActionBar();
    894         invalidateOptionsMenu();
    895         dumpStack();
    896     }
    897 
    898     public void onStackPicked(DocumentStack stack) {
    899         try {
    900             // Update the restored stack to ensure we have freshest data
    901             stack.updateDocuments(getContentResolver());
    902 
    903             mState.stack = stack;
    904             mState.stackTouched = true;
    905             onCurrentDirectoryChanged(ANIM_SIDE);
    906 
    907         } catch (FileNotFoundException e) {
    908             Log.w(TAG, "Failed to restore stack: " + e);
    909         }
    910     }
    911 
    912     public void onRootPicked(RootInfo root, boolean closeDrawer) {
    913         // Clear entire backstack and start in new root
    914         mState.stack.root = root;
    915         mState.stack.clear();
    916         mState.stackTouched = true;
    917 
    918         if (!mRoots.isRecentsRoot(root)) {
    919             new PickRootTask(root).executeOnExecutor(getCurrentExecutor());
    920         } else {
    921             onCurrentDirectoryChanged(ANIM_SIDE);
    922         }
    923 
    924         if (closeDrawer) {
    925             setRootsDrawerOpen(false);
    926         }
    927     }
    928 
    929     private class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
    930         private RootInfo mRoot;
    931 
    932         public PickRootTask(RootInfo root) {
    933             mRoot = root;
    934         }
    935 
    936         @Override
    937         protected DocumentInfo doInBackground(Void... params) {
    938             try {
    939                 final Uri uri = DocumentsContract.buildDocumentUri(
    940                         mRoot.authority, mRoot.documentId);
    941                 return DocumentInfo.fromUri(getContentResolver(), uri);
    942             } catch (FileNotFoundException e) {
    943                 Log.w(TAG, "Failed to find root", e);
    944                 return null;
    945             }
    946         }
    947 
    948         @Override
    949         protected void onPostExecute(DocumentInfo result) {
    950             if (result != null) {
    951                 mState.stack.push(result);
    952                 mState.stackTouched = true;
    953                 onCurrentDirectoryChanged(ANIM_SIDE);
    954             }
    955         }
    956     }
    957 
    958     public void onAppPicked(ResolveInfo info) {
    959         final Intent intent = new Intent(getIntent());
    960         intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    961         intent.setComponent(new ComponentName(
    962                 info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
    963         startActivityForResult(intent, CODE_FORWARD);
    964     }
    965 
    966     @Override
    967     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    968         Log.d(TAG, "onActivityResult() code=" + resultCode);
    969 
    970         // Only relay back results when not canceled; otherwise stick around to
    971         // let the user pick another app/backend.
    972         if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
    973 
    974             // Remember that we last picked via external app
    975             final String packageName = getCallingPackageMaybeExtra();
    976             final ContentValues values = new ContentValues();
    977             values.put(ResumeColumns.EXTERNAL, 1);
    978             getContentResolver().insert(RecentsProvider.buildResume(packageName), values);
    979 
    980             // Pass back result to original caller
    981             setResult(resultCode, data);
    982             finish();
    983         } else {
    984             super.onActivityResult(requestCode, resultCode, data);
    985         }
    986     }
    987 
    988     public void onDocumentPicked(DocumentInfo doc) {
    989         final FragmentManager fm = getFragmentManager();
    990         if (doc.isDirectory()) {
    991             mState.stack.push(doc);
    992             mState.stackTouched = true;
    993             onCurrentDirectoryChanged(ANIM_DOWN);
    994         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
    995             // Explicit file picked, return
    996             new ExistingFinishTask(doc.derivedUri).executeOnExecutor(getCurrentExecutor());
    997         } else if (mState.action == ACTION_CREATE) {
    998             // Replace selected file
    999             SaveFragment.get(fm).setReplaceTarget(doc);
   1000         } else if (mState.action == ACTION_MANAGE) {
   1001             // First try managing the document; we expect manager to filter
   1002             // based on authority, so we don't grant.
   1003             final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
   1004             manage.setData(doc.derivedUri);
   1005 
   1006             try {
   1007                 startActivity(manage);
   1008             } catch (ActivityNotFoundException ex) {
   1009                 // Fall back to viewing
   1010                 final Intent view = new Intent(Intent.ACTION_VIEW);
   1011                 view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
   1012                 view.setData(doc.derivedUri);
   1013 
   1014                 try {
   1015                     startActivity(view);
   1016                 } catch (ActivityNotFoundException ex2) {
   1017                     Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show();
   1018                 }
   1019             }
   1020         }
   1021     }
   1022 
   1023     public void onDocumentsPicked(List<DocumentInfo> docs) {
   1024         if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
   1025             final int size = docs.size();
   1026             final Uri[] uris = new Uri[size];
   1027             for (int i = 0; i < size; i++) {
   1028                 uris[i] = docs.get(i).derivedUri;
   1029             }
   1030             new ExistingFinishTask(uris).executeOnExecutor(getCurrentExecutor());
   1031         }
   1032     }
   1033 
   1034     public void onSaveRequested(DocumentInfo replaceTarget) {
   1035         new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getCurrentExecutor());
   1036     }
   1037 
   1038     public void onSaveRequested(String mimeType, String displayName) {
   1039         new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor());
   1040     }
   1041 
   1042     public void onPickRequested(DocumentInfo pickTarget) {
   1043         final Uri viaUri = DocumentsContract.buildTreeDocumentUri(pickTarget.authority,
   1044                 pickTarget.documentId);
   1045         new PickFinishTask(viaUri).executeOnExecutor(getCurrentExecutor());
   1046     }
   1047 
   1048     private void saveStackBlocking() {
   1049         final ContentResolver resolver = getContentResolver();
   1050         final ContentValues values = new ContentValues();
   1051 
   1052         final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
   1053         if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE) {
   1054             // Remember stack for last create
   1055             values.clear();
   1056             values.put(RecentColumns.KEY, mState.stack.buildKey());
   1057             values.put(RecentColumns.STACK, rawStack);
   1058             resolver.insert(RecentsProvider.buildRecent(), values);
   1059         }
   1060 
   1061         // Remember location for next app launch
   1062         final String packageName = getCallingPackageMaybeExtra();
   1063         values.clear();
   1064         values.put(ResumeColumns.STACK, rawStack);
   1065         values.put(ResumeColumns.EXTERNAL, 0);
   1066         resolver.insert(RecentsProvider.buildResume(packageName), values);
   1067     }
   1068 
   1069     private void onFinished(Uri... uris) {
   1070         Log.d(TAG, "onFinished() " + Arrays.toString(uris));
   1071 
   1072         final Intent intent = new Intent();
   1073         if (uris.length == 1) {
   1074             intent.setData(uris[0]);
   1075         } else if (uris.length > 1) {
   1076             final ClipData clipData = new ClipData(
   1077                     null, mState.acceptMimes, new ClipData.Item(uris[0]));
   1078             for (int i = 1; i < uris.length; i++) {
   1079                 clipData.addItem(new ClipData.Item(uris[i]));
   1080             }
   1081             intent.setClipData(clipData);
   1082         }
   1083 
   1084         if (mState.action == ACTION_GET_CONTENT) {
   1085             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
   1086         } else if (mState.action == ACTION_OPEN_TREE) {
   1087             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
   1088                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
   1089                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
   1090                     | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
   1091         } else {
   1092             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
   1093                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
   1094                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
   1095         }
   1096 
   1097         setResult(Activity.RESULT_OK, intent);
   1098         finish();
   1099     }
   1100 
   1101     private class CreateFinishTask extends AsyncTask<Void, Void, Uri> {
   1102         private final String mMimeType;
   1103         private final String mDisplayName;
   1104 
   1105         public CreateFinishTask(String mimeType, String displayName) {
   1106             mMimeType = mimeType;
   1107             mDisplayName = displayName;
   1108         }
   1109 
   1110         @Override
   1111         protected void onPreExecute() {
   1112             setPending(true);
   1113         }
   1114 
   1115         @Override
   1116         protected Uri doInBackground(Void... params) {
   1117             final ContentResolver resolver = getContentResolver();
   1118             final DocumentInfo cwd = getCurrentDirectory();
   1119 
   1120             ContentProviderClient client = null;
   1121             Uri childUri = null;
   1122             try {
   1123                 client = DocumentsApplication.acquireUnstableProviderOrThrow(
   1124                         resolver, cwd.derivedUri.getAuthority());
   1125                 childUri = DocumentsContract.createDocument(
   1126                         client, cwd.derivedUri, mMimeType, mDisplayName);
   1127             } catch (Exception e) {
   1128                 Log.w(TAG, "Failed to create document", e);
   1129             } finally {
   1130                 ContentProviderClient.releaseQuietly(client);
   1131             }
   1132 
   1133             if (childUri != null) {
   1134                 saveStackBlocking();
   1135             }
   1136 
   1137             return childUri;
   1138         }
   1139 
   1140         @Override
   1141         protected void onPostExecute(Uri result) {
   1142             if (result != null) {
   1143                 onFinished(result);
   1144             } else {
   1145                 Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT)
   1146                         .show();
   1147             }
   1148 
   1149             setPending(false);
   1150         }
   1151     }
   1152 
   1153     private class ExistingFinishTask extends AsyncTask<Void, Void, Void> {
   1154         private final Uri[] mUris;
   1155 
   1156         public ExistingFinishTask(Uri... uris) {
   1157             mUris = uris;
   1158         }
   1159 
   1160         @Override
   1161         protected Void doInBackground(Void... params) {
   1162             saveStackBlocking();
   1163             return null;
   1164         }
   1165 
   1166         @Override
   1167         protected void onPostExecute(Void result) {
   1168             onFinished(mUris);
   1169         }
   1170     }
   1171 
   1172     private class PickFinishTask extends AsyncTask<Void, Void, Void> {
   1173         private final Uri mUri;
   1174 
   1175         public PickFinishTask(Uri uri) {
   1176             mUri = uri;
   1177         }
   1178 
   1179         @Override
   1180         protected Void doInBackground(Void... params) {
   1181             saveStackBlocking();
   1182             return null;
   1183         }
   1184 
   1185         @Override
   1186         protected void onPostExecute(Void result) {
   1187             onFinished(mUri);
   1188         }
   1189     }
   1190 
   1191     public static class State implements android.os.Parcelable {
   1192         public int action;
   1193         public String[] acceptMimes;
   1194 
   1195         /** Explicit user choice */
   1196         public int userMode = MODE_UNKNOWN;
   1197         /** Derived after loader */
   1198         public int derivedMode = MODE_LIST;
   1199 
   1200         /** Explicit user choice */
   1201         public int userSortOrder = SORT_ORDER_UNKNOWN;
   1202         /** Derived after loader */
   1203         public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME;
   1204 
   1205         public boolean allowMultiple = false;
   1206         public boolean showSize = false;
   1207         public boolean localOnly = false;
   1208         public boolean forceAdvanced = false;
   1209         public boolean showAdvanced = false;
   1210         public boolean stackTouched = false;
   1211         public boolean restored = false;
   1212 
   1213         /** Current user navigation stack; empty implies recents. */
   1214         public DocumentStack stack = new DocumentStack();
   1215         /** Currently active search, overriding any stack. */
   1216         public String currentSearch;
   1217 
   1218         /** Instance state for every shown directory */
   1219         public HashMap<String, SparseArray<Parcelable>> dirState = Maps.newHashMap();
   1220 
   1221         public static final int ACTION_OPEN = 1;
   1222         public static final int ACTION_CREATE = 2;
   1223         public static final int ACTION_GET_CONTENT = 3;
   1224         public static final int ACTION_OPEN_TREE = 4;
   1225         public static final int ACTION_MANAGE = 5;
   1226 
   1227         public static final int MODE_UNKNOWN = 0;
   1228         public static final int MODE_LIST = 1;
   1229         public static final int MODE_GRID = 2;
   1230 
   1231         public static final int SORT_ORDER_UNKNOWN = 0;
   1232         public static final int SORT_ORDER_DISPLAY_NAME = 1;
   1233         public static final int SORT_ORDER_LAST_MODIFIED = 2;
   1234         public static final int SORT_ORDER_SIZE = 3;
   1235 
   1236         @Override
   1237         public int describeContents() {
   1238             return 0;
   1239         }
   1240 
   1241         @Override
   1242         public void writeToParcel(Parcel out, int flags) {
   1243             out.writeInt(action);
   1244             out.writeInt(userMode);
   1245             out.writeStringArray(acceptMimes);
   1246             out.writeInt(userSortOrder);
   1247             out.writeInt(allowMultiple ? 1 : 0);
   1248             out.writeInt(showSize ? 1 : 0);
   1249             out.writeInt(localOnly ? 1 : 0);
   1250             out.writeInt(forceAdvanced ? 1 : 0);
   1251             out.writeInt(showAdvanced ? 1 : 0);
   1252             out.writeInt(stackTouched ? 1 : 0);
   1253             out.writeInt(restored ? 1 : 0);
   1254             DurableUtils.writeToParcel(out, stack);
   1255             out.writeString(currentSearch);
   1256             out.writeMap(dirState);
   1257         }
   1258 
   1259         public static final Creator<State> CREATOR = new Creator<State>() {
   1260             @Override
   1261             public State createFromParcel(Parcel in) {
   1262                 final State state = new State();
   1263                 state.action = in.readInt();
   1264                 state.userMode = in.readInt();
   1265                 state.acceptMimes = in.readStringArray();
   1266                 state.userSortOrder = in.readInt();
   1267                 state.allowMultiple = in.readInt() != 0;
   1268                 state.showSize = in.readInt() != 0;
   1269                 state.localOnly = in.readInt() != 0;
   1270                 state.forceAdvanced = in.readInt() != 0;
   1271                 state.showAdvanced = in.readInt() != 0;
   1272                 state.stackTouched = in.readInt() != 0;
   1273                 state.restored = in.readInt() != 0;
   1274                 DurableUtils.readFromParcel(in, state.stack);
   1275                 state.currentSearch = in.readString();
   1276                 in.readMap(state.dirState, null);
   1277                 return state;
   1278             }
   1279 
   1280             @Override
   1281             public State[] newArray(int size) {
   1282                 return new State[size];
   1283             }
   1284         };
   1285     }
   1286 
   1287     private void dumpStack() {
   1288         Log.d(TAG, "Current stack: ");
   1289         Log.d(TAG, " * " + mState.stack.root);
   1290         for (DocumentInfo doc : mState.stack) {
   1291             Log.d(TAG, " +-- " + doc);
   1292         }
   1293     }
   1294 
   1295     public static DocumentsActivity get(Fragment fragment) {
   1296         return (DocumentsActivity) fragment.getActivity();
   1297     }
   1298 }
   1299