blob: 976f21d29a2b3e2abf615bd93cc1928d0464f1f8 [file] [log] [blame]
Steve McKaye934ce62015-03-25 14:35:33 -07001/*
2 * Copyright (C) 2015 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
19
20import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
21import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
22import static com.android.documentsui.DirectoryFragment.ANIM_SIDE;
23import static com.android.documentsui.DirectoryFragment.ANIM_UP;
24import android.app.Activity;
25import android.app.Fragment;
26import android.app.FragmentManager;
27import android.content.ActivityNotFoundException;
28import android.content.ClipData;
29import android.content.ComponentName;
30import android.content.ContentProviderClient;
31import android.content.ContentResolver;
32import android.content.ContentValues;
33import android.content.Context;
34import android.content.Intent;
35import android.content.pm.ResolveInfo;
36import android.content.res.Resources;
37import android.database.Cursor;
38import android.graphics.Point;
39import android.net.Uri;
40import android.os.AsyncTask;
41import android.os.Bundle;
42import android.os.Debug;
43import android.provider.DocumentsContract;
44import android.support.v4.app.ActionBarDrawerToggle;
45import android.support.v4.widget.DrawerLayout;
46import android.support.v4.widget.DrawerLayout.DrawerListener;
47import android.util.Log;
48import android.view.LayoutInflater;
49import android.view.Menu;
50import android.view.MenuItem;
51import android.view.MenuItem.OnActionExpandListener;
52import android.view.View;
53import android.view.ViewGroup;
54import android.view.WindowManager;
55import android.widget.AdapterView;
56import android.widget.AdapterView.OnItemSelectedListener;
57import android.widget.BaseAdapter;
58import android.widget.ImageView;
59import android.widget.SearchView;
60import android.widget.SearchView.OnQueryTextListener;
61import android.widget.Spinner;
62import android.widget.TextView;
63import android.widget.Toast;
64import android.widget.Toolbar;
65
Tomasz Mikolajewski61686592015-04-13 19:38:43 +090066import com.android.documentsui.FailureDialogFragment;
Steve McKaye934ce62015-03-25 14:35:33 -070067import com.android.documentsui.RecentsProvider.ResumeColumns;
68import com.android.documentsui.model.DocumentInfo;
69import com.android.documentsui.model.DocumentStack;
70import com.android.documentsui.model.DurableUtils;
71import com.android.documentsui.model.RootInfo;
72
73import libcore.io.IoUtils;
74
75import java.io.FileNotFoundException;
76import java.io.IOException;
Tomasz Mikolajewski61686592015-04-13 19:38:43 +090077import java.util.ArrayList;
Steve McKaye934ce62015-03-25 14:35:33 -070078import java.util.Arrays;
79import java.util.Collection;
80import java.util.List;
81import java.util.concurrent.Executor;
82
83public class StandaloneActivity extends BaseActivity {
84 public static final String TAG = "StandaloneFileManagement";
85
86 private static final String EXTRA_STATE = "state";
87
88 private static final int CODE_FORWARD = 42;
89
90 private SearchView mSearchView;
91
92 private Toolbar mToolbar;
93 private Spinner mToolbarStack;
94
95 private Toolbar mRootsToolbar;
96
97 private ActionBarDrawerToggle mDrawerToggle;
98
99 private DirectoryContainerView mDirectoryContainer;
100
101 private boolean mIgnoreNextNavigation;
102 private boolean mIgnoreNextClose;
103 private boolean mIgnoreNextCollapse;
104
105 private boolean mSearchExpanded;
106
107 private RootsCache mRoots;
108 private State mState;
109
110 @Override
111 public void onCreate(Bundle icicle) {
112 // Debug.waitForDebugger();
113 super.onCreate(icicle);
114
115 mRoots = DocumentsApplication.getRootsCache(this);
116
117 setResult(Activity.RESULT_CANCELED);
118 setContentView(R.layout.activity);
119
120 final Context context = this;
121 final Resources res = getResources();
122
123 // Strongly define our horizontal dimension; we leave vertical as
124 final WindowManager.LayoutParams a = getWindow().getAttributes();
125
126 final Point size = new Point();
127 getWindowManager().getDefaultDisplay().getSize(size);
128 // a.width = (int) res.getFraction(R.dimen.dialog_width, size.x, size.x);
129
130 getWindow().setAttributes(a);
131
132 mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory);
133
134 if (icicle != null) {
135 mState = icicle.getParcelable(EXTRA_STATE);
136 } else {
137 buildDefaultState();
138 }
139
140 mToolbar = (Toolbar) findViewById(R.id.toolbar);
141 mToolbar.setTitleTextAppearance(context,
142 android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
143
144 mToolbarStack = (Spinner) findViewById(R.id.stack);
145 mToolbarStack.setOnItemSelectedListener(mStackListener);
146
147 mRootsToolbar = (Toolbar) findViewById(R.id.roots_toolbar);
148 if (mRootsToolbar != null) {
149 mRootsToolbar.setTitleTextAppearance(context,
150 android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
151 }
152
153 setActionBar(mToolbar);
154
155 RootsFragment.show(getFragmentManager(), null);
156 if (!mState.restored) {
157 new RestoreStackTask().execute();
Tomasz Mikolajewski61686592015-04-13 19:38:43 +0900158 final Intent intent = getIntent();
159 final int failure = intent.getIntExtra(CopyService.EXTRA_FAILURE, 0);
160 if (failure != 0) {
161 final ArrayList<Uri> failedSrcList = intent.getParcelableArrayListExtra(
162 CopyService.EXTRA_SRC_LIST);
163 FailureDialogFragment.show(getFragmentManager(), failure, failedSrcList);
164 }
Steve McKaye934ce62015-03-25 14:35:33 -0700165 } else {
166 onCurrentDirectoryChanged(ANIM_NONE);
167 }
168 }
169
170 private void buildDefaultState() {
171 mState = new State();
172
173 final Intent intent = getIntent();
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700174 mState.action = State.ACTION_BROWSE_ALL;
Steve McKaye934ce62015-03-25 14:35:33 -0700175 mState.acceptMimes = new String[] { "*/*" };
176 mState.allowMultiple = true;
177 mState.acceptMimes = new String[] { intent.getType() };
178 mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
179 mState.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
180 mState.showAdvanced = mState.forceAdvanced
181 | LocalPreferences.getDisplayAdvancedDevices(this);
182 mState.showSize = true;
Tomasz Mikolajewskie1a03f82015-04-10 10:30:33 +0900183 final DocumentStack stack = intent.getParcelableExtra(CopyService.EXTRA_STACK);
184 if (stack != null)
185 mState.stack = stack;
Steve McKaye934ce62015-03-25 14:35:33 -0700186 }
187
188 private class RestoreStackTask extends AsyncTask<Void, Void, Void> {
189 private volatile boolean mRestoredStack;
190 private volatile boolean mExternal;
191
192 @Override
193 protected Void doInBackground(Void... params) {
194 // Restore last stack for calling package
195 final String packageName = getCallingPackageMaybeExtra();
196 final Cursor cursor = getContentResolver()
197 .query(RecentsProvider.buildResume(packageName), null, null, null, null);
198 try {
199 if (cursor.moveToFirst()) {
200 mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
201 final byte[] rawStack = cursor.getBlob(
202 cursor.getColumnIndex(ResumeColumns.STACK));
203 DurableUtils.readFromArray(rawStack, mState.stack);
204 mRestoredStack = true;
205 }
206 } catch (IOException e) {
207 Log.w(TAG, "Failed to resume: " + e);
208 } finally {
209 IoUtils.closeQuietly(cursor);
210 }
211
212 if (mRestoredStack) {
213 // Update the restored stack to ensure we have freshest data
214 final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState);
215 try {
216 mState.stack.updateRoot(matchingRoots);
217 mState.stack.updateDocuments(getContentResolver());
218 } catch (FileNotFoundException e) {
219 Log.w(TAG, "Failed to restore stack: " + e);
220 mState.stack.reset();
221 mRestoredStack = false;
222 }
223 }
224
225 return null;
226 }
227
228 @Override
229 protected void onPostExecute(Void result) {
230 if (isDestroyed()) return;
231 mState.restored = true;
232 onCurrentDirectoryChanged(ANIM_NONE);
233 }
234 }
235
236 @Override
237 protected void onPostCreate(Bundle savedInstanceState) {
238 super.onPostCreate(savedInstanceState);
239 if (mDrawerToggle != null) {
240 mDrawerToggle.syncState();
241 }
242 updateActionBar();
243 }
244
245 @Override
246 public void setRootsDrawerOpen(boolean open) {
247 Log.w(TAG, "Trying to change state of roots drawer to > " + (open ? "open" : "closed"));
248 // throw new UnsupportedOperationException();
249 }
250
251 public void updateActionBar() {
252 final RootInfo root = getCurrentRoot();
253 mToolbar.setNavigationIcon(
254 root != null ? root.loadToolbarIcon(mToolbar.getContext()) : null);
255 mToolbar.setNavigationContentDescription(R.string.drawer_open);
256 mToolbar.setNavigationOnClickListener(null);
257
258 if (mSearchExpanded) {
259 mToolbar.setTitle(null);
260 mToolbarStack.setVisibility(View.GONE);
261 mToolbarStack.setAdapter(null);
262 } else {
263 if (mState.stack.size() <= 1) {
264 mToolbar.setTitle(root.title);
265 mToolbarStack.setVisibility(View.GONE);
266 mToolbarStack.setAdapter(null);
267 } else {
268 mToolbar.setTitle(null);
269 mToolbarStack.setVisibility(View.VISIBLE);
270 mToolbarStack.setAdapter(mStackAdapter);
271
272 mIgnoreNextNavigation = true;
273 mToolbarStack.setSelection(mStackAdapter.getCount() - 1);
274 }
275 }
276 }
277
278 @Override
279 public boolean onCreateOptionsMenu(Menu menu) {
280 super.onCreateOptionsMenu(menu);
281 getMenuInflater().inflate(R.menu.activity, menu);
282
283 for (int i = 0; i < menu.size(); i++) {
284 final MenuItem item = menu.getItem(i);
285 switch (item.getItemId()) {
286 case R.id.menu_advanced:
287 case R.id.menu_file_size:
288 break;
289 default:
290 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
291 }
292 }
293
294 final MenuItem searchMenu = menu.findItem(R.id.menu_search);
295 mSearchView = (SearchView) searchMenu.getActionView();
296 mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
297 @Override
298 public boolean onQueryTextSubmit(String query) {
299 mSearchExpanded = true;
300 mState.currentSearch = query;
301 mSearchView.clearFocus();
302 onCurrentDirectoryChanged(ANIM_NONE);
303 return true;
304 }
305
306 @Override
307 public boolean onQueryTextChange(String newText) {
308 return false;
309 }
310 });
311
312 searchMenu.setOnActionExpandListener(new OnActionExpandListener() {
313 @Override
314 public boolean onMenuItemActionExpand(MenuItem item) {
315 mSearchExpanded = true;
316 updateActionBar();
317 return true;
318 }
319
320 @Override
321 public boolean onMenuItemActionCollapse(MenuItem item) {
322 mSearchExpanded = false;
323 if (mIgnoreNextCollapse) {
324 mIgnoreNextCollapse = false;
325 return true;
326 }
327
328 mState.currentSearch = null;
329 onCurrentDirectoryChanged(ANIM_NONE);
330 return true;
331 }
332 });
333
334 mSearchView.setOnCloseListener(new SearchView.OnCloseListener() {
335 @Override
336 public boolean onClose() {
337 mSearchExpanded = false;
338 if (mIgnoreNextClose) {
339 mIgnoreNextClose = false;
340 return false;
341 }
342
343 mState.currentSearch = null;
344 onCurrentDirectoryChanged(ANIM_NONE);
345 return false;
346 }
347 });
348
349 return true;
350 }
351
352 @Override
353 public boolean onPrepareOptionsMenu(Menu menu) {
354 super.onPrepareOptionsMenu(menu);
355
356 final FragmentManager fm = getFragmentManager();
357
358 final RootInfo root = getCurrentRoot();
359 final DocumentInfo cwd = getCurrentDirectory();
360
361 final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
362 final MenuItem search = menu.findItem(R.id.menu_search);
363 final MenuItem sort = menu.findItem(R.id.menu_sort);
364 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size);
365 final MenuItem grid = menu.findItem(R.id.menu_grid);
366 final MenuItem list = menu.findItem(R.id.menu_list);
367 final MenuItem advanced = menu.findItem(R.id.menu_advanced);
368 final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
369
370 sort.setVisible(cwd != null);
371 grid.setVisible(mState.derivedMode != State.MODE_GRID);
372 list.setVisible(mState.derivedMode != State.MODE_LIST);
373
374 if (mState.currentSearch != null) {
375 // Search uses backend ranking; no sorting
376 sort.setVisible(false);
377
378 search.expandActionView();
379
380 mSearchView.setIconified(false);
381 mSearchView.clearFocus();
382 mSearchView.setQuery(mState.currentSearch, false);
383 } else {
384 mIgnoreNextClose = true;
385 mSearchView.setIconified(true);
386 mSearchView.clearFocus();
387
388 mIgnoreNextCollapse = true;
389 search.collapseActionView();
390 }
391
392 // Only sort by size when visible
393 sortSize.setVisible(mState.showSize);
394
395 fileSize.setVisible(true);
396 search.setVisible(true);
397 createDir.setVisible(true);
398 advanced.setVisible(true);
399
400 advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
401 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
402 fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
403 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
404
405
406 return true;
407 }
408
409 @Override
410 public boolean onOptionsItemSelected(MenuItem item) {
411 if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
412 return true;
413 }
414
415 final int id = item.getItemId();
416 if (id == android.R.id.home) {
417 onBackPressed();
418 return true;
419 } else if (id == R.id.menu_create_dir) {
420 CreateDirectoryFragment.show(getFragmentManager());
421 return true;
422 } else if (id == R.id.menu_search) {
423 return false;
424 } else if (id == R.id.menu_sort_name) {
425 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
426 return true;
427 } else if (id == R.id.menu_sort_date) {
428 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
429 return true;
430 } else if (id == R.id.menu_sort_size) {
431 setUserSortOrder(State.SORT_ORDER_SIZE);
432 return true;
433 } else if (id == R.id.menu_grid) {
434 setUserMode(State.MODE_GRID);
435 return true;
436 } else if (id == R.id.menu_list) {
437 setUserMode(State.MODE_LIST);
438 return true;
439 } else if (id == R.id.menu_advanced) {
440 setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this));
441 return true;
442 } else if (id == R.id.menu_file_size) {
443 setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
444 return true;
445 } else {
446 return super.onOptionsItemSelected(item);
447 }
448 }
449
450 private void setDisplayAdvancedDevices(boolean display) {
451 LocalPreferences.setDisplayAdvancedDevices(this, display);
452 mState.showAdvanced = mState.forceAdvanced | display;
453 RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
454 invalidateOptionsMenu();
455 }
456
457 private void setDisplayFileSize(boolean display) {
458 LocalPreferences.setDisplayFileSize(this, display);
459 mState.showSize = display;
460 DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
461 invalidateOptionsMenu();
462 }
463
464 @Override
465 public void onStateChanged() {
466 invalidateOptionsMenu();
467 }
468
469 /**
470 * Set state sort order based on explicit user action.
471 */
472 private void setUserSortOrder(int sortOrder) {
473 mState.userSortOrder = sortOrder;
474 DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
475 }
476
477 /**
478 * Set state mode based on explicit user action.
479 */
480 private void setUserMode(int mode) {
481 mState.userMode = mode;
482 DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
483 }
484
485 @Override
486 public void setPending(boolean pending) {
487 final SaveFragment save = SaveFragment.get(getFragmentManager());
488 if (save != null) {
489 save.setPending(pending);
490 }
491 }
492
493 @Override
494 public void onBackPressed() {
495 if (!mState.stackTouched) {
496 super.onBackPressed();
497 return;
498 }
499
500 final int size = mState.stack.size();
501 if (size > 1) {
502 mState.stack.pop();
503 onCurrentDirectoryChanged(ANIM_UP);
504 } else {
505 super.onBackPressed();
506 }
507 }
508
509 @Override
510 protected void onSaveInstanceState(Bundle state) {
511 super.onSaveInstanceState(state);
512 state.putParcelable(EXTRA_STATE, mState);
513 }
514
515 @Override
516 protected void onRestoreInstanceState(Bundle state) {
517 super.onRestoreInstanceState(state);
518 }
519
520 private BaseAdapter mStackAdapter = new BaseAdapter() {
521 @Override
522 public int getCount() {
523 return mState.stack.size();
524 }
525
526 @Override
527 public DocumentInfo getItem(int position) {
528 return mState.stack.get(mState.stack.size() - position - 1);
529 }
530
531 @Override
532 public long getItemId(int position) {
533 return position;
534 }
535
536 @Override
537 public View getView(int position, View convertView, ViewGroup parent) {
538 if (convertView == null) {
539 convertView = LayoutInflater.from(parent.getContext())
540 .inflate(R.layout.item_subdir_title, parent, false);
541 }
542
543 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
544 final DocumentInfo doc = getItem(position);
545
546 if (position == 0) {
547 final RootInfo root = getCurrentRoot();
548 title.setText(root.title);
549 } else {
550 title.setText(doc.displayName);
551 }
552
553 return convertView;
554 }
555
556 @Override
557 public View getDropDownView(int position, View convertView, ViewGroup parent) {
558 if (convertView == null) {
559 convertView = LayoutInflater.from(parent.getContext())
560 .inflate(R.layout.item_subdir, parent, false);
561 }
562
563 final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
564 final TextView title = (TextView) convertView.findViewById(android.R.id.title);
565 final DocumentInfo doc = getItem(position);
566
567 if (position == 0) {
568 final RootInfo root = getCurrentRoot();
569 title.setText(root.title);
570 subdir.setVisibility(View.GONE);
571 } else {
572 title.setText(doc.displayName);
573 subdir.setVisibility(View.VISIBLE);
574 }
575
576 return convertView;
577 }
578 };
579
580 private OnItemSelectedListener mStackListener = new OnItemSelectedListener() {
581 @Override
582 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
583 if (mIgnoreNextNavigation) {
584 mIgnoreNextNavigation = false;
585 return;
586 }
587
588 while (mState.stack.size() > position + 1) {
589 mState.stackTouched = true;
590 mState.stack.pop();
591 }
592 onCurrentDirectoryChanged(ANIM_UP);
593 }
594
595 @Override
596 public void onNothingSelected(AdapterView<?> parent) {
597 // Ignored
598 }
599 };
600
601 @Override
602 public RootInfo getCurrentRoot() {
603 if (mState.stack.root != null) {
604 return mState.stack.root;
605 } else {
606 return mRoots.getRecentsRoot();
607 }
608 }
609
610 public DocumentInfo getCurrentDirectory() {
611 return mState.stack.peek();
612 }
613
614 private String getCallingPackageMaybeExtra() {
615 final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
616 return (extra != null) ? extra : getCallingPackage();
617 }
618
619 public Executor getCurrentExecutor() {
620 final DocumentInfo cwd = getCurrentDirectory();
621 if (cwd != null && cwd.authority != null) {
622 return ProviderExecutor.forAuthority(cwd.authority);
623 } else {
624 return AsyncTask.THREAD_POOL_EXECUTOR;
625 }
626 }
627
628 @Override
629 public State getDisplayState() {
630 return mState;
631 }
632
633 private void onCurrentDirectoryChanged(int anim) {
634 final FragmentManager fm = getFragmentManager();
635 final RootInfo root = getCurrentRoot();
636 final DocumentInfo cwd = getCurrentDirectory();
637
638 mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN);
639
640 if (cwd == null) {
641 DirectoryFragment.showRecentsOpen(fm, anim);
642
643 // Start recents in grid when requesting visual things
644 final boolean visualMimes = MimePredicate.mimeMatches(
645 MimePredicate.VISUAL_MIMES, mState.acceptMimes);
646 mState.userMode = visualMimes ? State.MODE_GRID : State.MODE_LIST;
647 mState.derivedMode = mState.userMode;
648 } else {
649 if (mState.currentSearch != null) {
650 // Ongoing search
651 DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim);
652 } else {
653 // Normal boring directory
654 DirectoryFragment.showNormal(fm, root, cwd, anim);
655 }
656 }
657
658 final RootsFragment roots = RootsFragment.get(fm);
659 if (roots != null) {
660 roots.onCurrentRootChanged();
661 }
662
663 updateActionBar();
664 invalidateOptionsMenu();
665 dumpStack();
666 }
667
668 @Override
669 public void onStackPicked(DocumentStack stack) {
670 try {
671 // Update the restored stack to ensure we have freshest data
672 stack.updateDocuments(getContentResolver());
673
674 mState.stack = stack;
675 mState.stackTouched = true;
676 onCurrentDirectoryChanged(ANIM_SIDE);
677
678 } catch (FileNotFoundException e) {
679 Log.w(TAG, "Failed to restore stack: " + e);
680 }
681 }
682
683 @Override
684 public void onRootPicked(RootInfo root, boolean closeDrawer) {
685 // Clear entire backstack and start in new root
686 mState.stack.root = root;
687 mState.stack.clear();
688 mState.stackTouched = true;
689
690 if (!mRoots.isRecentsRoot(root)) {
691 new PickRootTask(root).executeOnExecutor(getCurrentExecutor());
692 } else {
693 onCurrentDirectoryChanged(ANIM_SIDE);
694 }
695 }
696
697 private class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
698 private RootInfo mRoot;
699
700 public PickRootTask(RootInfo root) {
701 mRoot = root;
702 }
703
704 @Override
705 protected DocumentInfo doInBackground(Void... params) {
706 try {
707 final Uri uri = DocumentsContract.buildDocumentUri(
708 mRoot.authority, mRoot.documentId);
709 return DocumentInfo.fromUri(getContentResolver(), uri);
710 } catch (FileNotFoundException e) {
711 Log.w(TAG, "Failed to find root", e);
712 return null;
713 }
714 }
715
716 @Override
717 protected void onPostExecute(DocumentInfo result) {
718 if (result != null) {
719 mState.stack.push(result);
720 mState.stackTouched = true;
721 onCurrentDirectoryChanged(ANIM_SIDE);
722 }
723 }
724 }
725
726 @Override
727 public void onAppPicked(ResolveInfo info) {
728 final Intent intent = new Intent(getIntent());
729 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
730 intent.setComponent(new ComponentName(
731 info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
732 startActivityForResult(intent, CODE_FORWARD);
733 }
734
735 @Override
736 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
737 Log.d(TAG, "onActivityResult() code=" + resultCode);
738
739 // Only relay back results when not canceled; otherwise stick around to
740 // let the user pick another app/backend.
741 if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
742
743 // Remember that we last picked via external app
744 final String packageName = getCallingPackageMaybeExtra();
745 final ContentValues values = new ContentValues();
746 values.put(ResumeColumns.EXTERNAL, 1);
747 getContentResolver().insert(RecentsProvider.buildResume(packageName), values);
748
749 // Pass back result to original caller
750 setResult(resultCode, data);
751 finish();
752 } else {
753 super.onActivityResult(requestCode, resultCode, data);
754 }
755 }
756
757 @Override
758 public void onDocumentPicked(DocumentInfo doc) {
759 final FragmentManager fm = getFragmentManager();
760 if (doc.isDirectory()) {
761 mState.stack.push(doc);
762 mState.stackTouched = true;
763 onCurrentDirectoryChanged(ANIM_DOWN);
764 } else {
765 // Fall back to viewing
766 final Intent view = new Intent(Intent.ACTION_VIEW);
767 view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
768 view.setData(doc.derivedUri);
769
770 try {
771 startActivity(view);
772 } catch (ActivityNotFoundException ex2) {
773 Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show();
774 }
775 }
776 }
777
778 public void onDocumentsPicked(List<DocumentInfo> docs) {
779 // TODO
780 }
781
782 @Override
783 public void onSaveRequested(DocumentInfo replaceTarget) {
784 new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getCurrentExecutor());
785 }
786
787 @Override
788 public void onSaveRequested(String mimeType, String displayName) {
789 new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor());
790 }
791
792 @Override
793 public void onPickRequested(DocumentInfo pickTarget) {
794 final Uri viaUri = DocumentsContract.buildTreeDocumentUri(pickTarget.authority,
795 pickTarget.documentId);
796 new PickFinishTask(viaUri).executeOnExecutor(getCurrentExecutor());
797 }
798
799 private void saveStackBlocking() {
800 final ContentResolver resolver = getContentResolver();
801 final ContentValues values = new ContentValues();
802
803 final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
804
805 // Remember location for next app launch
806 final String packageName = getCallingPackageMaybeExtra();
807 values.clear();
808 values.put(ResumeColumns.STACK, rawStack);
809 values.put(ResumeColumns.EXTERNAL, 0);
810 resolver.insert(RecentsProvider.buildResume(packageName), values);
811 }
812
813 private void onFinished(Uri... uris) {
814 Log.d(TAG, "onFinished() " + Arrays.toString(uris));
815
816 final Intent intent = new Intent();
817 if (uris.length == 1) {
818 intent.setData(uris[0]);
819 } else if (uris.length > 1) {
820 final ClipData clipData = new ClipData(
821 null, mState.acceptMimes, new ClipData.Item(uris[0]));
822 for (int i = 1; i < uris.length; i++) {
823 clipData.addItem(new ClipData.Item(uris[i]));
824 }
825 intent.setClipData(clipData);
826 }
827
828 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
829 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
830 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
831
832 setResult(Activity.RESULT_OK, intent);
833 finish();
834 }
835
836 private class CreateFinishTask extends AsyncTask<Void, Void, Uri> {
837 private final String mMimeType;
838 private final String mDisplayName;
839
840 public CreateFinishTask(String mimeType, String displayName) {
841 mMimeType = mimeType;
842 mDisplayName = displayName;
843 }
844
845 @Override
846 protected void onPreExecute() {
847 setPending(true);
848 }
849
850 @Override
851 protected Uri doInBackground(Void... params) {
852 final ContentResolver resolver = getContentResolver();
853 final DocumentInfo cwd = getCurrentDirectory();
854
855 ContentProviderClient client = null;
856 Uri childUri = null;
857 try {
858 client = DocumentsApplication.acquireUnstableProviderOrThrow(
859 resolver, cwd.derivedUri.getAuthority());
860 childUri = DocumentsContract.createDocument(
861 client, cwd.derivedUri, mMimeType, mDisplayName);
862 } catch (Exception e) {
863 Log.w(TAG, "Failed to create document", e);
864 } finally {
865 ContentProviderClient.releaseQuietly(client);
866 }
867
868 if (childUri != null) {
869 saveStackBlocking();
870 }
871
872 return childUri;
873 }
874
875 @Override
876 protected void onPostExecute(Uri result) {
877 if (result != null) {
878 onFinished(result);
879 } else {
880 Toast.makeText(StandaloneActivity.this, R.string.save_error, Toast.LENGTH_SHORT)
881 .show();
882 }
883
884 setPending(false);
885 }
886 }
887
888 private class ExistingFinishTask extends AsyncTask<Void, Void, Void> {
889 private final Uri[] mUris;
890
891 public ExistingFinishTask(Uri... uris) {
892 mUris = uris;
893 }
894
895 @Override
896 protected Void doInBackground(Void... params) {
897 saveStackBlocking();
898 return null;
899 }
900
901 @Override
902 protected void onPostExecute(Void result) {
903 onFinished(mUris);
904 }
905 }
906
907 private class PickFinishTask extends AsyncTask<Void, Void, Void> {
908 private final Uri mUri;
909
910 public PickFinishTask(Uri uri) {
911 mUri = uri;
912 }
913
914 @Override
915 protected Void doInBackground(Void... params) {
916 saveStackBlocking();
917 return null;
918 }
919
920 @Override
921 protected void onPostExecute(Void result) {
922 onFinished(mUri);
923 }
924 }
925
926 private void dumpStack() {
927 Log.d(TAG, "Current stack: ");
928 Log.d(TAG, " * " + mState.stack.root);
929 for (DocumentInfo doc : mState.stack) {
930 Log.d(TAG, " +-- " + doc);
931 }
932 }
933
934 public static BaseActivity get(Fragment fragment) {
935 return (BaseActivity) fragment.getActivity();
936 }
937}