Merge "Create unique files, root ordering, UI bugs." into klp-dev
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 39b453d..0650798 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -26,6 +26,8 @@
 import android.os.ParcelFileDescriptor;
 import android.content.res.AssetFileDescriptor;
 
+import dalvik.system.CloseGuard;
+
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
 
@@ -49,6 +51,8 @@
     private final boolean mStable;
     private boolean mReleased;
 
+    private final CloseGuard mGuard = CloseGuard.get();
+
     /**
      * @hide
      */
@@ -58,6 +62,7 @@
         mContentResolver = contentResolver;
         mPackageName = contentResolver.mPackageName;
         mStable = stable;
+        mGuard.open("release");
     }
 
     /** See {@link ContentProvider#query ContentProvider.query} */
@@ -324,6 +329,7 @@
                 throw new IllegalStateException("Already released");
             }
             mReleased = true;
+            mGuard.close();
             if (mStable) {
                 return mContentResolver.releaseProvider(mContentProvider);
             } else {
@@ -332,6 +338,13 @@
         }
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        if (mGuard != null) {
+            mGuard.warnIfOpen();
+        }
+    }
+
     /**
      * Get a reference to the {@link ContentProvider} that is associated with this
      * client. If the {@link ContentProvider} is running in a different process then
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 19a29f2..71a0567 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -11,7 +11,8 @@
         <!-- TODO: allow rotation when state saving is in better shape -->
         <activity
             android:name=".DocumentsActivity"
-            android:theme="@style/Theme">
+            android:theme="@style/Theme"
+            android:icon="@drawable/ic_doc_text">
             <intent-filter android:priority="100">
                 <action android:name="android.intent.action.OPEN_DOCUMENT" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 457bb19..6d2f9b9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -87,8 +87,8 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 
 public class DocumentsActivity extends Activity {
@@ -96,6 +96,8 @@
 
     private static final String EXTRA_STATE = "state";
 
+    private static final int CODE_FORWARD = 42;
+
     private boolean mShowAsDialog;
 
     private SearchView mSearchView;
@@ -843,11 +845,24 @@
 
     public void onAppPicked(ResolveInfo info) {
         final Intent intent = new Intent(getIntent());
-        intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+        intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
         intent.setComponent(new ComponentName(
                 info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
-        startActivity(intent);
-        finish();
+        startActivityForResult(intent, CODE_FORWARD);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Log.d(TAG, "onActivityResult() code=" + resultCode);
+
+        // Only relay back results when not canceled; otherwise stick around to
+        // let the user pick another app/backend.
+        if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
+            setResult(resultCode, data);
+            finish();
+        } else {
+            super.onActivityResult(requestCode, resultCode, data);
+        }
     }
 
     public void onDocumentPicked(DocumentInfo doc) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index 3659c6e..e390456 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -52,6 +52,7 @@
 import java.util.concurrent.TimeUnit;
 
 public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
+    private static final boolean LOGD = true;
 
     public static final int MAX_OUTSTANDING_RECENTS = 2;
 
@@ -63,7 +64,7 @@
     /**
      * Maximum documents from a single root.
      */
-    public static final int MAX_DOCS_FROM_ROOT = 24;
+    public static final int MAX_DOCS_FROM_ROOT = 64;
 
     private static final ExecutorService sExecutor = buildExecutor();
 
@@ -194,6 +195,11 @@
             }
         }
 
+        if (LOGD) {
+            Log.d(TAG, "Found " + cursors.size() + " of " + mTasks.size() + " recent queries done");
+            Log.d(TAG, sExecutor.toString());
+        }
+
         final DirectoryResult result = new DirectoryResult();
         result.sortOrder = SORT_ORDER_LAST_MODIFIED;
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 5076370..a396f79 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -195,12 +195,9 @@
 
             final SpannableStringBuilder builder = new SpannableStringBuilder();
             builder.append(stack.root.title);
-            appendDrawable(builder, crumb);
             for (int i = stack.size() - 2; i >= 0; i--) {
+                appendDrawable(builder, crumb);
                 builder.append(stack.get(i).displayName);
-                if (i > 0) {
-                    appendDrawable(builder, crumb);
-                }
             }
             title.setText(builder);
             title.setEllipsize(TruncateAt.MIDDLE);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 52d6cc8..15af8aa 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -179,6 +179,8 @@
             final Multimap<String, RootInfo> roots = ArrayListMultimap.create();
             final HashSet<String> stoppedAuthorities = Sets.newHashSet();
 
