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.BaseActivity.State.ACTION_BROWSE;
20 import static com.android.documentsui.BaseActivity.State.ACTION_CREATE;
21 import static com.android.documentsui.BaseActivity.State.ACTION_GET_CONTENT;
22 import static com.android.documentsui.BaseActivity.State.ACTION_MANAGE;
23 import static com.android.documentsui.BaseActivity.State.ACTION_OPEN;
24 import static com.android.documentsui.BaseActivity.State.ACTION_OPEN_COPY_DESTINATION;
25 import static com.android.documentsui.BaseActivity.State.ACTION_OPEN_TREE;
26 import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
27 import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
28 import static com.android.documentsui.DirectoryFragment.ANIM_UP;
29 
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 
34 import android.app.ActionBar;
35 import android.app.Activity;
36 import android.app.Fragment;
37 import android.app.FragmentManager;
38 import android.content.ActivityNotFoundException;
39 import android.content.ClipData;
40 import android.content.ComponentName;
41 import android.content.ContentProviderClient;
42 import android.content.ContentResolver;
43 import android.content.ContentValues;
44 import android.content.Context;
45 import android.content.Intent;
46 import android.content.pm.ResolveInfo;
47 import android.content.res.Resources;
48 import android.graphics.Point;
49 import android.net.Uri;
50 import android.os.AsyncTask;
51 import android.os.Bundle;
52 import android.os.Parcelable;
53 import android.provider.DocumentsContract;
54 import android.provider.DocumentsContract.Root;
55 import android.support.v4.app.ActionBarDrawerToggle;
56 import android.support.v4.widget.DrawerLayout;
57 import android.support.v4.widget.DrawerLayout.DrawerListener;
58 import android.util.Log;
59 import android.view.Menu;
60 import android.view.MenuItem;
61 import android.view.View;
62 import android.view.WindowManager;
63 import android.widget.BaseAdapter;
64 import android.widget.Spinner;
65 import android.widget.Toast;
66 import android.widget.Toolbar;
67 
68 import com.android.documentsui.RecentsProvider.RecentColumns;
69 import com.android.documentsui.RecentsProvider.ResumeColumns;
70 import com.android.documentsui.model.DocumentInfo;
71 import com.android.documentsui.model.DocumentStack;
72 import com.android.documentsui.model.DurableUtils;
73 import com.android.documentsui.model.RootInfo;
74 
75 public class DocumentsActivity extends BaseActivity {
76     private static final int CODE_FORWARD = 42;
77     public static final String TAG = "Documents";
78 
79     private boolean mShowAsDialog;
80 
81     private Toolbar mToolbar;
82     private Spinner mToolbarStack;
83 
84     private Toolbar mRootsToolbar;
85 
86     private DrawerLayout mDrawerLayout;
87     private ActionBarDrawerToggle mDrawerToggle;
88     private View mRootsDrawer;
89 
90     private DirectoryContainerView mDirectoryContainer;
91 
92     private State mState;
93 
94     private ItemSelectedListener mStackListener;
95     private BaseAdapter mStackAdapter;
96 
DocumentsActivity()97     public DocumentsActivity() {
98         super(TAG);
99     }
100 
101     @Override
onCreate(Bundle icicle)102     public void onCreate(Bundle icicle) {
103         super.onCreate(icicle);
104 
105         setResult(Activity.RESULT_CANCELED);
106         setContentView(R.layout.activity);
107 
108         final Context context = this;
109         final Resources res = getResources();
110         mShowAsDialog = res.getBoolean(R.bool.show_as_dialog);
111 
112         if (mShowAsDialog) {
113             // Strongly define our horizontal dimension; we leave vertical as
114             // WRAP_CONTENT so that system resizes us when IME is showing.
115             final WindowManager.LayoutParams a = getWindow().getAttributes();
116 
117             final Point size = new Point();
118             getWindowManager().getDefaultDisplay().getSize(size);
119             a.width = (int) res.getFraction(R.dimen.dialog_width, size.x, size.x);
120 
121             getWindow().setAttributes(a);
122 
123         } else {
124             // Non-dialog means we have a drawer
125             mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
126 
127             mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
128                     R.drawable.ic_hamburger, R.string.drawer_open, R.string.drawer_close);
129 
130             mDrawerLayout.setDrawerListener(mDrawerListener);
131 
132             mRootsDrawer = findViewById(R.id.drawer_roots);
133         }
134 
135         mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory);
136 
137         mState = (icicle != null)
138                 ? icicle.<State>getParcelable(EXTRA_STATE)
139                 : buildDefaultState();
140 
141         mToolbar = (Toolbar) findViewById(R.id.toolbar);
142         mToolbar.setTitleTextAppearance(context,
143                 android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
144 
145         mStackAdapter = new StackAdapter();
146         mStackListener = new ItemSelectedListener();
147         mToolbarStack = (Spinner) findViewById(R.id.stack);
148         mToolbarStack.setOnItemSelectedListener(mStackListener);
149 
150         mRootsToolbar = (Toolbar) findViewById(R.id.roots_toolbar);
151         if (mRootsToolbar != null) {
152             mRootsToolbar.setTitleTextAppearance(context,
153                     android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
154         }
155 
156         setActionBar(mToolbar);
157 
158         // Hide roots when we're managing a specific root
159         if (mState.action == ACTION_MANAGE || mState.action == ACTION_BROWSE) {
160             if (mShowAsDialog) {
161                 findViewById(R.id.container_roots).setVisibility(View.GONE);
162             } else {
163                 mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
164             }
165         }
166 
167         if (mState.action == ACTION_CREATE) {
168             final String mimeType = getIntent().getType();
169             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
170             SaveFragment.show(getFragmentManager(), mimeType, title);
171         } else if (mState.action == ACTION_OPEN_TREE ||
172                    mState.action == ACTION_OPEN_COPY_DESTINATION) {
173             PickFragment.show(getFragmentManager());
174         }
175 
176         if (mState.action == ACTION_GET_CONTENT) {
177             final Intent moreApps = new Intent(getIntent());
178             moreApps.setComponent(null);
179             moreApps.setPackage(null);
180             RootsFragment.show(getFragmentManager(), moreApps);
181         } else if (mState.action == ACTION_OPEN ||
182                    mState.action == ACTION_CREATE ||
183                    mState.action == ACTION_OPEN_TREE ||
184                    mState.action == ACTION_OPEN_COPY_DESTINATION) {
185             RootsFragment.show(getFragmentManager(), null);
186         }
187 
188         if (!mState.restored) {
189             // In this case, we set the activity title in AsyncTask.onPostExecute().  To prevent
190             // talkback from reading aloud the default title, we clear it here.
191             setTitle("");
192             if (mState.action == ACTION_MANAGE || mState.action == ACTION_BROWSE) {
193                 final Uri rootUri = getIntent().getData();
194                 new RestoreRootTask(rootUri).executeOnExecutor(getCurrentExecutor());
195             } else {
196                 new RestoreStackTask().execute();
197             }
198 
199             // Show a failure dialog if there was a failed operation.
200             final Intent intent = getIntent();
201             final DocumentStack dstStack = intent.getParcelableExtra(CopyService.EXTRA_STACK);
202             final int failure = intent.getIntExtra(CopyService.EXTRA_FAILURE, 0);
203             if (failure != 0) {
204                 final ArrayList<DocumentInfo> failedSrcList =
205                         intent.getParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST);
206                 FailureDialogFragment.show(getFragmentManager(), failure, failedSrcList, dstStack);
207             }
208         } else {
209             onCurrentDirectoryChanged(ANIM_NONE);
210         }
211     }
212 
buildDefaultState()213     private State buildDefaultState() {
214         State state = new State();
215 
216         final Intent intent = getIntent();
217         final String action = intent.getAction();
218         if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
219             state.action = ACTION_OPEN;
220         } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
221             state.action = ACTION_CREATE;
222         } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
223             state.action = ACTION_GET_CONTENT;
224         } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
225             state.action = ACTION_OPEN_TREE;
226         } else if (DocumentsContract.ACTION_MANAGE_ROOT.equals(action)) {
227             state.action = ACTION_MANAGE;
228         } else if (DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT.equals(action)) {
229             state.action = ACTION_BROWSE;
230         } else if (DocumentsIntent.ACTION_OPEN_COPY_DESTINATION.equals(action)) {
231             state.action = ACTION_OPEN_COPY_DESTINATION;
232         }
233 
234         if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) {
235             state.allowMultiple = intent.getBooleanExtra(
236                     Intent.EXTRA_ALLOW_MULTIPLE, false);
237         }
238 
239         if (state.action == ACTION_MANAGE || state.action == ACTION_BROWSE) {
240             state.acceptMimes = new String[] { "*/*" };
241             state.allowMultiple = true;
242         } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
243             state.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
244         } else {
245             state.acceptMimes = new String[] { intent.getType() };
246         }
247 
248         state.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
249         state.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
250         state.showAdvanced = state.forceAdvanced
251                 | LocalPreferences.getDisplayAdvancedDevices(this);
252 
253         if (state.action == ACTION_MANAGE || state.action == ACTION_BROWSE) {
254             state.showSize = true;
255         } else {
256             state.showSize = LocalPreferences.getDisplayFileSize(this);
257         }
258         if (state.action == ACTION_OPEN_COPY_DESTINATION) {
259             state.directoryCopy = intent.getBooleanExtra(
260                     BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false);
261         }
262 
263         state.excludedAuthorities = getExcludedAuthorities();
264 
265         return state;
266     }
267 
268     private class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> {
269         private Uri mRootUri;
270 
RestoreRootTask(Uri rootUri)271         public RestoreRootTask(Uri rootUri) {
272             mRootUri = rootUri;
273         }
274 
275         @Override
doInBackground(Void... params)276         protected RootInfo doInBackground(Void... params) {
277             final String rootId = DocumentsContract.getRootId(mRootUri);
278             return mRoots.getRootOneshot(mRootUri.getAuthority(), rootId);
279         }
280 
281         @Override
onPostExecute(RootInfo root)282         protected void onPostExecute(RootInfo root) {
283             if (isDestroyed()) return;
284             mState.restored = true;
285 
286             if (root != null) {
287                 onRootPicked(root);
288             } else {
289                 Log.w(TAG, "Failed to find root: " + mRootUri);
290                 finish();
291             }
292         }
293     }
294 
295     @Override
onStackRestored(boolean restored, boolean external)296     void onStackRestored(boolean restored, boolean external) {
297         // Show drawer when no stack restored, but only when requesting
298         // non-visual content. However, if we last used an external app,
299         // drawer is always shown.
300 
301         boolean showDrawer = false;
302         if (!restored) {
303             showDrawer = true;
304         }
305         if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
306             showDrawer = false;
307         }
308         if (external && mState.action == ACTION_GET_CONTENT) {
309             showDrawer = true;
310         }
311 
312         if (showDrawer) {
313             setRootsDrawerOpen(true);
314         }
315     }
316 
onAppPicked(ResolveInfo info)317     public void onAppPicked(ResolveInfo info) {
318         final Intent intent = new Intent(getIntent());
319         intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
320         intent.setComponent(new ComponentName(
321                 info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
322         startActivityForResult(intent, CODE_FORWARD);
323     }
324 
325     @Override
onActivityResult(int requestCode, int resultCode, Intent data)326     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
327         Log.d(TAG, "onActivityResult() code=" + resultCode);
328 
329         // Only relay back results when not canceled; otherwise stick around to
330         // let the user pick another app/backend.
331         if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
332 
333             // Remember that we last picked via external app
334             final String packageName = getCallingPackageMaybeExtra();
335             final ContentValues values = new ContentValues();
336             values.put(ResumeColumns.EXTERNAL, 1);
337             getContentResolver().insert(RecentsProvider.buildResume(packageName), values);
338 
339             // Pass back result to original caller
340             setResult(resultCode, data);
341             finish();
342         } else {
343             super.onActivityResult(requestCode, resultCode, data);
344         }
345     }
346 
347     private DrawerListener mDrawerListener = new DrawerListener() {
348         @Override
349         public void onDrawerSlide(View drawerView, float slideOffset) {
350             mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
351         }
352 
353         @Override
354         public void onDrawerOpened(View drawerView) {
355             mDrawerToggle.onDrawerOpened(drawerView);
356         }
357 
358         @Override
359         public void onDrawerClosed(View drawerView) {
360             mDrawerToggle.onDrawerClosed(drawerView);
361         }
362 
363         @Override
364         public void onDrawerStateChanged(int newState) {
365             mDrawerToggle.onDrawerStateChanged(newState);
366         }
367     };
368 
369     @Override
onPostCreate(Bundle savedInstanceState)370     protected void onPostCreate(Bundle savedInstanceState) {
371         super.onPostCreate(savedInstanceState);
372         if (mDrawerToggle != null) {
373             mDrawerToggle.syncState();
374         }
375         updateActionBar();
376     }
377 
setRootsDrawerOpen(boolean open)378     public void setRootsDrawerOpen(boolean open) {
379         if (!mShowAsDialog) {
380             if (open) {
381                 mDrawerLayout.openDrawer(mRootsDrawer);
382             } else {
383                 mDrawerLayout.closeDrawer(mRootsDrawer);
384             }
385         }
386     }
387 
isRootsDrawerOpen()388     private boolean isRootsDrawerOpen() {
389         if (mShowAsDialog) {
390             return false;
391         } else {
392             return mDrawerLayout.isDrawerOpen(mRootsDrawer);
393         }
394     }
395 
396     @Override
updateActionBar()397     public void updateActionBar() {
398         if (mRootsToolbar != null) {
399             final String prompt = getIntent().getStringExtra(DocumentsContract.EXTRA_PROMPT);
400             if (prompt != null) {
401                 mRootsToolbar.setTitle(prompt);
402             } else {
403                 if (mState.action == ACTION_OPEN ||
404                     mState.action == ACTION_GET_CONTENT ||
405                     mState.action == ACTION_OPEN_TREE) {
406                     mRootsToolbar.setTitle(R.string.title_open);
407                 } else if (mState.action == ACTION_CREATE ||
408                            mState.action == ACTION_OPEN_COPY_DESTINATION) {
409                     mRootsToolbar.setTitle(R.string.title_save);
410                 }
411             }
412         }
413 
414         if (!mShowAsDialog && mDrawerLayout.getDrawerLockMode(mRootsDrawer) ==
415                 DrawerLayout.LOCK_MODE_UNLOCKED) {
416             mToolbar.setNavigationIcon(R.drawable.ic_hamburger);
417             mToolbar.setNavigationContentDescription(R.string.drawer_open);
418             mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
419                 @Override
420                 public void onClick(View v) {
421                     setRootsDrawerOpen(true);
422                 }
423             });
424         } else {
425             mToolbar.setNavigationIcon(null);
426             mToolbar.setNavigationContentDescription(R.string.drawer_open);
427             mToolbar.setNavigationOnClickListener(null);
428         }
429 
430         if (mSearchManager.isExpanded()) {
431             mToolbar.setTitle(null);
432             mToolbarStack.setVisibility(View.GONE);
433             mToolbarStack.setAdapter(null);
434         } else {
435             if (mState.stack.size() <= 1) {
436                 mToolbar.setTitle(getCurrentRoot().title);
437                 mToolbarStack.setVisibility(View.GONE);
438                 mToolbarStack.setAdapter(null);
439             } else {
440                 mToolbar.setTitle(null);
441                 mToolbarStack.setVisibility(View.VISIBLE);
442                 mToolbarStack.setAdapter(mStackAdapter);
443 
444                 mStackListener.mIgnoreNextNavigation = true;
445                 mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
446             }
447         }
448     }
449 
450     @Override
onCreateOptionsMenu(Menu menu)451     public boolean onCreateOptionsMenu(Menu menu) {
452         boolean showMenu = super.onCreateOptionsMenu(menu);
453 
454         // Most actions are visible when showing as dialog
455         if (mShowAsDialog) {
456             expandMenus(menu);
457         }
458         return showMenu;
459     }
460 
461     @Override
onPrepareOptionsMenu(Menu menu)462     public boolean onPrepareOptionsMenu(Menu menu) {
463         super.onPrepareOptionsMenu(menu);
464 
465         final RootInfo root = getCurrentRoot();
466         final DocumentInfo cwd = getCurrentDirectory();
467 
468         final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
469         final MenuItem grid = menu.findItem(R.id.menu_grid);
470         final MenuItem list = menu.findItem(R.id.menu_list);
471         final MenuItem advanced = menu.findItem(R.id.menu_advanced);
472         final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
473         final MenuItem settings = menu.findItem(R.id.menu_settings);
474 
475         boolean fileSizeVisible = !(mState.action == ACTION_MANAGE
476                 || mState.action == ACTION_BROWSE);
477         if (mState.action == ACTION_CREATE
478                 || mState.action == ACTION_OPEN_TREE
479                 || mState.action == ACTION_OPEN_COPY_DESTINATION) {
480             createDir.setVisible(cwd != null && cwd.isCreateSupported());
481             mSearchManager.showMenu(false);
482 
483             // No display options in recent directories
484             if (cwd == null) {
485                 grid.setVisible(false);
486                 list.setVisible(false);
487                 fileSizeVisible = false;
488             }
489 
490             if (mState.action == ACTION_CREATE) {
491                 final FragmentManager fm = getFragmentManager();
492                 SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported());
493             }
494         } else {
495             createDir.setVisible(false);
496         }
497 
498         advanced.setVisible(!(mState.action == ACTION_MANAGE || mState.action == ACTION_BROWSE));
499         fileSize.setVisible(fileSizeVisible);
500 
501         settings.setVisible((mState.action == ACTION_MANAGE || mState.action == ACTION_BROWSE)
502                 && (root.flags & Root.FLAG_HAS_SETTINGS) != 0);
503 
504         return true;
505     }
506 
507     @Override
onOptionsItemSelected(MenuItem item)508     public boolean onOptionsItemSelected(MenuItem item) {
509         if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
510             return true;
511         }
512         return super.onOptionsItemSelected(item);
513     }
514 
515     @Override
onBackPressed()516     public void onBackPressed() {
517         // While action bar is expanded, the state stack UI is hidden.
518         if (mSearchManager.cancelSearch()) {
519             return;
520         }
521 
522         if (!mState.stackTouched) {
523             super.onBackPressed();
524             return;
525         }
526 
527         final int size = mState.stack.size();
528         if (size > 1) {
529             mState.stack.pop();
530             onCurrentDirectoryChanged(ANIM_UP);
531         } else if (size == 1 && !isRootsDrawerOpen()) {
532             // TODO: open root drawer once we can capture back key
533             super.onBackPressed();
534         } else {
535             super.onBackPressed();
536         }
537     }
538 
539     @Override
getDisplayState()540     public State getDisplayState() {
541         return mState;
542     }
543 
544     @Override
onDirectoryChanged(int anim)545     void onDirectoryChanged(int anim) {
546         final FragmentManager fm = getFragmentManager();
547         final RootInfo root = getCurrentRoot();
548         final DocumentInfo cwd = getCurrentDirectory();
549 
550         mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN);
551 
552         if (cwd == null) {
553             // No directory means recents
554             if (mState.action == ACTION_CREATE ||
555                 mState.action == ACTION_OPEN_TREE ||
556                 mState.action == ACTION_OPEN_COPY_DESTINATION) {
557                 RecentsCreateFragment.show(fm);
558             } else {
559                 DirectoryFragment.showRecentsOpen(fm, anim);
560 
561                 // Start recents in grid when requesting visual things
562                 final boolean visualMimes = MimePredicate.mimeMatches(
563                         MimePredicate.VISUAL_MIMES, mState.acceptMimes);
564                 mState.userMode = visualMimes ? State.MODE_GRID : State.MODE_LIST;
565                 mState.derivedMode = mState.userMode;
566             }
567         } else {
568             if (mState.currentSearch != null) {
569                 // Ongoing search
570                 DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
571             } else {
572                 // Normal boring directory
573                 DirectoryFragment.showNormal(fm, root, cwd, anim);
574             }
575         }
576 
577         // Forget any replacement target
578         if (mState.action == ACTION_CREATE) {
579             final SaveFragment save = SaveFragment.get(fm);
580             if (save != null) {
581                 save.setReplaceTarget(null);
582             }
583         }
584 
585         if (mState.action == ACTION_OPEN_TREE ||
586             mState.action == ACTION_OPEN_COPY_DESTINATION) {
587             final PickFragment pick = PickFragment.get(fm);
588             if (pick != null) {
589                 pick.setPickTarget(mState.action, cwd);
590             }
591         }
592     }
593 
onSaveRequested(DocumentInfo replaceTarget)594     void onSaveRequested(DocumentInfo replaceTarget) {
595         new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getCurrentExecutor());
596     }
597 
onSaveRequested(String mimeType, String displayName)598     void onSaveRequested(String mimeType, String displayName) {
599         new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor());
600     }
601 
602     @Override
onRootPicked(RootInfo root)603     void onRootPicked(RootInfo root) {
604         super.onRootPicked(root);
605         setRootsDrawerOpen(false);
606     }
607 
608     @Override
onDocumentPicked(DocumentInfo doc)609     public void onDocumentPicked(DocumentInfo doc) {
610         final FragmentManager fm = getFragmentManager();
611         if (doc.isDirectory()) {
612             mState.stack.push(doc);
613             mState.stackTouched = true;
614             onCurrentDirectoryChanged(ANIM_DOWN);
615         } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
616             // Explicit file picked, return
617             new ExistingFinishTask(doc.derivedUri).executeOnExecutor(getCurrentExecutor());
618         } else if (mState.action == ACTION_CREATE) {
619             // Replace selected file
620             SaveFragment.get(fm).setReplaceTarget(doc);
621         } else if (mState.action == ACTION_MANAGE) {
622             // First try managing the document; we expect manager to filter
623             // based on authority, so we don't grant.
624             final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
625             manage.setData(doc.derivedUri);
626 
627             try {
628                 startActivity(manage);
629             } catch (ActivityNotFoundException ex) {
630                 // Fall back to viewing
631                 final Intent view = new Intent(Intent.ACTION_VIEW);
632                 view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
633                 view.setData(doc.derivedUri);
634 
635                 try {
636                     startActivity(view);
637                 } catch (ActivityNotFoundException ex2) {
638                     Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show();
639                 }
640             }
641         } else if (mState.action == ACTION_BROWSE) {
642             // Go straight to viewing
643             final Intent view = new Intent(Intent.ACTION_VIEW);
644             view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
645             view.setData(doc.derivedUri);
646 
647             try {
648                 startActivity(view);
649             } catch (ActivityNotFoundException ex) {
650                 Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show();
651             }
652         }
653     }
654 
655     @Override
onDocumentsPicked(List<DocumentInfo> docs)656     public void onDocumentsPicked(List<DocumentInfo> docs) {
657         if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
658             final int size = docs.size();
659             final Uri[] uris = new Uri[size];
660             for (int i = 0; i < size; i++) {
661                 uris[i] = docs.get(i).derivedUri;
662             }
663             new ExistingFinishTask(uris).executeOnExecutor(getCurrentExecutor());
664         }
665     }
666 
onPickRequested(DocumentInfo pickTarget)667     public void onPickRequested(DocumentInfo pickTarget) {
668         Uri result;
669         if (mState.action == ACTION_OPEN_TREE) {
670             result = DocumentsContract.buildTreeDocumentUri(
671                     pickTarget.authority, pickTarget.documentId);
672         } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
673             result = pickTarget.derivedUri;
674         } else {
675             // Should not be reached.
676             throw new IllegalStateException("Invalid mState.action.");
677         }
678         new PickFinishTask(result).executeOnExecutor(getCurrentExecutor());
679     }
680 
681     @Override
saveStackBlocking()682     void saveStackBlocking() {
683         final ContentResolver resolver = getContentResolver();
684         final ContentValues values = new ContentValues();
685 
686         final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
687         if (mState.action == ACTION_CREATE ||
688             mState.action == ACTION_OPEN_TREE ||
689             mState.action == ACTION_OPEN_COPY_DESTINATION) {
690             // Remember stack for last create
691             values.clear();
692             values.put(RecentColumns.KEY, mState.stack.buildKey());
693             values.put(RecentColumns.STACK, rawStack);
694             resolver.insert(RecentsProvider.buildRecent(), values);
695         }
696 
697         // Remember location for next app launch
698         final String packageName = getCallingPackageMaybeExtra();
699         values.clear();
700         values.put(ResumeColumns.STACK, rawStack);
701         values.put(ResumeColumns.EXTERNAL, 0);
702         resolver.insert(RecentsProvider.buildResume(packageName), values);
703     }
704 
705     @Override
onTaskFinished(Uri... uris)706     void onTaskFinished(Uri... uris) {
707         Log.d(TAG, "onFinished() " + Arrays.toString(uris));
708 
709         final Intent intent = new Intent();
710         if (uris.length == 1) {
711             intent.setData(uris[0]);
712         } else if (uris.length > 1) {
713             final ClipData clipData = new ClipData(
714                     null, mState.acceptMimes, new ClipData.Item(uris[0]));
715             for (int i = 1; i < uris.length; i++) {
716                 clipData.addItem(new ClipData.Item(uris[i]));
717             }
718             intent.setClipData(clipData);
719         }
720 
721         if (mState.action == ACTION_GET_CONTENT) {
722             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
723         } else if (mState.action == ACTION_OPEN_TREE) {
724             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
725                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
726                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
727                     | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
728         } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
729             // Picking a copy destination is only used internally by us, so we
730             // don't need to extend permissions to the caller.
731             intent.putExtra(CopyService.EXTRA_STACK, (Parcelable) mState.stack);
732         } else {
733             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
734                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
735                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
736         }
737 
738         setResult(Activity.RESULT_OK, intent);
739         finish();
740     }
741 
get(Fragment fragment)742     public static DocumentsActivity get(Fragment fragment) {
743         return (DocumentsActivity) fragment.getActivity();
744     }
745 
746     private final class PickFinishTask extends AsyncTask<Void, Void, Void> {
747         private final Uri mUri;
748 
PickFinishTask(Uri uri)749         public PickFinishTask(Uri uri) {
750             mUri = uri;
751         }
752 
753         @Override
doInBackground(Void... params)754         protected Void doInBackground(Void... params) {
755             saveStackBlocking();
756             return null;
757         }
758 
759         @Override
onPostExecute(Void result)760         protected void onPostExecute(Void result) {
761             onTaskFinished(mUri);
762         }
763     }
764 
765     final class ExistingFinishTask extends AsyncTask<Void, Void, Void> {
766         private final Uri[] mUris;
767 
ExistingFinishTask(Uri... uris)768         public ExistingFinishTask(Uri... uris) {
769             mUris = uris;
770         }
771 
772         @Override
doInBackground(Void... params)773         protected Void doInBackground(Void... params) {
774             saveStackBlocking();
775             return null;
776         }
777 
778         @Override
onPostExecute(Void result)779         protected void onPostExecute(Void result) {
780             onTaskFinished(mUris);
781         }
782     }
783 
784     /**
785      * Task that creates a new document in the background.
786      */
787     final class CreateFinishTask extends AsyncTask<Void, Void, Uri> {
788         private final String mMimeType;
789         private final String mDisplayName;
790 
CreateFinishTask(String mimeType, String displayName)791         public CreateFinishTask(String mimeType, String displayName) {
792             mMimeType = mimeType;
793             mDisplayName = displayName;
794         }
795 
796         @Override
onPreExecute()797         protected void onPreExecute() {
798             setPending(true);
799         }
800 
801         @Override
doInBackground(Void... params)802         protected Uri doInBackground(Void... params) {
803             final ContentResolver resolver = getContentResolver();
804             final DocumentInfo cwd = getCurrentDirectory();
805 
806             ContentProviderClient client = null;
807             Uri childUri = null;
808             try {
809                 client = DocumentsApplication.acquireUnstableProviderOrThrow(
810                         resolver, cwd.derivedUri.getAuthority());
811                 childUri = DocumentsContract.createDocument(
812                         client, cwd.derivedUri, mMimeType, mDisplayName);
813             } catch (Exception e) {
814                 Log.w(TAG, "Failed to create document", e);
815             } finally {
816                 ContentProviderClient.releaseQuietly(client);
817             }
818 
819             if (childUri != null) {
820                 saveStackBlocking();
821             }
822 
823             return childUri;
824         }
825 
826         @Override
onPostExecute(Uri result)827         protected void onPostExecute(Uri result) {
828             if (result != null) {
829                 onTaskFinished(result);
830             } else {
831                 Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT)
832                         .show();
833             }
834 
835             setPending(false);
836         }
837     }
838 }
839