DocumentsActivity.java revision 4ec973925fc2cd18f9ec0d0ca5af588564fded27
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.DocumentsActivity.State.ACTION_CREATE; 20import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT; 21import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE; 22import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN; 23import static com.android.documentsui.DocumentsActivity.State.MODE_GRID; 24import static com.android.documentsui.DocumentsActivity.State.MODE_LIST; 25import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; 26 27import android.app.ActionBar; 28import android.app.ActionBar.OnNavigationListener; 29import android.app.Activity; 30import android.app.Fragment; 31import android.app.FragmentManager; 32import android.content.ActivityNotFoundException; 33import android.content.ClipData; 34import android.content.ComponentName; 35import android.content.ContentResolver; 36import android.content.ContentValues; 37import android.content.Intent; 38import android.content.pm.ResolveInfo; 39import android.database.Cursor; 40import android.graphics.drawable.ColorDrawable; 41import android.net.Uri; 42import android.os.Bundle; 43import android.os.Parcel; 44import android.provider.DocumentsContract; 45import android.support.v4.app.ActionBarDrawerToggle; 46import android.support.v4.view.GravityCompat; 47import android.support.v4.widget.DrawerLayout; 48import android.support.v4.widget.DrawerLayout.DrawerListener; 49import android.util.Log; 50import android.view.LayoutInflater; 51import android.view.Menu; 52import android.view.MenuItem; 53import android.view.MenuItem.OnActionExpandListener; 54import android.view.View; 55import android.view.ViewGroup; 56import android.widget.BaseAdapter; 57import android.widget.ImageView; 58import android.widget.SearchView; 59import android.widget.SearchView.OnCloseListener; 60import android.widget.SearchView.OnQueryTextListener; 61import android.widget.TextView; 62import android.widget.Toast; 63 64import com.android.documentsui.RecentsProvider.RecentColumns; 65import com.android.documentsui.RecentsProvider.ResumeColumns; 66import com.android.documentsui.RecentsProvider.StateColumns; 67import com.android.documentsui.model.DocumentInfo; 68import com.android.documentsui.model.DocumentStack; 69import com.android.documentsui.model.DurableUtils; 70import com.android.documentsui.model.RootInfo; 71 72import libcore.io.IoUtils; 73 74import java.io.FileNotFoundException; 75import java.io.IOException; 76import java.util.Arrays; 77import java.util.List; 78 79public class DocumentsActivity extends Activity { 80 public static final String TAG = "Documents"; 81 82 private SearchView mSearchView; 83 84 private View mRootsContainer; 85 private DrawerLayout mDrawerLayout; 86 private ActionBarDrawerToggle mDrawerToggle; 87 88 private static final String EXTRA_STATE = "state"; 89 90 private boolean mIgnoreNextNavigation; 91 private boolean mIgnoreNextCollapse; 92 93 private RootsCache mRoots; 94 private State mState; 95 96 @Override 97 public void onCreate(Bundle icicle) { 98 super.onCreate(icicle); 99 100 mRoots = DocumentsApplication.getRootsCache(this); 101 102 setResult(Activity.RESULT_CANCELED); 103 setContentView(R.layout.activity); 104 105 mRootsContainer = findViewById(R.id.container_roots); 106 107 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); 108 109 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, 110 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close); 111 112 mDrawerLayout.setDrawerListener(mDrawerListener); 113 mDrawerLayout.setDrawerShadow(R.drawable.ic_drawer_shadow, GravityCompat.START); 114 115 if (icicle != null) { 116 mState = icicle.getParcelable(EXTRA_STATE); 117 } else { 118 buildDefaultState(); 119 } 120 121 if (mState.action == ACTION_MANAGE) { 122 mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); 123 } 124 125 if (mState.action == ACTION_CREATE) { 126 final String mimeType = getIntent().getType(); 127 final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE); 128 SaveFragment.show(getFragmentManager(), mimeType, title); 129 } 130 131 if (mState.action == ACTION_GET_CONTENT) { 132 final Intent moreApps = new Intent(getIntent()); 133 moreApps.setComponent(null); 134 moreApps.setPackage(null); 135 RootsFragment.show(getFragmentManager(), moreApps); 136 } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE) { 137 RootsFragment.show(getFragmentManager(), null); 138 } 139 140 onCurrentDirectoryChanged(); 141 } 142 143 private void buildDefaultState() { 144 mState = new State(); 145 146 final Intent intent = getIntent(); 147 final String action = intent.getAction(); 148 if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { 149 mState.action = ACTION_OPEN; 150 } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { 151 mState.action = ACTION_CREATE; 152 } else if (Intent.ACTION_GET_CONTENT.equals(action)) { 153 mState.action = ACTION_GET_CONTENT; 154 } else if (DocumentsContract.ACTION_MANAGE_ROOT.equals(action)) { 155 mState.action = ACTION_MANAGE; 156 } 157 158 if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { 159 mState.allowMultiple = intent.getBooleanExtra( 160 Intent.EXTRA_ALLOW_MULTIPLE, false); 161 } 162 163 if (mState.action == ACTION_MANAGE) { 164 mState.acceptMimes = new String[] { "*/*" }; 165 mState.allowMultiple = true; 166 } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { 167 mState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); 168 } else { 169 mState.acceptMimes = new String[] { intent.getType() }; 170 } 171 172 mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); 173 mState.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(this); 174 175 if (mState.action == ACTION_MANAGE) { 176 final Uri uri = intent.getData(); 177 final String rootId = DocumentsContract.getRootId(uri); 178 final RootInfo root = mRoots.getRoot(uri.getAuthority(), rootId); 179 if (root != null) { 180 onRootPicked(root, true); 181 } else { 182 Log.w(TAG, "Failed to find root: " + uri); 183 finish(); 184 } 185 186 } else { 187 // Restore last stack for calling package 188 // TODO: move into async loader 189 final String packageName = getCallingPackage(); 190 final Cursor cursor = getContentResolver() 191 .query(RecentsProvider.buildResume(packageName), null, null, null, null); 192 try { 193 if (cursor.moveToFirst()) { 194 final byte[] rawStack = cursor.getBlob( 195 cursor.getColumnIndex(ResumeColumns.STACK)); 196 DurableUtils.readFromArray(rawStack, mState.stack); 197 } 198 } catch (IOException e) { 199 Log.w(TAG, "Failed to resume", e); 200 } finally { 201 IoUtils.closeQuietly(cursor); 202 } 203 204 // If restored root isn't valid, fall back to recents 205 final RootInfo root = getCurrentRoot(); 206 final List<RootInfo> matchingRoots = mRoots.getMatchingRoots(mState); 207 if (!matchingRoots.contains(root)) { 208 mState.stack.reset(); 209 } 210 211 // Only open drawer when showing recents 212 if (mState.stack.isRecents()) { 213 mDrawerLayout.openDrawer(mRootsContainer); 214 } 215 } 216 } 217 218 @Override 219 public void onStart() { 220 super.onStart(); 221 222 if (mState.action == ACTION_MANAGE) { 223 mState.showSize = true; 224 } else { 225 mState.showSize = SettingsActivity.getDisplayFileSize(this); 226 } 227 } 228 229 private DrawerListener mDrawerListener = new DrawerListener() { 230 @Override 231 public void onDrawerSlide(View drawerView, float slideOffset) { 232 mDrawerToggle.onDrawerSlide(drawerView, slideOffset); 233 } 234 235 @Override 236 public void onDrawerOpened(View drawerView) { 237 mDrawerToggle.onDrawerOpened(drawerView); 238 updateActionBar(); 239 invalidateOptionsMenu(); 240 } 241 242 @Override 243 public void onDrawerClosed(View drawerView) { 244 mDrawerToggle.onDrawerClosed(drawerView); 245 updateActionBar(); 246 invalidateOptionsMenu(); 247 } 248 249 @Override 250 public void onDrawerStateChanged(int newState) { 251 mDrawerToggle.onDrawerStateChanged(newState); 252 } 253 }; 254 255 @Override 256 protected void onPostCreate(Bundle savedInstanceState) { 257 super.onPostCreate(savedInstanceState); 258 mDrawerToggle.syncState(); 259 } 260 261 public void updateActionBar() { 262 final ActionBar actionBar = getActionBar(); 263 264 actionBar.setDisplayShowHomeEnabled(true); 265 266 if (mState.action == ACTION_MANAGE) { 267 actionBar.setDisplayHomeAsUpEnabled(false); 268 mDrawerToggle.setDrawerIndicatorEnabled(false); 269 } else { 270 actionBar.setDisplayHomeAsUpEnabled(true); 271 mDrawerToggle.setDrawerIndicatorEnabled(true); 272 } 273 274 if (mDrawerLayout.isDrawerOpen(mRootsContainer)) { 275 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); 276 actionBar.setIcon(new ColorDrawable()); 277 278 if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { 279 actionBar.setTitle(R.string.title_open); 280 } else if (mState.action == ACTION_CREATE) { 281 actionBar.setTitle(R.string.title_save); 282 } 283 } else { 284 final RootInfo root = getCurrentRoot(); 285 actionBar.setIcon(root != null ? root.loadIcon(this) : null); 286 287 if (mState.stack.size() <= 1) { 288 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); 289 actionBar.setTitle(root.title); 290 } else { 291 mIgnoreNextNavigation = true; 292 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); 293 actionBar.setTitle(null); 294 actionBar.setListNavigationCallbacks(mStackAdapter, mStackListener); 295 actionBar.setSelectedNavigationItem(mStackAdapter.getCount() - 1); 296 } 297 } 298 } 299 300 @Override 301 public boolean onCreateOptionsMenu(Menu menu) { 302 super.onCreateOptionsMenu(menu); 303 getMenuInflater().inflate(R.menu.activity, menu); 304 305 final MenuItem searchMenu = menu.findItem(R.id.menu_search); 306 mSearchView = (SearchView) searchMenu.getActionView(); 307 mSearchView.setOnQueryTextListener(new OnQueryTextListener() { 308 @Override 309 public boolean onQueryTextSubmit(String query) { 310 mState.currentSearch = query; 311 onCurrentDirectoryChanged(); 312 return true; 313 } 314 315 @Override 316 public boolean onQueryTextChange(String newText) { 317 return false; 318 } 319 }); 320 321 searchMenu.setOnActionExpandListener(new OnActionExpandListener() { 322 @Override 323 public boolean onMenuItemActionExpand(MenuItem item) { 324 return true; 325 } 326 327 @Override 328 public boolean onMenuItemActionCollapse(MenuItem item) { 329 if (mIgnoreNextCollapse) { 330 mIgnoreNextCollapse = false; 331 return true; 332 } 333 334 mState.currentSearch = null; 335 onCurrentDirectoryChanged(); 336 return true; 337 } 338 }); 339 340 return true; 341 } 342 343 @Override 344 public boolean onPrepareOptionsMenu(Menu menu) { 345 super.onPrepareOptionsMenu(menu); 346 347 final FragmentManager fm = getFragmentManager(); 348 final DocumentInfo cwd = getCurrentDirectory(); 349 350 final MenuItem createDir = menu.findItem(R.id.menu_create_dir); 351 final MenuItem search = menu.findItem(R.id.menu_search); 352 final MenuItem sort = menu.findItem(R.id.menu_sort); 353 final MenuItem sortSize = menu.findItem(R.id.menu_sort_size); 354 final MenuItem grid = menu.findItem(R.id.menu_grid); 355 final MenuItem list = menu.findItem(R.id.menu_list); 356 final MenuItem settings = menu.findItem(R.id.menu_settings); 357 358 // Open drawer means we hide most actions 359 if (mDrawerLayout.isDrawerOpen(mRootsContainer)) { 360 createDir.setVisible(false); 361 search.setVisible(false); 362 sort.setVisible(false); 363 grid.setVisible(false); 364 list.setVisible(false); 365 mIgnoreNextCollapse = true; 366 search.collapseActionView(); 367 return true; 368 } 369 370 if (cwd != null) { 371 sort.setVisible(true); 372 grid.setVisible(mState.derivedMode != MODE_GRID); 373 list.setVisible(mState.derivedMode != MODE_LIST); 374 } else { 375 sort.setVisible(false); 376 grid.setVisible(false); 377 list.setVisible(false); 378 } 379 380 if (mState.currentSearch != null) { 381 // Search uses backend ranking; no sorting 382 sort.setVisible(false); 383 384 search.expandActionView(); 385 mSearchView.setQuery(mState.currentSearch, false); 386 } else { 387 mIgnoreNextCollapse = true; 388 search.collapseActionView(); 389 } 390 391 // Only sort by size when visible 392 sortSize.setVisible(mState.showSize); 393 394 final boolean searchVisible; 395 if (mState.action == ACTION_CREATE) { 396 createDir.setVisible(cwd != null && cwd.isCreateSupported()); 397 searchVisible = false; 398 399 // No display options in recent directories 400 if (cwd == null) { 401 grid.setVisible(false); 402 list.setVisible(false); 403 } 404 405 SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported()); 406 } else { 407 createDir.setVisible(false); 408 searchVisible = cwd != null && cwd.isSearchSupported(); 409 } 410 411 // TODO: close any search in-progress when hiding 412 search.setVisible(searchVisible); 413 414 settings.setVisible(mState.action != ACTION_MANAGE); 415 416 return true; 417 } 418 419 @Override 420 public boolean onOptionsItemSelected(MenuItem item) { 421 if (mDrawerToggle.onOptionsItemSelected(item)) { 422 return true; 423 } 424 425 final int id = item.getItemId(); 426 if (id == android.R.id.home) { 427 onBackPressed(); 428 return true; 429 } else if (id == R.id.menu_create_dir) { 430 CreateDirectoryFragment.show(getFragmentManager()); 431 return true; 432 } else if (id == R.id.menu_search) { 433 return false; 434 } else if (id == R.id.menu_sort_name) { 435 setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME); 436 return true; 437 } else if (id == R.id.menu_sort_date) { 438 setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED); 439 return true; 440 } else if (id == R.id.menu_sort_size) { 441 setUserSortOrder(State.SORT_ORDER_SIZE); 442 return true; 443 } else if (id == R.id.menu_grid) { 444 setUserMode(State.MODE_GRID); 445 return true; 446 } else if (id == R.id.menu_list) { 447 setUserMode(State.MODE_LIST); 448 return true; 449 } else if (id == R.id.menu_settings) { 450 startActivity(new Intent(this, SettingsActivity.class)); 451 return true; 452 } else { 453 return super.onOptionsItemSelected(item); 454 } 455 } 456 457 /** 458 * Update UI to reflect internal state changes not from user. 459 */ 460 public void onStateChanged() { 461 invalidateOptionsMenu(); 462 } 463 464 /** 465 * Set state sort order based on explicit user action. 466 */ 467 private void setUserSortOrder(int sortOrder) { 468 mState.userSortOrder = sortOrder; 469 DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged(); 470 } 471 472 /** 473 * Set state mode based on explicit user action. 474 */ 475 private void setUserMode(int mode) { 476 mState.userMode = mode; 477 DirectoryFragment.get(getFragmentManager()).onUserModeChanged(); 478 } 479 480 @Override 481 public void onBackPressed() { 482 if (!mState.stackTouched) { 483 super.onBackPressed(); 484 return; 485 } 486 487 final int size = mState.stack.size(); 488 if (size > 1) { 489 mState.stack.pop(); 490 onCurrentDirectoryChanged(); 491 } else if (size == 1 && !mDrawerLayout.isDrawerOpen(mRootsContainer)) { 492 // TODO: open root drawer once we can capture back key 493 super.onBackPressed(); 494 } else { 495 super.onBackPressed(); 496 } 497 } 498 499 @Override 500 protected void onSaveInstanceState(Bundle state) { 501 super.onSaveInstanceState(state); 502 state.putParcelable(EXTRA_STATE, mState); 503 } 504 505 @Override 506 protected void onRestoreInstanceState(Bundle state) { 507 super.onRestoreInstanceState(state); 508 updateActionBar(); 509 } 510 511 private BaseAdapter mStackAdapter = new BaseAdapter() { 512 @Override 513 public int getCount() { 514 return mState.stack.size(); 515 } 516 517 @Override 518 public DocumentInfo getItem(int position) { 519 return mState.stack.get(mState.stack.size() - position - 1); 520 } 521 522 @Override 523 public long getItemId(int position) { 524 return position; 525 } 526 527 @Override 528 public View getView(int position, View convertView, ViewGroup parent) { 529 if (convertView == null) { 530 convertView = LayoutInflater.from(parent.getContext()) 531 .inflate(R.layout.item_title, parent, false); 532 } 533 534 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 535 final DocumentInfo doc = getItem(position); 536 537 if (position == 0) { 538 final RootInfo root = getCurrentRoot(); 539 title.setText(root.title); 540 } else { 541 title.setText(doc.displayName); 542 } 543 544 // No padding when shown in actionbar 545 convertView.setPadding(0, 0, 0, 0); 546 return convertView; 547 } 548 549 @Override 550 public View getDropDownView(int position, View convertView, ViewGroup parent) { 551 if (convertView == null) { 552 convertView = LayoutInflater.from(parent.getContext()) 553 .inflate(R.layout.item_title, parent, false); 554 } 555 556 final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir); 557 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 558 final DocumentInfo doc = getItem(position); 559 560 if (position == 0) { 561 final RootInfo root = getCurrentRoot(); 562 title.setText(root.title); 563 subdir.setVisibility(View.GONE); 564 } else { 565 title.setText(doc.displayName); 566 subdir.setVisibility(View.VISIBLE); 567 } 568 569 return convertView; 570 } 571 }; 572 573 private OnNavigationListener mStackListener = new OnNavigationListener() { 574 @Override 575 public boolean onNavigationItemSelected(int itemPosition, long itemId) { 576 if (mIgnoreNextNavigation) { 577 mIgnoreNextNavigation = false; 578 return false; 579 } 580 581 while (mState.stack.size() > itemPosition + 1) { 582 mState.stackTouched = true; 583 mState.stack.pop(); 584 } 585 onCurrentDirectoryChanged(); 586 return true; 587 } 588 }; 589 590 public RootInfo getCurrentRoot() { 591 if (mState.stack.root != null) { 592 return mState.stack.root; 593 } else { 594 return mRoots.getRecentsRoot(); 595 } 596 } 597 598 public DocumentInfo getCurrentDirectory() { 599 return mState.stack.peek(); 600 } 601 602 public State getDisplayState() { 603 return mState; 604 } 605 606 private void onCurrentDirectoryChanged() { 607 final FragmentManager fm = getFragmentManager(); 608 final RootInfo root = getCurrentRoot(); 609 final DocumentInfo cwd = getCurrentDirectory(); 610 611 if (cwd == null) { 612 // No directory means recents 613 if (mState.action == ACTION_CREATE) { 614 RecentsCreateFragment.show(fm); 615 } else { 616 DirectoryFragment.showRecentsOpen(fm); 617 } 618 } else { 619 if (mState.currentSearch != null) { 620 // Ongoing search 621 DirectoryFragment.showSearch(fm, root, cwd, mState.currentSearch); 622 } else { 623 // Normal boring directory 624 DirectoryFragment.showNormal(fm, root, cwd); 625 } 626 } 627 628 // Forget any replacement target 629 if (mState.action == ACTION_CREATE) { 630 final SaveFragment save = SaveFragment.get(fm); 631 if (save != null) { 632 save.setReplaceTarget(null); 633 } 634 } 635 636 final RootsFragment roots = RootsFragment.get(fm); 637 if (roots != null) { 638 roots.onCurrentRootChanged(); 639 } 640 641 updateActionBar(); 642 invalidateOptionsMenu(); 643 dumpStack(); 644 } 645 646 public void onStackPicked(DocumentStack stack) { 647 mState.stack = stack; 648 mState.stackTouched = true; 649 onCurrentDirectoryChanged(); 650 } 651 652 public void onRootPicked(RootInfo root, boolean closeDrawer) { 653 // Clear entire backstack and start in new root 654 mState.stack.root = root; 655 mState.stack.clear(); 656 mState.stackTouched = true; 657 658 if (!mRoots.isRecentsRoot(root)) { 659 try { 660 final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId); 661 onDocumentPicked(DocumentInfo.fromUri(getContentResolver(), uri)); 662 } catch (FileNotFoundException e) { 663 } 664 } else { 665 onCurrentDirectoryChanged(); 666 } 667 668 if (closeDrawer) { 669 mDrawerLayout.closeDrawers(); 670 } 671 } 672 673 public void onAppPicked(ResolveInfo info) { 674 final Intent intent = new Intent(getIntent()); 675 intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); 676 intent.setComponent(new ComponentName( 677 info.activityInfo.applicationInfo.packageName, info.activityInfo.name)); 678 startActivity(intent); 679 finish(); 680 } 681 682 public void onDocumentPicked(DocumentInfo doc) { 683 final FragmentManager fm = getFragmentManager(); 684 if (doc.isDirectory()) { 685 mState.stack.push(doc); 686 mState.stackTouched = true; 687 onCurrentDirectoryChanged(); 688 } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { 689 // Explicit file picked, return 690 onFinished(doc.derivedUri); 691 } else if (mState.action == ACTION_CREATE) { 692 // Replace selected file 693 SaveFragment.get(fm).setReplaceTarget(doc); 694 } else if (mState.action == ACTION_MANAGE) { 695 // First try managing the document; we expect manager to filter 696 // based on authority, so we don't grant. 697 final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT); 698 manage.setData(doc.derivedUri); 699 700 try { 701 startActivity(manage); 702 } catch (ActivityNotFoundException ex) { 703 // Fall back to viewing 704 final Intent view = new Intent(Intent.ACTION_VIEW); 705 view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 706 view.setData(doc.derivedUri); 707 708 try { 709 startActivity(view); 710 } catch (ActivityNotFoundException ex2) { 711 Toast.makeText(this, R.string.toast_no_application, Toast.LENGTH_SHORT).show(); 712 } 713 } 714 } 715 } 716 717 public void onDocumentsPicked(List<DocumentInfo> docs) { 718 if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { 719 final int size = docs.size(); 720 final Uri[] uris = new Uri[size]; 721 for (int i = 0; i < size; i++) { 722 uris[i] = docs.get(i).derivedUri; 723 } 724 onFinished(uris); 725 } 726 } 727 728 public void onSaveRequested(DocumentInfo replaceTarget) { 729 onFinished(replaceTarget.derivedUri); 730 } 731 732 public void onSaveRequested(String mimeType, String displayName) { 733 final DocumentInfo cwd = getCurrentDirectory(); 734 735 final Uri childUri = DocumentsContract.createDocument( 736 getContentResolver(), cwd.derivedUri, mimeType, displayName); 737 if (childUri != null) { 738 onFinished(childUri); 739 } else { 740 Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show(); 741 } 742 } 743 744 private void onFinished(Uri... uris) { 745 Log.d(TAG, "onFinished() " + Arrays.toString(uris)); 746 747 final ContentResolver resolver = getContentResolver(); 748 final ContentValues values = new ContentValues(); 749 750 final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack); 751 if (mState.action == ACTION_CREATE) { 752 // Remember stack for last create 753 values.clear(); 754 values.put(RecentColumns.STACK, rawStack); 755 resolver.insert(RecentsProvider.buildRecent(), values); 756 } 757 758 // Remember location for next app launch 759 final String packageName = getCallingPackage(); 760 values.clear(); 761 values.put(ResumeColumns.STACK, rawStack); 762 resolver.insert(RecentsProvider.buildResume(packageName), values); 763 764 final Intent intent = new Intent(); 765 if (uris.length == 1) { 766 intent.setData(uris[0]); 767 } else if (uris.length > 1) { 768 final ClipData clipData = new ClipData( 769 null, mState.acceptMimes, new ClipData.Item(uris[0])); 770 for (int i = 1; i < uris.length; i++) { 771 clipData.addItem(new ClipData.Item(uris[i])); 772 } 773 intent.setClipData(clipData); 774 } 775 776 if (mState.action == ACTION_GET_CONTENT) { 777 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 778 } else { 779 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION 780 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION 781 | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION); 782 } 783 784 setResult(Activity.RESULT_OK, intent); 785 finish(); 786 } 787 788 public static class State implements android.os.Parcelable { 789 public int action; 790 public String[] acceptMimes; 791 792 /** Explicit user choice */ 793 public int userMode = MODE_UNKNOWN; 794 /** Derived after loader */ 795 public int derivedMode = MODE_LIST; 796 797 /** Explicit user choice */ 798 public int userSortOrder = SORT_ORDER_UNKNOWN; 799 /** Derived after loader */ 800 public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME; 801 802 public boolean allowMultiple = false; 803 public boolean showSize = false; 804 public boolean localOnly = false; 805 public boolean showAdvanced = false; 806 public boolean stackTouched = false; 807 808 /** Current user navigation stack; empty implies recents. */ 809 public DocumentStack stack = new DocumentStack(); 810 /** Currently active search, overriding any stack. */ 811 public String currentSearch; 812 813 public static final int ACTION_OPEN = 1; 814 public static final int ACTION_CREATE = 2; 815 public static final int ACTION_GET_CONTENT = 3; 816 public static final int ACTION_MANAGE = 4; 817 818 public static final int MODE_UNKNOWN = 0; 819 public static final int MODE_LIST = 1; 820 public static final int MODE_GRID = 2; 821 822 public static final int SORT_ORDER_UNKNOWN = 0; 823 public static final int SORT_ORDER_DISPLAY_NAME = 1; 824 public static final int SORT_ORDER_LAST_MODIFIED = 2; 825 public static final int SORT_ORDER_SIZE = 3; 826 827 @Override 828 public int describeContents() { 829 return 0; 830 } 831 832 @Override 833 public void writeToParcel(Parcel out, int flags) { 834 out.writeInt(action); 835 out.writeInt(userMode); 836 out.writeStringArray(acceptMimes); 837 out.writeInt(userSortOrder); 838 out.writeInt(allowMultiple ? 1 : 0); 839 out.writeInt(showSize ? 1 : 0); 840 out.writeInt(localOnly ? 1 : 0); 841 out.writeInt(showAdvanced ? 1 : 0); 842 out.writeInt(stackTouched ? 1 : 0); 843 DurableUtils.writeToParcel(out, stack); 844 out.writeString(currentSearch); 845 } 846 847 public static final Creator<State> CREATOR = new Creator<State>() { 848 @Override 849 public State createFromParcel(Parcel in) { 850 final State state = new State(); 851 state.action = in.readInt(); 852 state.userMode = in.readInt(); 853 state.acceptMimes = in.readStringArray(); 854 state.userSortOrder = in.readInt(); 855 state.allowMultiple = in.readInt() != 0; 856 state.showSize = in.readInt() != 0; 857 state.localOnly = in.readInt() != 0; 858 state.showAdvanced = in.readInt() != 0; 859 state.stackTouched = in.readInt() != 0; 860 DurableUtils.readFromParcel(in, state.stack); 861 state.currentSearch = in.readString(); 862 return state; 863 } 864 865 @Override 866 public State[] newArray(int size) { 867 return new State[size]; 868 } 869 }; 870 } 871 872 private void dumpStack() { 873 Log.d(TAG, "Current stack: "); 874 Log.d(TAG, " * " + mState.stack.root); 875 for (DocumentInfo doc : mState.stack) { 876 Log.d(TAG, " +-- " + doc); 877 } 878 } 879 880 public static DocumentsActivity get(Fragment fragment) { 881 return (DocumentsActivity) fragment.getActivity(); 882 } 883} 884