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