+            roots.put(mRecentsRoot.authority, mRecentsRoot);
+
             final ContentResolver resolver = mContext.getContentResolver();
             final PackageManager pm = mContext.getPackageManager();
             final List<ProviderInfo> providers = pm.queryContentProviders(
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index df9bce1..d602622 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -253,6 +253,7 @@
     }
 
     private static class SectionedRootsAdapter extends SectionedListAdapter {
+        private final RootsAdapter mRecent;
         private final RootsAdapter mServices;
         private final RootsAdapter mShortcuts;
         private final RootsAdapter mDevices;
@@ -260,12 +261,18 @@
 
         public SectionedRootsAdapter(
                 Context context, Collection<RootInfo> roots, Intent includeApps) {
+            mRecent = new RootsAdapter(context);
             mServices = new RootsAdapter(context);
             mShortcuts = new RootsAdapter(context);
             mDevices = new RootsAdapter(context);
             mApps = new AppsAdapter(context);
 
             for (RootInfo root : roots) {
+                if (root.authority == null) {
+                    mRecent.add(root);
+                    continue;
+                }
+
                 switch (root.rootType) {
                     case Root.ROOT_TYPE_SERVICE:
                         mServices.add(root);
@@ -297,15 +304,18 @@
             mShortcuts.sort(comp);
             mDevices.sort(comp);
 
+            if (mRecent.getCount() > 0) {
+                addSection(mRecent);
+            }
+            if (mServices.getCount() > 0) {
+                addSection(mServices);
+            }
             if (mShortcuts.getCount() > 0) {
                 addSection(mShortcuts);
             }
             if (mDevices.getCount() > 0) {
                 addSection(mDevices);
             }
-            if (mServices.getCount() > 0) {
-                addSection(mServices);
-            }
             if (mApps.getCount() > 0) {
                 addSection(mApps);
             }
@@ -315,12 +325,6 @@
     public static class RootComparator implements Comparator<RootInfo> {
         @Override
         public int compare(RootInfo lhs, RootInfo rhs) {
-            if (lhs.authority == null) {
-                return -1;
-            } else if (rhs.authority == null) {
-                return 1;
-            }
-
             final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
             if (score != 0) {
                 return score;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SettingsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/SettingsActivity.java
index a85f6a9..d423e3f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SettingsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SettingsActivity.java
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceManager;
+import android.view.MenuItem;
 
 public class SettingsActivity extends Activity {
     private static final String KEY_ADVANCED_DEVICES = "advancedDevices";
@@ -47,9 +48,19 @@
         final ActionBar bar = getActionBar();
         if (bar != null) {
             bar.setDisplayShowHomeEnabled(false);
+            bar.setDisplayHomeAsUpEnabled(true);
         }
     }
 
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
     public static class SettingsFragment extends PreferenceFragment {
         @Override
         public void onCreate(Bundle savedInstanceState) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 681cc9b..08a8c13 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -181,7 +181,7 @@
 
     @Override
     public String toString() {
-        return "Document{name=" + displayName + ", docId=" + documentId + "}";
+        return "Document{docId=" + documentId + ", name=" + displayName + "}";
     }
 
     public boolean isCreateSupported() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index a870c7b..014901a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -185,7 +185,7 @@
 
     @Override
     public String toString() {
-        return "Root{title=" + title + ", rootId=" + rootId + "}";
+        return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}";
     }
 
     public Drawable loadIcon(Context context) {
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index f468abc..0ef5f56 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -96,25 +96,6 @@
             throw new IllegalStateException(e);
         }
 
-        try {
-            final String rootId = "documents";
-            final File path = Environment.getExternalStoragePublicDirectory(
-                    Environment.DIRECTORY_DOCUMENTS);
-            mIdToPath.put(rootId, path);
-
-            final RootInfo root = new RootInfo();
-            root.rootId = rootId;
-            root.rootType = Root.ROOT_TYPE_SHORTCUT;
-            root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY
-                    | Root.FLAG_SUPPORTS_SEARCH;
-            root.title = getContext().getString(R.string.root_documents);
-            root.docId = getDocIdForFile(path);
-            mRoots.add(root);
-            mIdToRoot.put(rootId, root);
-        } catch (FileNotFoundException e) {
-            throw new IllegalStateException(e);
-        }
-
         return true;
     }
 
@@ -230,14 +211,23 @@
     public String createDocument(String docId, String mimeType, String displayName)
             throws FileNotFoundException {
         final File parent = getFileForDocId(docId);
-        displayName = validateDisplayName(mimeType, displayName);
+        File file;
 
-        final File file = new File(parent, displayName);
         if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+            file = new File(parent, displayName);
             if (!file.mkdir()) {
                 throw new IllegalStateException("Failed to mkdir " + file);
             }
         } else {
+            displayName = removeExtension(mimeType, displayName);
+            file = new File(parent, addExtension(mimeType, displayName));
+
+            // If conflicting file, try adding counter suffix
+            int n = 0;
+            while (file.exists() && n++ < 32) {
+                file = new File(parent, addExtension(mimeType, displayName + " (" + n + ")"));
+            }
+
             try {
                 if (!file.createNewFile()) {
                     throw new IllegalStateException("Failed to touch " + file);
@@ -354,20 +344,31 @@
         return "application/octet-stream";
     }
 
-    private static String validateDisplayName(String mimeType, String displayName) {
-        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
-            return displayName;
-        } else {
-            // Try appending meaningful extension if needed
-            if (!mimeType.equals(getTypeForName(displayName))) {
-                final String extension = MimeTypeMap.getSingleton()
-                        .getExtensionFromMimeType(mimeType);
-                if (extension != null) {
-                    displayName += "." + extension;
-                }
+    /**
+     * Remove file extension from name, but only if exact MIME type mapping
+     * exists. This means we can reapply the extension later.
+     */
+    private static String removeExtension(String mimeType, String name) {
+        final int lastDot = name.lastIndexOf('.');
+        if (lastDot >= 0) {
+            final String extension = name.substring(lastDot + 1);
+            final String nameMime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+            if (mimeType.equals(nameMime)) {
+                return name.substring(0, lastDot);
             }
-
-            return displayName;
         }
+        return name;
+    }
+
+    /**
+     * Add file extension to name, but only if exact MIME type mapping exists.
+     */
+    private static String addExtension(String mimeType, String name) {
+        final String extension = MimeTypeMap.getSingleton()
+                .getExtensionFromMimeType(mimeType);
+        if (extension != null) {
+            return name + "." + extension;
+        }
+        return name;
     }
 }