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