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