summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-08-27 18:26:48 -0700
committerJeff Sharkey <jsharkey@android.com>2013-08-28 20:49:42 -0700
commitaeb16e2435f9975b9fa1fc4b747796647a21292e (patch)
tree2e228b0ca25799bf59472d5df08d71e068498b2b /packages
parent61f0f9ada51a6718075acfe7fafbb9954f38e980 (diff)
downloadframeworks_base-aeb16e2435f9975b9fa1fc4b747796647a21292e.zip
frameworks_base-aeb16e2435f9975b9fa1fc4b747796647a21292e.tar.gz
frameworks_base-aeb16e2435f9975b9fa1fc4b747796647a21292e.tar.bz2
Stronger DocumentsProvider contract.
Using a contract class requires that a provider implement it exactly with little help. This change introduces a DocumentsProvider abstract class that provides a client-side implementation of the contract that greatly reduces developer burden, and improves correctness. This also moves to first-class DocumentRoot objects, and moves calls with complex side effects to be ContentProvider.call() invocations, offering more granular permission control over Uri operations that shouldn't be available through Uri grants. This new design also relaxes the requirement that root information be burned into every Uri. Migrate ExternalDocumentsProvider and DocumentsUI to adopt new API. Bug: 10497206 Change-Id: I6f2b3f519bfd62a9d693223ea5628a971ce2e743
Diffstat (limited to 'packages')
-rw-r--r--packages/DocumentsUI/AndroidManifest.xml4
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java29
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java59
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java18
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java5
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java65
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java6
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RootsCache.java118
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java45
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java4
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/Document.java21
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java17
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java121
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/Root.java123
-rw-r--r--packages/ExternalStorageProvider/AndroidManifest.xml13
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/CloudTestDocumentsProvider.java253
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java473
17 files changed, 358 insertions, 1016 deletions
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index d79f5c6..6cc92e3 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -35,9 +35,9 @@
</intent-filter>
<!-- data expected to point at existing root to manage -->
<intent-filter>
- <action android:name="android.intent.action.MANAGE_DOCUMENT" />
+ <action android:name="android.provider.action.MANAGE_DOCUMENTS" />
<category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.item/root" />
+ <data android:mimeType="vnd.android.doc/dir" />
</intent-filter>
</activity>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index 575947f..6bc554f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -20,14 +20,14 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.net.Uri;
import android.os.Bundle;
-import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Documents;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,8 +36,6 @@ import android.widget.Toast;
import com.android.documentsui.model.Document;
-import java.io.FileNotFoundException;
-
/**
* Dialog to create a new directory.
*/
@@ -58,7 +56,7 @@ public class CreateDirectoryFragment extends DialogFragment {
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
final View view = dialogInflater.inflate(R.layout.dialog_create_dir, null, false);
- final EditText text1 = (EditText)view.findViewById(android.R.id.text1);
+ final EditText text1 = (EditText) view.findViewById(android.R.id.text1);
builder.setTitle(R.string.menu_create_dir);
builder.setView(view);
@@ -68,24 +66,25 @@ public class CreateDirectoryFragment extends DialogFragment {
public void onClick(DialogInterface dialog, int which) {
final String displayName = text1.getText().toString();
- final ContentValues values = new ContentValues();
- values.put(DocumentColumns.MIME_TYPE, Documents.MIME_TYPE_DIR);
- values.put(DocumentColumns.DISPLAY_NAME, displayName);
-
final DocumentsActivity activity = (DocumentsActivity) getActivity();
final Document cwd = activity.getCurrentDirectory();
- Uri childUri = resolver.insert(cwd.uri, values);
+ final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
+ cwd.uri.getAuthority());
try {
+ final String docId = DocumentsContract.createDocument(client,
+ DocumentsContract.getDocId(cwd.uri), Documents.MIME_TYPE_DIR,
+ displayName);
+
// Navigate into newly created child
+ final Uri childUri = DocumentsContract.buildDocumentUri(
+ cwd.uri.getAuthority(), docId);
final Document childDoc = Document.fromUri(resolver, childUri);
activity.onDocumentPicked(childDoc);
- } catch (FileNotFoundException e) {
- childUri = null;
- }
-
- if (childUri == null) {
+ } catch (Exception e) {
Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show();
+ } finally {
+ ContentProviderClient.closeQuietly(client);
}
}
});
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index dd9aee5..783b6ff 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -20,8 +20,8 @@ import static com.android.documentsui.DocumentsActivity.TAG;
import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE;
import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID;
import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST;
-import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DATE;
-import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_NAME;
+import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DISPLAY_NAME;
+import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED;
import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_SIZE;
import android.app.Fragment;
@@ -32,7 +32,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
-import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
@@ -55,7 +54,6 @@ import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
-import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ListView;
@@ -64,7 +62,6 @@ import android.widget.Toast;
import com.android.documentsui.DocumentsActivity.DisplayState;
import com.android.documentsui.model.Document;
-import com.android.documentsui.model.Root;
import com.android.internal.util.Predicate;
import com.google.android.collect.Lists;
@@ -81,7 +78,6 @@ public class DirectoryFragment extends Fragment {
private View mEmptyView;
private ListView mListView;
private GridView mGridView;
- private Button mMoreView;
private AbsListView mCurrentView;
@@ -110,7 +106,8 @@ public class DirectoryFragment extends Fragment {
}
public static void showSearch(FragmentManager fm, Uri uri, String query) {
- final Uri searchUri = DocumentsContract.buildSearchUri(uri, query);
+ final Uri searchUri = DocumentsContract.buildSearchUri(
+ uri.getAuthority(), DocumentsContract.getDocId(uri), query);
show(fm, TYPE_SEARCH, searchUri);
}
@@ -153,8 +150,6 @@ public class DirectoryFragment extends Fragment {
mGridView.setOnItemClickListener(mItemListener);
mGridView.setMultiChoiceModeListener(mMultiListener);
- mMoreView = (Button) view.findViewById(R.id.more);
-
mAdapter = new DocumentsAdapter();
final Uri uri = getArguments().getParcelable(EXTRA_URI);
@@ -168,22 +163,19 @@ public class DirectoryFragment extends Fragment {
Uri contentsUri;
if (mType == TYPE_NORMAL) {
- contentsUri = DocumentsContract.buildContentsUri(uri);
+ contentsUri = DocumentsContract.buildChildrenUri(
+ uri.getAuthority(), DocumentsContract.getDocId(uri));
} else if (mType == TYPE_RECENT_OPEN) {
contentsUri = RecentsProvider.buildRecentOpen();
} else {
contentsUri = uri;
}
- if (state.localOnly) {
- contentsUri = DocumentsContract.setLocalOnly(contentsUri);
- }
-
final Comparator<Document> sortOrder;
- if (state.sortOrder == SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN) {
- sortOrder = new Document.DateComparator();
- } else if (state.sortOrder == SORT_ORDER_NAME) {
- sortOrder = new Document.NameComparator();
+ if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) {
+ sortOrder = new Document.LastModifiedComparator();
+ } else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) {
+ sortOrder = new Document.DisplayNameComparator();
} else if (state.sortOrder == SORT_ORDER_SIZE) {
sortOrder = new Document.SizeComparator();
} else {
@@ -196,28 +188,6 @@ public class DirectoryFragment extends Fragment {
@Override
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
mAdapter.swapDocuments(result.contents);
-
- final Cursor cursor = result.cursor;
- if (cursor != null && cursor.getExtras()
- .getBoolean(DocumentsContract.EXTRA_HAS_MORE, false)) {
- mMoreView.setText(R.string.more);
- mMoreView.setVisibility(View.VISIBLE);
- mMoreView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mMoreView.setText(R.string.loading);
- final Bundle bundle = new Bundle();
- bundle.putBoolean(DocumentsContract.EXTRA_REQUEST_MORE, true);
- try {
- cursor.respond(bundle);
- } catch (Exception e) {
- Log.w(TAG, "Failed to respond: " + e);
- }
- }
- });
- } else {
- mMoreView.setVisibility(View.GONE);
- }
}
@Override
@@ -489,8 +459,7 @@ public class DirectoryFragment extends Fragment {
task.execute(doc.uri);
}
} else {
- icon.setImageDrawable(roots.resolveDocumentIcon(
- context, doc.uri.getAuthority(), doc.mimeType));
+ icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, doc.mimeType));
}
title.setText(doc.displayName);
@@ -504,11 +473,7 @@ public class DirectoryFragment extends Fragment {
summary.setVisibility(View.INVISIBLE);
}
} else if (mType == TYPE_RECENT_OPEN) {
- final Root root = roots.findRoot(doc);
- icon1.setVisibility(View.VISIBLE);
- icon1.setImageDrawable(root.icon);
- summary.setText(root.getDirectoryString());
- summary.setVisibility(View.VISIBLE);
+ // TODO: resolve storage root
}
if (summaryGrid != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 14d6fd5..4ce5ef8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -26,7 +26,6 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
-import android.provider.DocumentsContract.DocumentColumns;
import android.util.Log;
import com.android.documentsui.model.Document;
@@ -77,9 +76,10 @@ public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
}
private void loadInBackgroundInternal(
- DirectoryResult result, Uri uri, CancellationSignal signal) {
+ DirectoryResult result, Uri uri, CancellationSignal signal) throws RuntimeException {
+ // TODO: switch to using unstable CPC
final ContentResolver resolver = getContext().getContentResolver();
- final Cursor cursor = resolver.query(uri, null, null, null, getQuerySortOrder(), signal);
+ final Cursor cursor = resolver.query(uri, null, null, null, null, signal);
result.cursor = cursor;
result.cursor.registerContentObserver(mObserver);
@@ -110,16 +110,4 @@ public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
Collections.sort(result.contents, mSortOrder);
}
}
-
- private String getQuerySortOrder() {
- if (mSortOrder instanceof Document.DateComparator) {
- return DocumentColumns.LAST_MODIFIED + " DESC";
- } else if (mSortOrder instanceof Document.NameComparator) {
- return DocumentColumns.DISPLAY_NAME + " ASC";
- } else if (mSortOrder instanceof Document.SizeComparator) {
- return DocumentColumns.SIZE + " DESC";
- } else {
- return null;
- }
- }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java
index 72afd9e..0ce5968 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java
@@ -21,12 +21,11 @@ import static com.android.documentsui.DocumentsActivity.TAG;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.provider.DocumentsContract.DocumentRoot;
import android.util.Log;
-import com.android.documentsui.model.Root;
-
/**
- * Handles {@link Root} changes which invalidate cached data.
+ * Handles {@link DocumentRoot} changes which invalidate cached data.
*/
public class DocumentChangedReceiver extends BroadcastReceiver {
@Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 22e3b98..73ca8fa 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -22,7 +22,7 @@ import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANA
import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_OPEN;
import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID;
import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST;
-import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DATE;
+import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
@@ -32,6 +32,7 @@ import android.app.FragmentManager;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ComponentName;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
@@ -41,7 +42,7 @@ import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract.DocumentRoot;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
@@ -61,7 +62,6 @@ import android.widget.Toast;
import com.android.documentsui.model.Document;
import com.android.documentsui.model.DocumentStack;
-import com.android.documentsui.model.Root;
import java.io.FileNotFoundException;
import java.util.Arrays;
@@ -101,7 +101,7 @@ public class DocumentsActivity extends Activity {
mAction = ACTION_CREATE;
} else if (Intent.ACTION_GET_CONTENT.equals(action)) {
mAction = ACTION_GET_CONTENT;
- } else if (Intent.ACTION_MANAGE_DOCUMENT.equals(action)) {
+ } else if (DocumentsContract.ACTION_MANAGE_DOCUMENTS.equals(action)) {
mAction = ACTION_MANAGE;
}
@@ -143,7 +143,7 @@ public class DocumentsActivity extends Activity {
}
if (mAction == ACTION_MANAGE) {
- mDisplayState.sortOrder = SORT_ORDER_DATE;
+ mDisplayState.sortOrder = SORT_ORDER_LAST_MODIFIED;
}
mRootsContainer = findViewById(R.id.container_roots);
@@ -160,10 +160,7 @@ public class DocumentsActivity extends Activity {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
final Uri rootUri = intent.getData();
- final String authority = rootUri.getAuthority();
- final String rootId = DocumentsContract.getRootId(rootUri);
-
- final Root root = mRoots.findRoot(authority, rootId);
+ final DocumentRoot root = mRoots.findRoot(rootUri);
if (root != null) {
onRootPicked(root, true);
} else {
@@ -255,10 +252,10 @@ public class DocumentsActivity extends Activity {
mDrawerToggle.setDrawerIndicatorEnabled(true);
} else {
- final Root root = getCurrentRoot();
- actionBar.setIcon(root != null ? root.icon : null);
+ final DocumentRoot root = getCurrentRoot();
+ actionBar.setIcon(root != null ? root.loadIcon(this) : null);
- if (root.isRecents) {
+ if (mRoots.isRecentsRoot(root)) {
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setTitle(root.title);
} else {
@@ -441,9 +438,8 @@ public class DocumentsActivity extends Activity {
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
- final Document cwd = getCurrentDirectory();
- if (cwd != null) {
- title.setText(cwd.displayName);
+ if (mStack.size() > 0) {
+ title.setText(mStack.getTitle(mRoots));
} else {
// No directory means recents
title.setText(R.string.root_recent);
@@ -477,10 +473,9 @@ public class DocumentsActivity extends Activity {
}
};
- public Root getCurrentRoot() {
- final Document cwd = getCurrentDirectory();
- if (cwd != null) {
- return mRoots.findRoot(cwd);
+ public DocumentRoot getCurrentRoot() {
+ if (mStack.size() > 0) {
+ return mStack.getRoot(mRoots);
} else {
return mRoots.getRecentsRoot();
}
@@ -538,13 +533,14 @@ public class DocumentsActivity extends Activity {
onCurrentDirectoryChanged();
}
- public void onRootPicked(Root root, boolean closeDrawer) {
+ public void onRootPicked(DocumentRoot root, boolean closeDrawer) {
// Clear entire backstack and start in new root
mStack.clear();
- if (!root.isRecents) {
+ if (!mRoots.isRecentsRoot(root)) {
try {
- onDocumentPicked(Document.fromRoot(getContentResolver(), root));
+ final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.docId);
+ onDocumentPicked(Document.fromUri(getContentResolver(), uri));
} catch (FileNotFoundException e) {
}
} else {
@@ -611,16 +607,21 @@ public class DocumentsActivity extends Activity {
}
public void onSaveRequested(String mimeType, String displayName) {
- final ContentValues values = new ContentValues();
- values.put(DocumentColumns.MIME_TYPE, mimeType);
- values.put(DocumentColumns.DISPLAY_NAME, displayName);
-
final Document cwd = getCurrentDirectory();
- final Uri childUri = getContentResolver().insert(cwd.uri, values);
- if (childUri != null) {
+ final String authority = cwd.uri.getAuthority();
+
+ final ContentProviderClient client = getContentResolver()
+ .acquireUnstableContentProviderClient(authority);
+ try {
+ final String docId = DocumentsContract.createDocument(client,
+ DocumentsContract.getDocId(cwd.uri), mimeType, displayName);
+
+ final Uri childUri = DocumentsContract.buildDocumentUri(authority, docId);
onFinished(childUri);
- } else {
+ } catch (Exception e) {
Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
+ } finally {
+ ContentProviderClient.closeQuietly(client);
}
}
@@ -680,7 +681,7 @@ public class DocumentsActivity extends Activity {
public int action;
public int mode = MODE_LIST;
public String[] acceptMimes;
- public int sortOrder = SORT_ORDER_NAME;
+ public int sortOrder = SORT_ORDER_DISPLAY_NAME;
public boolean allowMultiple = false;
public boolean showSize = false;
public boolean localOnly = false;
@@ -693,8 +694,8 @@ public class DocumentsActivity extends Activity {
public static final int MODE_LIST = 0;
public static final int MODE_GRID = 1;
- public static final int SORT_ORDER_NAME = 0;
- public static final int SORT_ORDER_DATE = 1;
+ public static final int SORT_ORDER_DISPLAY_NAME = 0;
+ public static final int SORT_ORDER_LAST_MODIFIED = 1;
public static final int SORT_ORDER_SIZE = 2;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 5466dbf..3447a51 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -29,6 +29,7 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.provider.DocumentsContract.DocumentRoot;
import android.text.TextUtils.TruncateAt;
import android.util.Log;
import android.view.LayoutInflater;
@@ -42,7 +43,6 @@ import android.widget.ListView;
import android.widget.TextView;
import com.android.documentsui.model.DocumentStack;
-import com.android.documentsui.model.Root;
import com.google.android.collect.Lists;
import libcore.io.IoUtils;
@@ -181,8 +181,8 @@ public class RecentsCreateFragment extends Fragment {
final View summaryList = convertView.findViewById(R.id.summary_list);
final DocumentStack stack = getItem(position);
- final Root root = roots.findRoot(stack.peek());
- icon.setImageDrawable(root != null ? root.icon : null);
+ final DocumentRoot root = stack.getRoot(roots);
+ icon.setImageDrawable(root.loadIcon(context));
final StringBuilder builder = new StringBuilder();
for (int i = stack.size() - 1; i >= 0; i--) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index c3b498e..aa21457 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -18,30 +18,24 @@ package com.android.documentsui;
import static com.android.documentsui.DocumentsActivity.TAG;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
-import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.DocumentRoot;
import android.provider.DocumentsContract.Documents;
import android.util.Log;
-import android.util.Pair;
-import com.android.documentsui.model.Document;
-import com.android.documentsui.model.DocumentsProviderInfo;
-import com.android.documentsui.model.DocumentsProviderInfo.Icon;
-import com.android.documentsui.model.Root;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Objects;
import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
/**
@@ -54,14 +48,9 @@ public class RootsCache {
private final Context mContext;
- /** Map from authority to cached info */
- private HashMap<String, DocumentsProviderInfo> mProviders = Maps.newHashMap();
- /** Map from (authority+rootId) to cached info */
- private HashMap<Pair<String, String>, Root> mRoots = Maps.newHashMap();
+ public List<DocumentRoot> mRoots = Lists.newArrayList();
- public ArrayList<Root> mRootsList = Lists.newArrayList();
-
- private Root mRecentsRoot;
+ private DocumentRoot mRecentsRoot;
public RootsCache(Context context) {
mContext = context;
@@ -73,95 +62,78 @@ public class RootsCache {
*/
@GuardedBy("ActivityThread")
public void update() {
- mProviders.clear();
mRoots.clear();
- mRootsList.clear();
{
// Create special root for recents
- final Root root = Root.buildRecents(mContext);
- mRootsList.add(root);
+ final DocumentRoot root = new DocumentRoot();
+ root.rootType = DocumentRoot.ROOT_TYPE_SHORTCUT;
+ root.docId = null;
+ root.icon = R.drawable.ic_dir;
+ root.title = mContext.getString(R.string.root_recent);
+ root.summary = null;
+ root.availableBytes = -1;
+
+ mRoots.add(root);
mRecentsRoot = root;
}
// Query for other storage backends
+ final ContentResolver resolver = mContext.getContentResolver();
final PackageManager pm = mContext.getPackageManager();
final List<ProviderInfo> providers = pm.queryContentProviders(
null, -1, PackageManager.GET_META_DATA);
- for (ProviderInfo providerInfo : providers) {
- if (providerInfo.metaData != null && providerInfo.metaData.containsKey(
+ for (ProviderInfo info : providers) {
+ if (info.metaData != null && info.metaData.containsKey(
DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
- final DocumentsProviderInfo info = DocumentsProviderInfo.parseInfo(
- mContext, providerInfo);
- if (info == null) {
- Log.w(TAG, "Missing info for " + providerInfo);
- continue;
- }
-
- mProviders.put(info.providerInfo.authority, info);
+ // TODO: remove deprecated customRoots flag
+ // TODO: populate roots on background thread, and cache results
+ final ContentProviderClient client = resolver
+ .acquireUnstableContentProviderClient(info.authority);
try {
- // TODO: remove deprecated customRoots flag
- // TODO: populate roots on background thread, and cache results
- final Uri uri = DocumentsContract.buildRootsUri(providerInfo.authority);
- final Cursor cursor = mContext.getContentResolver()
- .query(uri, null, null, null, null);
- try {
- while (cursor.moveToNext()) {
- final Root root = Root.fromCursor(mContext, info, cursor);
- mRoots.put(Pair.create(info.providerInfo.authority, root.rootId), root);
- mRootsList.add(root);
- }
- } finally {
- cursor.close();
+ final List<DocumentRoot> roots = DocumentsContract.getDocumentRoots(client);
+ for (DocumentRoot root : roots) {
+ root.authority = info.authority;
}
+ mRoots.addAll(roots);
} catch (Exception e) {
- Log.w(TAG, "Failed to load some roots from " + info.providerInfo.authority
- + ": " + e);
+ Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e);
+ } finally {
+ ContentProviderClient.closeQuietly(client);
}
}
}
}
- @GuardedBy("ActivityThread")
- public DocumentsProviderInfo findProvider(String authority) {
- return mProviders.get(authority);
- }
-
- @GuardedBy("ActivityThread")
- public Root findRoot(String authority, String rootId) {
- return mRoots.get(Pair.create(authority, rootId));
+ public DocumentRoot findRoot(Uri uri) {
+ final String authority = uri.getAuthority();
+ final String docId = DocumentsContract.getDocId(uri);
+ for (DocumentRoot root : mRoots) {
+ if (Objects.equal(root.authority, authority) && Objects.equal(root.docId, docId)) {
+ return root;
+ }
+ }
+ return null;
}
@GuardedBy("ActivityThread")
- public Root findRoot(Document doc) {
- final String authority = doc.uri.getAuthority();
- final String rootId = DocumentsContract.getRootId(doc.uri);
- return findRoot(authority, rootId);
+ public DocumentRoot getRecentsRoot() {
+ return mRecentsRoot;
}
@GuardedBy("ActivityThread")
- public Root getRecentsRoot() {
- return mRecentsRoot;
+ public boolean isRecentsRoot(DocumentRoot root) {
+ return mRecentsRoot == root;
}
@GuardedBy("ActivityThread")
- public Collection<Root> getRoots() {
- return mRootsList;
+ public List<DocumentRoot> getRoots() {
+ return mRoots;
}
@GuardedBy("ActivityThread")
- public Drawable resolveDocumentIcon(Context context, String authority, String mimeType) {
- // Custom icons take precedence
- final DocumentsProviderInfo info = mProviders.get(authority);
- if (info != null) {
- for (Icon icon : info.customIcons) {
- if (MimePredicate.mimeMatches(icon.mimeType, mimeType)) {
- return icon.icon;
- }
- }
- }
-
+ public static Drawable resolveDocumentIcon(Context context, String mimeType) {
if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
return context.getResources().getDrawable(R.drawable.ic_dir);
} else {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 8a48e2a..2cfa841 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -26,7 +26,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
-import android.provider.DocumentsContract.Roots;
+import android.provider.DocumentsContract.DocumentRoot;
import android.text.format.Formatter;
import android.util.Log;
import android.view.LayoutInflater;
@@ -40,10 +40,9 @@ import android.widget.ListView;
import android.widget.TextView;
import com.android.documentsui.SectionedListAdapter.SectionAdapter;
-import com.android.documentsui.model.Root;
-import com.android.documentsui.model.Root.RootComparator;
+import com.android.documentsui.model.Document;
-import java.util.Collection;
+import java.util.Comparator;
import java.util.List;
/**
@@ -102,8 +101,8 @@ public class RootsFragment extends Fragment {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
final Object item = mAdapter.getItem(position);
- if (item instanceof Root) {
- activity.onRootPicked((Root) item, true);
+ if (item instanceof DocumentRoot) {
+ activity.onRootPicked((DocumentRoot) item, true);
} else if (item instanceof ResolveInfo) {
activity.onAppPicked((ResolveInfo) item);
} else {
@@ -112,7 +111,7 @@ public class RootsFragment extends Fragment {
}
};
- private static class RootsAdapter extends ArrayAdapter<Root> implements SectionAdapter {
+ private static class RootsAdapter extends ArrayAdapter<DocumentRoot> implements SectionAdapter {
private int mHeaderId;
public RootsAdapter(Context context, int headerId) {
@@ -132,14 +131,14 @@ public class RootsFragment extends Fragment {
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
- final Root root = getItem(position);
- icon.setImageDrawable(root.icon);
+ final DocumentRoot root = getItem(position);
+ icon.setImageDrawable(root.loadIcon(context));
title.setText(root.title);
// Device summary is always available space
final String summaryText;
- if ((root.rootType == Roots.ROOT_TYPE_DEVICE
- || root.rootType == Roots.ROOT_TYPE_DEVICE_ADVANCED)
+ if ((root.rootType == DocumentRoot.ROOT_TYPE_DEVICE
+ || root.rootType == DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED)
&& root.availableBytes >= 0) {
summaryText = context.getString(R.string.root_available_bytes,
Formatter.formatFileSize(context, root.availableBytes));
@@ -216,27 +215,27 @@ public class RootsFragment extends Fragment {
private final RootsAdapter mDevicesAdvanced;
private final AppsAdapter mApps;
- public SectionedRootsAdapter(Context context, Collection<Root> roots, Intent includeApps) {
+ public SectionedRootsAdapter(Context context, List<DocumentRoot> roots, Intent includeApps) {
mServices = new RootsAdapter(context, R.string.root_type_service);
mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut);
mDevices = new RootsAdapter(context, R.string.root_type_device);
mDevicesAdvanced = new RootsAdapter(context, R.string.root_type_device);
mApps = new AppsAdapter(context);
- for (Root root : roots) {
+ for (DocumentRoot root : roots) {
Log.d(TAG, "Found rootType=" + root.rootType);
switch (root.rootType) {
- case Roots.ROOT_TYPE_SERVICE:
+ case DocumentRoot.ROOT_TYPE_SERVICE:
mServices.add(root);
break;
- case Roots.ROOT_TYPE_SHORTCUT:
+ case DocumentRoot.ROOT_TYPE_SHORTCUT:
mShortcuts.add(root);
break;
- case Roots.ROOT_TYPE_DEVICE:
+ case DocumentRoot.ROOT_TYPE_DEVICE:
mDevices.add(root);
mDevicesAdvanced.add(root);
break;
- case Roots.ROOT_TYPE_DEVICE_ADVANCED:
+ case DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED:
mDevicesAdvanced.add(root);
break;
}
@@ -281,4 +280,16 @@ public class RootsFragment extends Fragment {
}
}
}
+
+ public static class RootComparator implements Comparator<DocumentRoot> {
+ @Override
+ public int compare(DocumentRoot lhs, DocumentRoot rhs) {
+ final int score = Document.compareToIgnoreCaseNullable(lhs.title, rhs.title);
+ if (score != 0) {
+ return score;
+ } else {
+ return Document.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
+ }
+ }
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
index 8eb81b8..7e1a297 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
@@ -73,8 +73,8 @@ public class SaveFragment extends Fragment {
final View view = inflater.inflate(R.layout.fragment_save, container, false);
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
- icon.setImageDrawable(roots.resolveDocumentIcon(
- context, null, getArguments().getString(EXTRA_MIME_TYPE)));
+ icon.setImageDrawable(
+ RootsCache.resolveDocumentIcon(context, getArguments().getString(EXTRA_MIME_TYPE)));
mDisplayName = (EditText) view.findViewById(android.R.id.title);
mDisplayName.addTextChangedListener(mDisplayNameWatcher);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java
index c0f21cb..692d171 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java
@@ -53,17 +53,11 @@ public class Document {
this.size = size;
}
- public static Document fromRoot(ContentResolver resolver, Root root)
- throws FileNotFoundException {
- return fromUri(resolver, root.uri);
- }
-
public static Document fromDirectoryCursor(Uri parent, Cursor cursor) {
final String authority = parent.getAuthority();
- final String rootId = DocumentsContract.getRootId(parent);
final String docId = getCursorString(cursor, DocumentColumns.DOC_ID);
- final Uri uri = DocumentsContract.buildDocumentUri(authority, rootId, docId);
+ final Uri uri = DocumentsContract.buildDocumentUri(authority, docId);
final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
@@ -74,6 +68,7 @@ public class Document {
return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
}
+ @Deprecated
public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor)
throws FileNotFoundException {
final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI));
@@ -176,7 +171,7 @@ public class Document {
return (index != -1) ? cursor.getInt(index) : 0;
}
- public static class NameComparator implements Comparator<Document> {
+ public static class DisplayNameComparator implements Comparator<Document> {
@Override
public int compare(Document lhs, Document rhs) {
final boolean leftDir = lhs.isDirectory();
@@ -185,12 +180,12 @@ public class Document {
if (leftDir != rightDir) {
return leftDir ? -1 : 1;
} else {
- return Root.compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName);
+ return compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName);
}
}
}
- public static class DateComparator implements Comparator<Document> {
+ public static class LastModifiedComparator implements Comparator<Document> {
@Override
public int compare(Document lhs, Document rhs) {
return Long.compare(rhs.lastModified, lhs.lastModified);
@@ -213,4 +208,10 @@ public class Document {
fnfe.initCause(t);
throw fnfe;
}
+
+ public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
+ if (lhs == null) return -1;
+ if (rhs == null) return 1;
+ return lhs.compareToIgnoreCase(rhs);
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
index d6c852e..81f75d2 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
@@ -21,8 +21,11 @@ import static com.android.documentsui.model.Document.asFileNotFoundException;
import android.content.ContentResolver;
import android.net.Uri;
+import android.provider.DocumentsContract.DocumentRoot;
import android.util.Log;
+import com.android.documentsui.RootsCache;
+
import org.json.JSONArray;
import org.json.JSONException;
@@ -62,4 +65,18 @@ public class DocumentStack extends LinkedList<Document> {
// TODO: handle roots that have gone missing
return stack;
}
+
+ public DocumentRoot getRoot(RootsCache roots) {
+ return roots.findRoot(getLast().uri);
+ }
+
+ public String getTitle(RootsCache roots) {
+ if (size() == 1) {
+ return getRoot(roots).title;
+ } else if (size() > 1) {
+ return peek().displayName;
+ } else {
+ return null;
+ }
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java
deleted file mode 100644
index 96eb58e..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui.model;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ProviderInfo;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.drawable.Drawable;
-import android.provider.DocumentsContract;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-
-import com.android.documentsui.DocumentsActivity;
-import com.google.android.collect.Lists;
-
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Representation of a storage backend.
- */
-public class DocumentsProviderInfo {
- private static final String TAG = DocumentsActivity.TAG;
-
- public ProviderInfo providerInfo;
- public boolean customRoots;
- public List<Icon> customIcons;
-
- public static class Icon {
- public String mimeType;
- public Drawable icon;
- }
-
- private static final String TAG_DOCUMENTS_PROVIDER = "documents-provider";
- private static final String TAG_ICON = "icon";
-
- public static DocumentsProviderInfo buildRecents(Context context, ProviderInfo providerInfo) {
- final DocumentsProviderInfo info = new DocumentsProviderInfo();
- info.providerInfo = providerInfo;
- info.customRoots = false;
- return info;
- }
-
- public static DocumentsProviderInfo parseInfo(Context context, ProviderInfo providerInfo) {
- final DocumentsProviderInfo info = new DocumentsProviderInfo();
- info.providerInfo = providerInfo;
- info.customIcons = Lists.newArrayList();
-
- final PackageManager pm = context.getPackageManager();
- final Resources res;
- try {
- res = pm.getResourcesForApplication(providerInfo.applicationInfo);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Failed to find resources for " + providerInfo, e);
- return null;
- }
-
- XmlResourceParser parser = null;
- try {
- parser = providerInfo.loadXmlMetaData(
- pm, DocumentsContract.META_DATA_DOCUMENT_PROVIDER);
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- int type = 0;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
- final String tag = parser.getName();
- if (type == XmlPullParser.START_TAG && TAG_DOCUMENTS_PROVIDER.equals(tag)) {
- final TypedArray a = res.obtainAttributes(
- attrs, com.android.internal.R.styleable.DocumentsProviderInfo);
- info.customRoots = a.getBoolean(
- com.android.internal.R.styleable.DocumentsProviderInfo_customRoots,
- false);
- a.recycle();
-
- } else if (type == XmlPullParser.START_TAG && TAG_ICON.equals(tag)) {
- final TypedArray a = res.obtainAttributes(
- attrs, com.android.internal.R.styleable.Icon);
- final Icon icon = new Icon();
- icon.mimeType = a.getString(com.android.internal.R.styleable.Icon_mimeType);
- icon.icon = a.getDrawable(com.android.internal.R.styleable.Icon_icon);
- info.customIcons.add(icon);
- a.recycle();
- }
- }
- } catch (IOException e) {
- Log.w(TAG, "Failed to parse metadata", e);
- return null;
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Failed to parse metadata", e);
- return null;
- } finally {
- IoUtils.closeQuietly(parser);
- }
-
- return info;
- }
-}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Root.java b/packages/DocumentsUI/src/com/android/documentsui/model/Root.java
deleted file mode 100644
index 23d16df..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/model/Root.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.documentsui.model;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Documents;
-import android.provider.DocumentsContract.RootColumns;
-import android.provider.DocumentsContract.Roots;
-
-import com.android.documentsui.R;
-
-import java.util.Comparator;
-
-/**
- * Representation of a root under a storage backend.
- */
-public class Root {
- public String rootId;
- public int rootType;
- public Uri uri;
- public Drawable icon;
- public String title;
- public String summary;
- public long availableBytes = -1;
- public boolean isRecents;
-
- public static Root buildRecents(Context context) {
- final PackageManager pm = context.getPackageManager();
- final Root root = new Root();
- root.rootId = null;
- root.rootType = Roots.ROOT_TYPE_SHORTCUT;
- root.uri = null;
- root.icon = context.getResources().getDrawable(R.drawable.ic_dir);
- root.title = context.getString(R.string.root_recent);
- root.summary = null;
- root.availableBytes = -1;
- root.isRecents = true;
- return root;
- }
-
- public static Root fromCursor(
- Context context, DocumentsProviderInfo info, Cursor cursor) {
- final PackageManager pm = context.getPackageManager();
-
- final Root root = new Root();
- root.rootId = cursor.getString(cursor.getColumnIndex(RootColumns.ROOT_ID));
- root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
- root.uri = DocumentsContract.buildDocumentUri(
- info.providerInfo.authority, root.rootId, Documents.DOC_ID_ROOT);
- root.icon = info.providerInfo.loadIcon(pm);
- root.title = info.providerInfo.loadLabel(pm).toString();
- root.availableBytes = cursor.getLong(cursor.getColumnIndex(RootColumns.AVAILABLE_BYTES));
- root.summary = null;
-
- final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON));
- if (icon != 0) {
- try {
- root.icon = pm.getResourcesForApplication(info.providerInfo.applicationInfo)
- .getDrawable(icon);
- } catch (NotFoundException e) {
- throw new RuntimeException(e);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
-
- final String title = cursor.getString(cursor.getColumnIndex(RootColumns.TITLE));
- if (title != null) {
- root.title = title;
- }
-
- root.summary = cursor.getString(cursor.getColumnIndex(RootColumns.SUMMARY));
- root.isRecents = false;
-
- return root;
- }
-
- /**
- * Return string most suited to showing in a directory listing.
- */
- public String getDirectoryString() {
- return (summary != null) ? summary : title;
- }
-
- public static class RootComparator implements Comparator<Root> {
- @Override
- public int compare(Root lhs, Root rhs) {
- final int score = compareToIgnoreCaseNullable(lhs.title, rhs.title);
- if (score != 0) {
- return score;
- } else {
- return compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
- }
- }
- }
-
- public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
- if (lhs == null) return -1;
- if (rhs == null) return 1;
- return lhs.compareToIgnoreCase(rhs);
- }
-}
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml
index 8bd2a6d..5272166 100644
--- a/packages/ExternalStorageProvider/AndroidManifest.xml
+++ b/packages/ExternalStorageProvider/AndroidManifest.xml
@@ -15,18 +15,5 @@
android:name="android.content.DOCUMENT_PROVIDER"
android:resource="@xml/document_provider" />
</provider>
-
- <!-- TODO: remove when we have real providers -->
- <provider
- android:name=".CloudTestDocumentsProvider"
- android:authorities="com.android.externalstorage.cloudtest"
- android:grantUriPermissions="true"
- android:exported="true"
- android:enabled="false"
- android:permission="android.permission.MANAGE_DOCUMENTS">
- <meta-data
- android:name="android.content.DOCUMENT_PROVIDER"
- android:resource="@xml/document_provider" />
- </provider>
</application>
</manifest>
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/CloudTestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/CloudTestDocumentsProvider.java
deleted file mode 100644
index 119d92e..0000000
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/CloudTestDocumentsProvider.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.externalstorage;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MatrixCursor.RowBuilder;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.DocumentColumns;
-import android.provider.DocumentsContract.Documents;
-import android.provider.DocumentsContract.RootColumns;
-import android.provider.DocumentsContract.Roots;
-import android.util.Log;
-
-import com.google.android.collect.Lists;
-
-import libcore.io.IoUtils;
-
-import java.io.FileNotFoundException;
-import java.util.List;
-
-public class CloudTestDocumentsProvider extends ContentProvider {
- private static final String TAG = "CloudTest";
-
- private static final String AUTHORITY = "com.android.externalstorage.cloudtest";
-
- private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-
- private static final int URI_ROOTS = 1;
- private static final int URI_ROOTS_ID = 2;
- private static final int URI_DOCS_ID = 3;
- private static final int URI_DOCS_ID_CONTENTS = 4;
- private static final int URI_DOCS_ID_SEARCH = 5;
-
- static {
- sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS);
- sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID);
- sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID);
- sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS);
- sMatcher.addURI(AUTHORITY, "roots/*/docs/*/search", URI_DOCS_ID_SEARCH);
- }
-
- private static final String[] ALL_ROOTS_COLUMNS = new String[] {
- RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, RootColumns.TITLE,
- RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES
- };
-
- private static final String[] ALL_DOCUMENTS_COLUMNS = new String[] {
- DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE,
- DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS
- };
-
- private List<String> mKnownDocs = Lists.newArrayList("meow.png", "kittens.pdf");
-
- private int mPage;
-
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- switch (sMatcher.match(uri)) {
- case URI_ROOTS: {
- final MatrixCursor result = new MatrixCursor(
- projection != null ? projection : ALL_ROOTS_COLUMNS);
- includeDefaultRoot(result);
- return result;
- }
- case URI_ROOTS_ID: {
- final MatrixCursor result = new MatrixCursor(
- projection != null ? projection : ALL_ROOTS_COLUMNS);
- includeDefaultRoot(result);
- return result;
- }
- case URI_DOCS_ID: {
- final String docId = DocumentsContract.getDocId(uri);
- final MatrixCursor result = new MatrixCursor(
- projection != null ? projection : ALL_DOCUMENTS_COLUMNS);
- includeDoc(result, docId);
- return result;
- }
- case URI_DOCS_ID_CONTENTS: {
- final CloudCursor result = new CloudCursor(
- projection != null ? projection : ALL_DOCUMENTS_COLUMNS, uri);
- for (String docId : mKnownDocs) {
- includeDoc(result, docId);
- }
- if (mPage < 3) {
- result.setHasMore();
- }
- result.setNotificationUri(getContext().getContentResolver(), uri);
- return result;
- }
- default: {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
- }
- }
-
- private void includeDefaultRoot(MatrixCursor result) {
- final RowBuilder row = result.newRow();
- row.offer(RootColumns.ROOT_ID, "testroot");
- row.offer(RootColumns.ROOT_TYPE, Roots.ROOT_TYPE_SERVICE);
- row.offer(RootColumns.TITLE, "_TestTitle");
- row.offer(RootColumns.SUMMARY, "_TestSummary");
- }
-
- private void includeDoc(MatrixCursor result, String docId) {
- int flags = 0;
-
- final String mimeType;
- if (Documents.DOC_ID_ROOT.equals(docId)) {
- mimeType = Documents.MIME_TYPE_DIR;
- } else {
- mimeType = "application/octet-stream";
- }
-
- final RowBuilder row = result.newRow();
- row.offer(DocumentColumns.DOC_ID, docId);
- row.offer(DocumentColumns.DISPLAY_NAME, docId);
- row.offer(DocumentColumns.MIME_TYPE, mimeType);
- row.offer(DocumentColumns.LAST_MODIFIED, System.currentTimeMillis());
- row.offer(DocumentColumns.FLAGS, flags);
- }
-
- private class CloudCursor extends MatrixCursor {
- private final Uri mUri;
- private Bundle mExtras = new Bundle();
-
- public CloudCursor(String[] columnNames, Uri uri) {
- super(columnNames);
- mUri = uri;
- }
-
- public void setHasMore() {
- mExtras.putBoolean(DocumentsContract.EXTRA_HAS_MORE, true);
- }
-
- @Override
- public Bundle getExtras() {
- Log.d(TAG, "getExtras() " + mExtras);
- return mExtras;
- }
-
- @Override
- public Bundle respond(Bundle extras) {
- extras.size();
- Log.d(TAG, "respond() " + extras);
- if (extras.getBoolean(DocumentsContract.EXTRA_REQUEST_MORE, false)) {
- new CloudTask().execute(mUri);
- }
- return Bundle.EMPTY;
- }
- }
-
- private class CloudTask extends AsyncTask<Uri, Void, Void> {
- @Override
- protected Void doInBackground(Uri... uris) {
- final Uri uri = uris[0];
-
- SystemClock.sleep(1000);
-
- // Grab some files from the cloud
- for (int i = 0; i < 5; i++) {
- mKnownDocs.add("cloud-page" + mPage + "-file" + i);
- }
- mPage++;
-
- Log.d(TAG, "Loaded more; notifying " + uri);
- getContext().getContentResolver().notifyChange(uri, null, false);
- return null;
- }
- }
-
- private interface TypeQuery {
- final String[] PROJECTION = {
- DocumentColumns.MIME_TYPE };
-
- final int MIME_TYPE = 0;
- }
-
- @Override
- public String getType(Uri uri) {
- switch (sMatcher.match(uri)) {
- case URI_ROOTS: {
- return Roots.MIME_TYPE_DIR;
- }
- case URI_ROOTS_ID: {
- return Roots.MIME_TYPE_ITEM;
- }
- case URI_DOCS_ID: {
- final Cursor cursor = query(uri, TypeQuery.PROJECTION, null, null, null);
- try {
- if (cursor.moveToFirst()) {
- return cursor.getString(TypeQuery.MIME_TYPE);
- } else {
- return null;
- }
- } finally {
- IoUtils.closeQuietly(cursor);
- }
- }
- default: {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
- }
- }
-
- @Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
-}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8843e19..583ecc9 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,205 +16,130 @@
package com.android.externalstorage;
-import android.content.ContentProvider;
import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.UriMatcher;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
+import android.graphics.Point;
import android.media.ExifInterface;
-import android.net.Uri;
-import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.provider.DocumentsContract;
import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract.DocumentRoot;
import android.provider.DocumentsContract.Documents;
-import android.provider.DocumentsContract.RootColumns;
-import android.provider.DocumentsContract.Roots;
-import android.util.Log;
+import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap;
+import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
-public class ExternalStorageProvider extends ContentProvider {
+public class ExternalStorageProvider extends DocumentsProvider {
private static final String TAG = "ExternalStorage";
- private static final String AUTHORITY = "com.android.externalstorage.documents";
+ // docId format: root:path/to/file
- // TODO: support multiple storage devices
-
- private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-
- private static final int URI_ROOTS = 1;
- private static final int URI_ROOTS_ID = 2;
- private static final int URI_DOCS_ID = 3;
- private static final int URI_DOCS_ID_CONTENTS = 4;
- private static final int URI_DOCS_ID_SEARCH = 5;
-
- static {
- sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS);
- sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID);
- sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID);
- sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS);
- sMatcher.addURI(AUTHORITY, "roots/*/docs/*/search", URI_DOCS_ID_SEARCH);
- }
-
- private HashMap<String, Root> mRoots = Maps.newHashMap();
-
- private static class Root {
- public int rootType;
- public String name;
- public int icon = 0;
- public String title = null;
- public String summary = null;
- public File path;
- }
-
- private static final String[] ALL_ROOTS_COLUMNS = new String[] {
- RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, RootColumns.TITLE,
- RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES
- };
-
- private static final String[] ALL_DOCUMENTS_COLUMNS = new String[] {
+ private static final String[] SUPPORTED_COLUMNS = new String[] {
DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE,
DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS
};
+ private ArrayList<DocumentRoot> mRoots;
+ private HashMap<String, DocumentRoot> mTagToRoot;
+ private HashMap<String, File> mTagToPath;
+
@Override
public boolean onCreate() {
- mRoots.clear();
-
- final Root root = new Root();
- root.rootType = Roots.ROOT_TYPE_DEVICE_ADVANCED;
- root.name = "primary";
- root.title = getContext().getString(R.string.root_internal_storage);
- root.path = Environment.getExternalStorageDirectory();
- mRoots.put(root.name, root);
+ mRoots = Lists.newArrayList();
+ mTagToRoot = Maps.newHashMap();
+ mTagToPath = Maps.newHashMap();
+
+ // TODO: support multiple storage devices
+
+ try {
+ final String tag = "primary";
+ final File path = Environment.getExternalStorageDirectory();
+ mTagToPath.put(tag, path);
+
+ final DocumentRoot root = new DocumentRoot();
+ root.docId = getDocIdForFile(path);
+ root.rootType = DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED;
+ root.title = getContext().getString(R.string.root_internal_storage);
+ root.icon = R.drawable.ic_pdf;
+ root.flags = DocumentRoot.FLAG_LOCAL_ONLY;
+ mRoots.add(root);
+ mTagToRoot.put(tag, root);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
return true;
}
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- switch (sMatcher.match(uri)) {
- case URI_ROOTS: {
- final MatrixCursor result = new MatrixCursor(
- projection != null ? projection : ALL_ROOTS_COLUMNS);
- for (Root root : mRoots.values()) {
- includeRoot(result, root);
- }
- return result;
- }
- case URI_ROOTS_ID: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
+ private String getDocIdForFile(File file) throws FileNotFoundException {
+ String path = file.getAbsolutePath();
- final MatrixCursor result = new MatrixCursor(
- projection != null ? projection : ALL_ROOTS_COLUMNS);
- includeRoot(result, root);
- return result;
+ // Find the most-specific root path
+ Map.Entry<String, File> mostSpecific = null;
+ for (Map.Entry<String, File> root : mTagToPath.entrySet()) {
+ final String rootPath = root.getValue().getPath();
+ if (path.startsWith(rootPath) && (mostSpecific == null
+ || rootPath.length() > mostSpecific.getValue().getPath().length())) {
+ mostSpecific = root;
}
- case URI_DOCS_ID: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
- final String docId = DocumentsContract.getDocId(uri);
-
- final MatrixCursor result = new MatrixCursor(
- projection != null ? projection : ALL_DOCUMENTS_COLUMNS);
- final File file = docIdToFile(root, docId);
- includeFile(result, root, file);
- return result;
- }
- case URI_DOCS_ID_CONTENTS: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
- final String docId = DocumentsContract.getDocId(uri);
-
- final MatrixCursor result = new MatrixCursor(
- projection != null ? projection : ALL_DOCUMENTS_COLUMNS);
- final File parent = docIdToFile(root, docId);
-
- for (File file : parent.listFiles()) {
- includeFile(result, root, file);
- }
+ }
- return result;
- }
- case URI_DOCS_ID_SEARCH: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
- final String docId = DocumentsContract.getDocId(uri);
- final String query = DocumentsContract.getSearchQuery(uri).toLowerCase();
-
- final MatrixCursor result = new MatrixCursor(
- projection != null ? projection : ALL_DOCUMENTS_COLUMNS);
- final File parent = docIdToFile(root, docId);
-
- final LinkedList<File> pending = new LinkedList<File>();
- pending.add(parent);
- while (!pending.isEmpty() && result.getCount() < 20) {
- final File file = pending.removeFirst();
- if (file.isDirectory()) {
- for (File child : file.listFiles()) {
- pending.add(child);
- }
- } else {
- if (file.getName().toLowerCase().contains(query)) {
- includeFile(result, root, file);
- }
- }
- }
+ if (mostSpecific == null) {
+ throw new FileNotFoundException("Failed to find root that contains " + path);
+ }
- return result;
- }
- default: {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
+ // Start at first char of path under root
+ final String rootPath = mostSpecific.getValue().getPath();
+ if (rootPath.equals(path)) {
+ path = "";
+ } else if (rootPath.endsWith("/")) {
+ path = path.substring(rootPath.length());
+ } else {
+ path = path.substring(rootPath.length() + 1);
}
+
+ return mostSpecific.getKey() + ':' + path;
}
- private String fileToDocId(Root root, File file) {
- String rootPath = root.path.getAbsolutePath();
- final String path = file.getAbsolutePath();
- if (path.equals(rootPath)) {
- return Documents.DOC_ID_ROOT;
- }
+ private File getFileForDocId(String docId) throws FileNotFoundException {
+ final int splitIndex = docId.indexOf(':', 1);
+ final String tag = docId.substring(0, splitIndex);
+ final String path = docId.substring(splitIndex + 1);
- if (!rootPath.endsWith("/")) {
- rootPath += "/";
+ File target = mTagToPath.get(tag);
+ if (target == null) {
+ throw new FileNotFoundException("No root for " + tag);
}
- if (!path.startsWith(rootPath)) {
- throw new IllegalArgumentException("File " + path + " outside root " + root.path);
- } else {
- return path.substring(rootPath.length());
+ target = new File(target, path);
+ if (!target.exists()) {
+ throw new FileNotFoundException("Missing file for " + docId + " at " + target);
}
+ return target;
}
- private File docIdToFile(Root root, String docId) {
- if (Documents.DOC_ID_ROOT.equals(docId)) {
- return root.path;
+ private void includeFile(MatrixCursor result, String docId, File file)
+ throws FileNotFoundException {
+ if (docId == null) {
+ docId = getDocIdForFile(file);
} else {
- return new File(root.path, docId);
+ file = getFileForDocId(docId);
}
- }
- private void includeRoot(MatrixCursor result, Root root) {
- final RowBuilder row = result.newRow();
- row.offer(RootColumns.ROOT_ID, root.name);
- row.offer(RootColumns.ROOT_TYPE, root.rootType);
- row.offer(RootColumns.ICON, root.icon);
- row.offer(RootColumns.TITLE, root.title);
- row.offer(RootColumns.SUMMARY, root.summary);
- row.offer(RootColumns.AVAILABLE_BYTES, root.path.getFreeSpace());
- }
-
- private void includeFile(MatrixCursor result, Root root, File file) {
int flags = 0;
if (file.isDirectory()) {
@@ -229,19 +154,12 @@ public class ExternalStorageProvider extends ContentProvider {
flags |= Documents.FLAG_SUPPORTS_DELETE;
}
+ final String displayName = file.getName();
final String mimeType = getTypeForFile(file);
if (mimeType.startsWith("image/")) {
flags |= Documents.FLAG_SUPPORTS_THUMBNAIL;
}
- final String docId = fileToDocId(root, file);
- final String displayName;
- if (Documents.DOC_ID_ROOT.equals(docId)) {
- displayName = root.title;
- } else {
- displayName = file.getName();
- }
-
final RowBuilder row = result.newRow();
row.offer(DocumentColumns.DOC_ID, docId);
row.offer(DocumentColumns.DISPLAY_NAME, displayName);
@@ -252,169 +170,150 @@ public class ExternalStorageProvider extends ContentProvider {
}
@Override
- public String getType(Uri uri) {
- switch (sMatcher.match(uri)) {
- case URI_ROOTS: {
- return Roots.MIME_TYPE_DIR;
- }
- case URI_ROOTS_ID: {
- return Roots.MIME_TYPE_ITEM;
- }
- case URI_DOCS_ID: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
- final String docId = DocumentsContract.getDocId(uri);
- return getTypeForFile(docIdToFile(root, docId));
- }
- default: {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
+ public List<DocumentRoot> getDocumentRoots() {
+ // Update free space
+ for (String tag : mTagToRoot.keySet()) {
+ final DocumentRoot root = mTagToRoot.get(tag);
+ final File path = mTagToPath.get(tag);
+ root.availableBytes = path.getFreeSpace();
}
+ return mRoots;
}
- private String getTypeForFile(File file) {
- if (file.isDirectory()) {
- return Documents.MIME_TYPE_DIR;
+ @Override
+ public String createDocument(String docId, String mimeType, String displayName)
+ throws FileNotFoundException {
+ final File parent = getFileForDocId(docId);
+ displayName = validateDisplayName(mimeType, displayName);
+
+ final File file = new File(parent, displayName);
+ if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
+ if (!file.mkdir()) {
+ throw new IllegalStateException("Failed to mkdir " + file);
+ }
} else {
- return getTypeForName(file.getName());
+ try {
+ if (!file.createNewFile()) {
+ throw new IllegalStateException("Failed to touch " + file);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to touch " + file + ": " + e);
+ }
}
+ return getDocIdForFile(file);
}
- private String getTypeForName(String name) {
- final int lastDot = name.lastIndexOf('.');
- if (lastDot >= 0) {
- final String extension = name.substring(lastDot + 1);
- final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
- if (mime != null) {
- return mime;
- }
+ @Override
+ public void renameDocument(String docId, String displayName) throws FileNotFoundException {
+ final File file = getFileForDocId(docId);
+ final File newFile = new File(file.getParentFile(), displayName);
+ if (!file.renameTo(newFile)) {
+ throw new IllegalStateException("Failed to rename " + docId);
}
-
- return "application/octet-stream";
+ // TODO: update any outstanding grants
}
@Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- switch (sMatcher.match(uri)) {
- case URI_DOCS_ID: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
- final String docId = DocumentsContract.getDocId(uri);
-
- final File file = docIdToFile(root, docId);
- return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode));
- }
- default: {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
+ public void deleteDocument(String docId) throws FileNotFoundException {
+ final File file = getFileForDocId(docId);
+ if (!file.delete()) {
+ throw new IllegalStateException("Failed to delete " + file);
}
}
@Override
- public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
- throws FileNotFoundException {
- if (opts == null || !opts.containsKey(DocumentsContract.EXTRA_THUMBNAIL_SIZE)) {
- return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
+ public Cursor queryDocument(String docId) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
+ includeFile(result, docId, null);
+ return result;
+ }
+
+ @Override
+ public Cursor queryDocumentChildren(String docId) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
+ final File parent = getFileForDocId(docId);
+ for (File file : parent.listFiles()) {
+ includeFile(result, null, file);
}
+ return result;
+ }
- switch (sMatcher.match(uri)) {
- case URI_DOCS_ID: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
- final String docId = DocumentsContract.getDocId(uri);
-
- final File file = docIdToFile(root, docId);
- final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
- file, ParcelFileDescriptor.MODE_READ_ONLY);
-
- try {
- final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
- final long[] thumb = exif.getThumbnailRange();
- if (thumb != null) {
- return new AssetFileDescriptor(pfd, thumb[0], thumb[1]);
- }
- } catch (IOException e) {
+ @Override
+ public Cursor querySearch(String docId, String query) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
+ final File parent = getFileForDocId(docId);
+
+ final LinkedList<File> pending = new LinkedList<File>();
+ pending.add(parent);
+ while (!pending.isEmpty() && result.getCount() < 20) {
+ final File file = pending.removeFirst();
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) {
+ pending.add(child);
+ }
+ } else {
+ if (file.getName().toLowerCase().contains(query)) {
+ includeFile(result, null, file);
}
-
- return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
- }
- default: {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
}
}
+ return result;
}
@Override
- public Uri insert(Uri uri, ContentValues values) {
- switch (sMatcher.match(uri)) {
- case URI_DOCS_ID: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
- final String docId = DocumentsContract.getDocId(uri);
-
- final File parent = docIdToFile(root, docId);
-
- final String mimeType = values.getAsString(DocumentColumns.MIME_TYPE);
- final String name = validateDisplayName(
- values.getAsString(DocumentColumns.DISPLAY_NAME), mimeType);
-
- final File file = new File(parent, name);
- if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
- if (!file.mkdir()) {
- return null;
- }
-
- } else {
- try {
- if (!file.createNewFile()) {
- return null;
- }
- } catch (IOException e) {
- Log.w(TAG, "Failed to create file", e);
- return null;
- }
- }
+ public String getType(String docId) throws FileNotFoundException {
+ final File file = getFileForDocId(docId);
+ return getTypeForFile(file);
+ }
- final String newDocId = fileToDocId(root, file);
- return DocumentsContract.buildDocumentUri(AUTHORITY, root.name, newDocId);
- }
- default: {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
- }
+ @Override
+ public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
+ throws FileNotFoundException {
+ final File file = getFileForDocId(docId);
+ return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(null, mode));
}
@Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- switch (sMatcher.match(uri)) {
- case URI_DOCS_ID: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
- final String docId = DocumentsContract.getDocId(uri);
-
- final File file = docIdToFile(root, docId);
- final File newFile = new File(
- file.getParentFile(), values.getAsString(DocumentColumns.DISPLAY_NAME));
- return file.renameTo(newFile) ? 1 : 0;
- }
- default: {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ public AssetFileDescriptor openDocumentThumbnail(
+ String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
+ final File file = getFileForDocId(docId);
+ final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+ file, ParcelFileDescriptor.MODE_READ_ONLY);
+
+ try {
+ final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
+ final long[] thumb = exif.getThumbnailRange();
+ if (thumb != null) {
+ return new AssetFileDescriptor(pfd, thumb[0], thumb[1]);
}
+ } catch (IOException e) {
}
+
+ return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
}
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- switch (sMatcher.match(uri)) {
- case URI_DOCS_ID: {
- final Root root = mRoots.get(DocumentsContract.getRootId(uri));
- final String docId = DocumentsContract.getDocId(uri);
-
- final File file = docIdToFile(root, docId);
- return file.delete() ? 1 : 0;
- }
- default: {
- throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ private static String getTypeForFile(File file) {
+ if (file.isDirectory()) {
+ return Documents.MIME_TYPE_DIR;
+ } else {
+ return getTypeForName(file.getName());
+ }
+ }
+
+ private static String getTypeForName(String name) {
+ final int lastDot = name.lastIndexOf('.');
+ if (lastDot >= 0) {
+ final String extension = name.substring(lastDot + 1);
+ final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mime != null) {
+ return mime;
}
}
+
+ return "application/octet-stream";
}
- private String validateDisplayName(String displayName, String mimeType) {
+ private static String validateDisplayName(String mimeType, String displayName) {
if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
return displayName;
} else {