DocumentsActivity.java revision 0f7078f0045f354ba6573f0094e097a9afd6534d
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_OPEN; 23import static com.android.documentsui.BaseActivity.State.ACTION_OPEN_COPY_DESTINATION; 24import static com.android.documentsui.BaseActivity.State.ACTION_OPEN_TREE; 25import static com.android.documentsui.DirectoryFragment.ANIM_DOWN; 26import static com.android.documentsui.DirectoryFragment.ANIM_NONE; 27 28import android.app.Activity; 29import android.app.Fragment; 30import android.app.FragmentManager; 31import android.content.ActivityNotFoundException; 32import android.content.ClipData; 33import android.content.ComponentName; 34import android.content.ContentProviderClient; 35import android.content.ContentResolver; 36import android.content.ContentValues; 37import android.content.Context; 38import android.content.Intent; 39import android.content.pm.ResolveInfo; 40import android.content.res.Resources; 41import android.graphics.Point; 42import android.net.Uri; 43import android.os.AsyncTask; 44import android.os.Bundle; 45import android.os.Parcelable; 46import android.provider.DocumentsContract; 47import android.provider.DocumentsContract.Root; 48import android.util.Log; 49import android.view.Menu; 50import android.view.MenuItem; 51import android.view.View; 52import android.view.WindowManager; 53import android.widget.BaseAdapter; 54import android.widget.Spinner; 55import android.widget.Toast; 56import android.widget.Toolbar; 57 58import com.android.documentsui.RecentsProvider.RecentColumns; 59import com.android.documentsui.RecentsProvider.ResumeColumns; 60import com.android.documentsui.model.DocumentInfo; 61import com.android.documentsui.model.DurableUtils; 62import com.android.documentsui.model.RootInfo; 63 64import java.util.Arrays; 65import java.util.List; 66 67public class DocumentsActivity extends BaseActivity { 68 private static final int CODE_FORWARD = 42; 69 private static final String TAG = "DocumentsActivity"; 70 71 private boolean mShowAsDialog; 72 73 private Toolbar mToolbar; 74 private Spinner mToolbarStack; 75 76 private Toolbar mRootsToolbar; 77 78 private DirectoryContainerView mDirectoryContainer; 79 80 private ItemSelectedListener mStackListener; 81 private BaseAdapter mStackAdapter; 82 83 public DocumentsActivity() { 84 super(R.layout.docs_activity, TAG); 85 } 86 87 @Override 88 public void onCreate(Bundle icicle) { 89 super.onCreate(icicle); 90 91 final Resources res = getResources(); 92 mShowAsDialog = res.getBoolean(R.bool.show_as_dialog) && mState.action != ACTION_BROWSE; 93 94 if (!mShowAsDialog) { 95 setTheme(R.style.DocumentsNonDialogTheme); 96 } 97 98 final Context context = this; 99 100 if (mShowAsDialog) { 101 mDrawer = DrawerController.createDummy(); 102 103 // Strongly define our horizontal dimension; we leave vertical as 104 // WRAP_CONTENT so that system resizes us when IME is showing. 105 final WindowManager.LayoutParams a = getWindow().getAttributes(); 106 107 final Point size = new Point(); 108 getWindowManager().getDefaultDisplay().getSize(size); 109 a.width = (int) res.getFraction(R.dimen.dialog_width, size.x, size.x); 110 111 getWindow().setAttributes(a); 112 113 } else { 114 mDrawer = DrawerController.create(this); 115 } 116 117 mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory); 118 119 mToolbar = (Toolbar) findViewById(R.id.toolbar); 120 121 mStackAdapter = new StackAdapter(); 122 mStackListener = new ItemSelectedListener(); 123 mToolbarStack = (Spinner) findViewById(R.id.stack); 124 mToolbarStack.setOnItemSelectedListener(mStackListener); 125 126 mRootsToolbar = (Toolbar) findViewById(R.id.roots_toolbar); 127 128 setActionBar(mToolbar); 129 130 // Hide roots when we're managing a specific root 131 if (mState.action == ACTION_BROWSE) { 132 mDrawer.lockClosed(); 133 if (mShowAsDialog) { 134 findViewById(R.id.container_roots).setVisibility(View.GONE); 135 } 136 } 137 138 if (mState.action == ACTION_CREATE) { 139 final String mimeType = getIntent().getType(); 140 final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE); 141 SaveFragment.show(getFragmentManager(), mimeType, title); 142 } else if (mState.action == ACTION_OPEN_TREE || 143 mState.action == ACTION_OPEN_COPY_DESTINATION) { 144 PickFragment.show(getFragmentManager()); 145 } 146 147 if (mState.action == ACTION_GET_CONTENT) { 148 final Intent moreApps = new Intent(getIntent()); 149 moreApps.setComponent(null); 150 moreApps.setPackage(null); 151 RootsFragment.show(getFragmentManager(), moreApps); 152 } else if (mState.action == ACTION_OPEN || 153 mState.action == ACTION_CREATE || 154 mState.action == ACTION_OPEN_TREE || 155 mState.action == ACTION_OPEN_COPY_DESTINATION) { 156 RootsFragment.show(getFragmentManager(), null); 157 } 158 159 if (!mState.restored) { 160 // In this case, we set the activity title in AsyncTask.onPostExecute(). To prevent 161 // talkback from reading aloud the default title, we clear it here. 162 setTitle(""); 163 if (mState.action == ACTION_BROWSE) { 164 final Uri rootUri = getIntent().getData(); 165 new RestoreRootTask(rootUri).executeOnExecutor(getCurrentExecutor()); 166 } else { 167 new RestoreStackTask().execute(); 168 } 169 } else { 170 onCurrentDirectoryChanged(ANIM_NONE); 171 } 172 } 173 174 @Override 175 State buildState() { 176 State state = buildDefaultState(); 177 178 final Intent intent = getIntent(); 179 final String action = intent.getAction(); 180 if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { 181 state.action = ACTION_OPEN; 182 } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { 183 state.action = ACTION_CREATE; 184 } else if (Intent.ACTION_GET_CONTENT.equals(action)) { 185 state.action = ACTION_GET_CONTENT; 186 } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) { 187 state.action = ACTION_OPEN_TREE; 188 } else if (DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT.equals(action)) { 189 state.action = ACTION_BROWSE; 190 } else if (DocumentsIntent.ACTION_OPEN_COPY_DESTINATION.equals(action)) { 191 state.action = ACTION_OPEN_COPY_DESTINATION; 192 } 193 194 if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) { 195 state.allowMultiple = intent.getBooleanExtra( 196 Intent.EXTRA_ALLOW_MULTIPLE, false); 197 } 198 199 if (state.action == ACTION_BROWSE) { 200 state.acceptMimes = new String[] { "*/*" }; 201 state.allowMultiple = true; 202 } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { 203 state.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); 204 } else { 205 state.acceptMimes = new String[] { intent.getType() }; 206 } 207 208 if (state.action == ACTION_BROWSE) { 209 state.showSize = true; 210 } else { 211 state.showSize = LocalPreferences.getDisplayFileSize(this); 212 } 213 if (state.action == ACTION_OPEN_COPY_DESTINATION) { 214 state.directoryCopy = intent.getBooleanExtra( 215 BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false); 216 state.transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE, 217 CopyService.TRANSFER_MODE_NONE); 218 } 219 220 return state; 221 } 222 223 @Override 224 void onStackRestored(boolean restored, boolean external) { 225 // Show drawer when no stack restored, but only when requesting 226 // non-visual content. However, if we last used an external app, 227 // drawer is always shown. 228 229 boolean showDrawer = false; 230 if (!restored) { 231 showDrawer = true; 232 } 233 if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) { 234 showDrawer = false; 235 } 236 if (external && mState.action == ACTION_GET_CONTENT) { 237 showDrawer = true; 238 } 239 240 if (showDrawer) { 241 setRootsDrawerOpen(true); 242 } 243 } 244 245 public void onAppPicked(ResolveInfo info) { 246 final Intent intent = new Intent(getIntent()); 247 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT); 248 intent.setComponent(new ComponentName( 249 info.activityInfo.applicationInfo.packageName, info.activityInfo.name)); 250 startActivityForResult(intent, CODE_FORWARD); 251 } 252 253 @Override 254 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 255 Log.d(TAG, "onActivityResult() code=" + resultCode); 256 257 // Only relay back results when not canceled; otherwise stick around to 258 // let the user pick another app/backend. 259 if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) { 260 261 // Remember that we last picked via external app 262 final String packageName = getCallingPackageMaybeExtra(); 263 final ContentValues values = new ContentValues(); 264 values.put(ResumeColumns.EXTERNAL, 1); 265 getContentResolver().insert(RecentsProvider.buildResume(packageName), values); 266 267 // Pass back result to original caller 268 setResult(resultCode, data); 269 finish(); 270 } else { 271 super.onActivityResult(requestCode, resultCode, data); 272 } 273 } 274 275 @Override 276 protected void onPostCreate(Bundle savedInstanceState) { 277 super.onPostCreate(savedInstanceState); 278 mDrawer.syncState(); 279 updateActionBar(); 280 } 281 282 public void setRootsDrawerOpen(boolean open) { 283 mDrawer.setOpen(open); 284 } 285 286 @Override 287 public void updateActionBar() { 288 if (mRootsToolbar != null) { 289 final String prompt = getIntent().getStringExtra(DocumentsContract.EXTRA_PROMPT); 290 if (prompt != null) { 291 mRootsToolbar.setTitle(prompt); 292 } else { 293 if (mState.action == ACTION_OPEN || 294 mState.action == ACTION_GET_CONTENT || 295 mState.action == ACTION_OPEN_TREE) { 296 mRootsToolbar.setTitle(R.string.title_open); 297 } else if (mState.action == ACTION_CREATE || 298 mState.action == ACTION_OPEN_COPY_DESTINATION) { 299 mRootsToolbar.setTitle(R.string.title_save); 300 } 301 } 302 } 303 304 if (!mShowAsDialog && mDrawer.isUnlocked()) { 305 mToolbar.setNavigationIcon(R.drawable.ic_hamburger); 306 mToolbar.setNavigationContentDescription(R.string.drawer_open); 307 mToolbar.setNavigationOnClickListener(new View.OnClickListener() { 308 @Override 309 public void onClick(View v) { 310 setRootsDrawerOpen(true); 311 } 312 }); 313 } else { 314 mToolbar.setNavigationIcon(null); 315 mToolbar.setNavigationContentDescription(R.string.drawer_open); 316 mToolbar.setNavigationOnClickListener(null); 317 } 318 319 if (mSearchManager.isExpanded()) { 320 mToolbar.setTitle(null); 321 mToolbarStack.setVisibility(View.GONE); 322 mToolbarStack.setAdapter(null); 323 } else { 324 if (mState.stack.size() <= 1) { 325 mToolbar.setTitle(getCurrentRoot().title); 326 mToolbarStack.setVisibility(View.GONE); 327 mToolbarStack.setAdapter(null); 328 } else { 329 mToolbar.setTitle(null); 330 mToolbarStack.setVisibility(View.VISIBLE); 331 mToolbarStack.setAdapter(mStackAdapter); 332 333 mStackListener.mIgnoreNextNavigation = true; 334 mToolbarStack.setSelection(mStackAdapter.getCount() - 1); 335 } 336 } 337 } 338 339 @Override 340 public boolean onCreateOptionsMenu(Menu menu) { 341 boolean showMenu = super.onCreateOptionsMenu(menu); 342 343 // Most actions are visible when showing as dialog 344 if (mShowAsDialog) { 345 expandMenus(menu); 346 } 347 return showMenu; 348 } 349 350 @Override 351 public boolean onPrepareOptionsMenu(Menu menu) { 352 super.onPrepareOptionsMenu(menu); 353 354 final RootInfo root = getCurrentRoot(); 355 final DocumentInfo cwd = getCurrentDirectory(); 356 357 final MenuItem createDir = menu.findItem(R.id.menu_create_dir); 358 final MenuItem grid = menu.findItem(R.id.menu_grid); 359 final MenuItem list = menu.findItem(R.id.menu_list); 360 final MenuItem advanced = menu.findItem(R.id.menu_advanced); 361 final MenuItem fileSize = menu.findItem(R.id.menu_file_size); 362 final MenuItem settings = menu.findItem(R.id.menu_settings); 363 364 // File size is locked visible for browse because that is the action triggered by Settings, 365 // where the user is trying to find large files to clean up. 366 // TODO: instead of setting this according to the action, use a local preference, but 367 // provide a @hide extra to let callers like Settings force-enable size visibility. 368 boolean fileSizeVisible = mState.action != ACTION_BROWSE; 369 if (mState.action == ACTION_CREATE 370 || mState.action == ACTION_OPEN_TREE 371 || mState.action == ACTION_OPEN_COPY_DESTINATION) { 372 createDir.setVisible(cwd != null && cwd.isCreateSupported()); 373 mSearchManager.showMenu(false); 374 375 // No display options in recent directories 376 if (cwd == null) { 377 grid.setVisible(false); 378 list.setVisible(false); 379 fileSizeVisible = false; 380 } 381 382 if (mState.action == ACTION_CREATE) { 383 final FragmentManager fm = getFragmentManager(); 384 SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported()); 385 } 386 } else { 387 createDir.setVisible(false); 388 } 389 390 advanced.setVisible(mState.action != ACTION_BROWSE && !mState.forceAdvanced); 391 fileSize.setVisible(fileSizeVisible); 392 393 settings.setVisible(mState.action == ACTION_BROWSE 394 && (root.flags & Root.FLAG_HAS_SETTINGS) != 0); 395 396 return true; 397 } 398 399 @Override 400 public boolean onOptionsItemSelected(MenuItem item) { 401 return mDrawer.onOptionsItemSelected(item) || super.onOptionsItemSelected(item); 402 } 403 404 @Override 405 void onDirectoryChanged(int anim) { 406 final FragmentManager fm = getFragmentManager(); 407 final RootInfo root = getCurrentRoot(); 408 final DocumentInfo cwd = getCurrentDirectory(); 409 410 mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN); 411 412 if (cwd == null) { 413 // No directory means recents 414 if (mState.action == ACTION_CREATE || 415 mState.action == ACTION_OPEN_TREE || 416 mState.action == ACTION_OPEN_COPY_DESTINATION) { 417 RecentsCreateFragment.show(fm); 418 } else { 419 DirectoryFragment.showRecentsOpen(fm, anim); 420 421 // Start recents in grid when requesting visual things 422 final boolean visualMimes = MimePredicate.mimeMatches( 423 MimePredicate.VISUAL_MIMES, mState.acceptMimes); 424 mState.userMode = visualMimes ? State.MODE_GRID : State.MODE_LIST; 425 mState.derivedMode = mState.userMode; 426 } 427 } else { 428 if (mState.currentSearch != null) { 429 // Ongoing search 430 DirectoryFragment.showSearch(fm, root, mState.currentSearch, anim); 431 } else { 432 // Normal boring directory 433 DirectoryFragment.showNormal(fm, root, cwd, anim); 434 } 435 } 436 437 // Forget any replacement target 438 if (mState.action == ACTION_CREATE) { 439 final SaveFragment save = SaveFragment.get(fm); 440 if (save != null) { 441 save.setReplaceTarget(null); 442 } 443 } 444 445 if (mState.action == ACTION_OPEN_TREE || 446 mState.action == ACTION_OPEN_COPY_DESTINATION) { 447 final PickFragment pick = PickFragment.get(fm); 448 if (pick != null) { 449 pick.setPickTarget(mState.action, mState.transferMode, cwd); 450 } 451 } 452 } 453 454 void onSaveRequested(DocumentInfo replaceTarget) { 455 new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getCurrentExecutor()); 456 } 457 458 void onSaveRequested(String mimeType, String displayName) { 459 new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor()); 460 } 461 462 @Override 463 void onRootPicked(RootInfo root) { 464 super.onRootPicked(root); 465 setRootsDrawerOpen(false); 466 } 467 468 @Override 469 public void onDocumentPicked(DocumentInfo doc, DocumentContext context) { 470 final FragmentManager fm = getFragmentManager(); 471 if (doc.isDirectory()) { 472 openDirectory(doc); 473 } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { 474 // Explicit file picked, return 475 new ExistingFinishTask(doc.derivedUri).executeOnExecutor(getCurrentExecutor()); 476 } else if (mState.action == ACTION_CREATE) { 477 // Replace selected file 478 SaveFragment.get(fm).setReplaceTarget(doc); 479 } else if (mState.action == ACTION_BROWSE) { 480 // Go straight to viewing 481 final Intent view = new Intent(Intent.ACTION_VIEW); 482 view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 483 view.setData(doc.derivedUri); 484 485 try { 486 startActivity(view); 487 } catch (ActivityNotFoundException ex) { 488 Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show(); 489 } 490 } 491 } 492 493 @Override 494 public void onDocumentsPicked(List<DocumentInfo> docs) { 495 if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { 496 final int size = docs.size(); 497 final Uri[] uris = new Uri[size]; 498 for (int i = 0; i < size; i++) { 499 uris[i] = docs.get(i).derivedUri; 500 } 501 new ExistingFinishTask(uris).executeOnExecutor(getCurrentExecutor()); 502 } 503 } 504 505 public void onPickRequested(DocumentInfo pickTarget) { 506 Uri result; 507 if (mState.action == ACTION_OPEN_TREE) { 508 result = DocumentsContract.buildTreeDocumentUri( 509 pickTarget.authority, pickTarget.documentId); 510 } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) { 511 result = pickTarget.derivedUri; 512 } else { 513 // Should not be reached. 514 throw new IllegalStateException("Invalid mState.action."); 515 } 516 new PickFinishTask(result).executeOnExecutor(getCurrentExecutor()); 517 } 518 519 @Override 520 void saveStackBlocking() { 521 final ContentResolver resolver = getContentResolver(); 522 final ContentValues values = new ContentValues(); 523 524 final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack); 525 if (mState.action == ACTION_CREATE || 526 mState.action == ACTION_OPEN_TREE || 527 mState.action == ACTION_OPEN_COPY_DESTINATION) { 528 // Remember stack for last create 529 values.clear(); 530 values.put(RecentColumns.KEY, mState.stack.buildKey()); 531 values.put(RecentColumns.STACK, rawStack); 532 resolver.insert(RecentsProvider.buildRecent(), values); 533 } 534 535 // Remember location for next app launch 536 final String packageName = getCallingPackageMaybeExtra(); 537 values.clear(); 538 values.put(ResumeColumns.STACK, rawStack); 539 values.put(ResumeColumns.EXTERNAL, 0); 540 resolver.insert(RecentsProvider.buildResume(packageName), values); 541 } 542 543 @Override 544 void onTaskFinished(Uri... uris) { 545 Log.d(TAG, "onFinished() " + Arrays.toString(uris)); 546 547 final Intent intent = new Intent(); 548 if (uris.length == 1) { 549 intent.setData(uris[0]); 550 } else if (uris.length > 1) { 551 final ClipData clipData = new ClipData( 552 null, mState.acceptMimes, new ClipData.Item(uris[0])); 553 for (int i = 1; i < uris.length; i++) { 554 clipData.addItem(new ClipData.Item(uris[i])); 555 } 556 intent.setClipData(clipData); 557 } 558 559 if (mState.action == ACTION_GET_CONTENT) { 560 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 561 } else if (mState.action == ACTION_OPEN_TREE) { 562 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION 563 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION 564 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION 565 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); 566 } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) { 567 // Picking a copy destination is only used internally by us, so we 568 // don't need to extend permissions to the caller. 569 intent.putExtra(CopyService.EXTRA_STACK, (Parcelable) mState.stack); 570 intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mState.transferMode); 571 } else { 572 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION 573 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION 574 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); 575 } 576 577 setResult(Activity.RESULT_OK, intent); 578 finish(); 579 } 580 581 public static DocumentsActivity get(Fragment fragment) { 582 return (DocumentsActivity) fragment.getActivity(); 583 } 584 585 private final class PickFinishTask extends AsyncTask<Void, Void, Void> { 586 private final Uri mUri; 587 588 public PickFinishTask(Uri uri) { 589 mUri = uri; 590 } 591 592 @Override 593 protected Void doInBackground(Void... params) { 594 saveStackBlocking(); 595 return null; 596 } 597 598 @Override 599 protected void onPostExecute(Void result) { 600 onTaskFinished(mUri); 601 } 602 } 603 604 final class ExistingFinishTask extends AsyncTask<Void, Void, Void> { 605 private final Uri[] mUris; 606 607 public ExistingFinishTask(Uri... uris) { 608 mUris = uris; 609 } 610 611 @Override 612 protected Void doInBackground(Void... params) { 613 saveStackBlocking(); 614 return null; 615 } 616 617 @Override 618 protected void onPostExecute(Void result) { 619 onTaskFinished(mUris); 620 } 621 } 622 623 /** 624 * Task that creates a new document in the background. 625 */ 626 final class CreateFinishTask extends AsyncTask<Void, Void, Uri> { 627 private final String mMimeType; 628 private final String mDisplayName; 629 630 public CreateFinishTask(String mimeType, String displayName) { 631 mMimeType = mimeType; 632 mDisplayName = displayName; 633 } 634 635 @Override 636 protected void onPreExecute() { 637 setPending(true); 638 } 639 640 @Override 641 protected Uri doInBackground(Void... params) { 642 final ContentResolver resolver = getContentResolver(); 643 final DocumentInfo cwd = getCurrentDirectory(); 644 645 ContentProviderClient client = null; 646 Uri childUri = null; 647 try { 648 client = DocumentsApplication.acquireUnstableProviderOrThrow( 649 resolver, cwd.derivedUri.getAuthority()); 650 childUri = DocumentsContract.createDocument( 651 client, cwd.derivedUri, mMimeType, mDisplayName); 652 } catch (Exception e) { 653 Log.w(TAG, "Failed to create document", e); 654 } finally { 655 ContentProviderClient.releaseQuietly(client); 656 } 657 658 if (childUri != null) { 659 saveStackBlocking(); 660 } 661 662 return childUri; 663 } 664 665 @Override 666 protected void onPostExecute(Uri result) { 667 if (result != null) { 668 onTaskFinished(result); 669 } else { 670 Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT) 671 .show(); 672 } 673 674 setPending(false); 675 } 676 } 677} 678