diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-08-05 17:56:48 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2013-08-05 20:14:12 -0700 |
commit | ef7184a1aa0be5d496a5cb495a0f9e11f342af44 (patch) | |
tree | db95537171aedc73dd595ee6d09cf5433cb8697b /packages | |
parent | dc2963aecaf38bf53d6de82957412a486049c207 (diff) | |
download | frameworks_base-ef7184a1aa0be5d496a5cb495a0f9e11f342af44.zip frameworks_base-ef7184a1aa0be5d496a5cb495a0f9e11f342af44.tar.gz frameworks_base-ef7184a1aa0be5d496a5cb495a0f9e11f342af44.tar.bz2 |
More recents work; filtering and sorting.
Update DirectoryFragment to render List<Document>, making it more
general purpose. Feed it documents either from a backend Cursor or
after resolving fields from a recents Cursor. Start in recents when
no persisted stack available. Synthesize a root for recents.
Local directory filtering and sorting using predicates and
comparators, all performed on background thread. Introduce
UriDerivativeLoader which handles ContentObserver updates while
producing a derivative work of a Cursor.
Split data model classes into separate files.
Change-Id: Idb88b4ee22c58c8e508328e678877f7e4c978533
Diffstat (limited to 'packages')
10 files changed, 855 insertions, 337 deletions
diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml index eb6d803..d4a01d3 100644 --- a/packages/DocumentsUI/res/layout/activity.xml +++ b/packages/DocumentsUI/res/layout/activity.xml @@ -39,7 +39,7 @@ <ListView android:id="@+id/roots_list" - android:layout_width="300dp" + android:layout_width="250dp" android:layout_height="match_parent" android:layout_gravity="start" android:background="#fff" /> diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 665f3b1..3eda207 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -39,4 +39,6 @@ <string name="save_error">Failed to save document</string> + <string name="root_recent">Recent</string> + </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 2740e53..f6f3f9c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -21,13 +21,10 @@ import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; -import android.content.CursorLoader; import android.content.Loader; -import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.DocumentsContract; -import android.provider.DocumentsContract.DocumentColumns; import android.text.format.DateUtils; import android.util.SparseBooleanArray; import android.view.ActionMode; @@ -41,17 +38,20 @@ import android.widget.AbsListView; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; -import android.widget.CursorAdapter; +import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import com.android.documentsui.DocumentsActivity.DisplayState; -import com.android.documentsui.DocumentsActivity.Document; +import com.android.documentsui.model.Document; +import com.android.internal.util.Predicate; import com.google.android.collect.Lists; import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; /** * Display the documents inside a single directory. @@ -59,23 +59,21 @@ import java.util.ArrayList; public class DirectoryFragment extends Fragment { // TODO: show storage backend in item views when requested - // TODO: apply sort order locally - // TODO: apply MIME filtering locally private ListView mListView; private GridView mGridView; private AbsListView mCurrentView; - private static final int TYPE_NORMAL = 1; - private static final int TYPE_SEARCH = 2; - private static final int TYPE_RECENT_OPEN = 3; - private static final int TYPE_RECENT_CREATE = 4; + public static final int TYPE_NORMAL = 1; + public static final int TYPE_SEARCH = 2; + public static final int TYPE_RECENT_OPEN = 3; + public static final int TYPE_RECENT_CREATE = 4; private int mType = TYPE_NORMAL; private DocumentsAdapter mAdapter; - private LoaderCallbacks<Cursor> mCallbacks; + private LoaderCallbacks<List<Document>> mCallbacks; private static final String EXTRA_URI = "uri"; @@ -93,6 +91,11 @@ public class DirectoryFragment extends Fragment { ft.commitAllowingStateLoss(); } + public static DirectoryFragment get(FragmentManager fm) { + // TODO: deal with multiple directories shown at once + return (DirectoryFragment) fm.findFragmentById(R.id.directory); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -114,7 +117,7 @@ public class DirectoryFragment extends Fragment { mGridView.setOnItemClickListener(mItemListener); mGridView.setMultiChoiceModeListener(mMultiListener); - mAdapter = new DocumentsAdapter(context); + mAdapter = new DocumentsAdapter(); updateMode(); final Uri uri = getArguments().getParcelable(EXTRA_URI); @@ -129,18 +132,10 @@ public class DirectoryFragment extends Fragment { mType = TYPE_NORMAL; } - mCallbacks = new LoaderCallbacks<Cursor>() { + mCallbacks = new LoaderCallbacks<List<Document>>() { @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { + public Loader<List<Document>> onCreateLoader(int id, Bundle args) { final DisplayState state = getDisplayState(DirectoryFragment.this); - final String sortOrder; - if (state.sortBy == DisplayState.SORT_BY_NAME) { - sortOrder = DocumentColumns.DISPLAY_NAME + " ASC"; - } else if (state.sortBy == DisplayState.SORT_BY_DATE) { - sortOrder = DocumentColumns.LAST_MODIFIED + " DESC"; - } else { - sortOrder = null; - } final Uri contentsUri; if (mType == TYPE_NORMAL) { @@ -149,17 +144,29 @@ public class DirectoryFragment extends Fragment { contentsUri = uri; } - return new CursorLoader(context, contentsUri, null, null, null, sortOrder); + final Predicate<Document> filter = new MimePredicate(state.acceptMimes); + + final Comparator<Document> sortOrder; + if (state.sortOrder == DisplayState.SORT_ORDER_DATE || mType == TYPE_RECENT_OPEN + || mType == TYPE_RECENT_CREATE) { + sortOrder = new Document.DateComparator(); + } else if (state.sortOrder == DisplayState.SORT_ORDER_NAME) { + sortOrder = new Document.NameComparator(); + } else { + throw new IllegalArgumentException("Unknown sort order " + state.sortOrder); + } + + return new DirectoryLoader(context, contentsUri, mType, filter, sortOrder); } @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - mAdapter.swapCursor(data); + public void onLoadFinished(Loader<List<Document>> loader, List<Document> data) { + mAdapter.swapDocuments(data); } @Override - public void onLoaderReset(Loader<Cursor> loader) { - mAdapter.swapCursor(null); + public void onLoaderReset(Loader<List<Document>> loader) { + mAdapter.swapDocuments(null); } }; @@ -243,16 +250,16 @@ public class DirectoryFragment extends Fragment { } } - public void updateSortBy() { + public void updateSortOrder() { getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks); + mListView.smoothScrollToPosition(0); + mGridView.smoothScrollToPosition(0); } private OnItemClickListener mItemListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final Cursor cursor = (Cursor) mAdapter.getItem(position); - final Uri uri = getArguments().getParcelable(EXTRA_URI); - final Document doc = Document.fromCursor(uri, cursor); + final Document doc = mAdapter.getItem(position); ((DocumentsActivity) getActivity()).onDocumentPicked(doc); } }; @@ -279,8 +286,8 @@ public class DirectoryFragment extends Fragment { final int size = checked.size(); for (int i = 0; i < size; i++) { if (checked.valueAt(i)) { - final Cursor cursor = (Cursor) mAdapter.getItem(checked.keyAt(i)); - docs.add(Document.fromCursor(uri, cursor)); + final Document doc = mAdapter.getItem(checked.keyAt(i)); + docs.add(doc); } } @@ -300,11 +307,9 @@ public class DirectoryFragment extends Fragment { public void onItemCheckedStateChanged( ActionMode mode, int position, long id, boolean checked) { if (checked) { - final Cursor cursor = (Cursor) mAdapter.getItem(position); - final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - // Directories cannot be checked - if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { + final Document doc = mAdapter.getItem(position); + if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) { mCurrentView.setItemChecked(position, false); } } @@ -318,61 +323,68 @@ public class DirectoryFragment extends Fragment { return ((DocumentsActivity) fragment.getActivity()).getDisplayState(); } - private class DocumentsAdapter extends CursorAdapter { - public DocumentsAdapter(Context context) { - super(context, null, false); + private class DocumentsAdapter extends BaseAdapter { + private List<Document> mDocuments; + + public DocumentsAdapter() { } - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - final LayoutInflater inflater = LayoutInflater.from(context); - final DisplayState state = getDisplayState(DirectoryFragment.this); - if (state.mode == DisplayState.MODE_LIST) { - return inflater.inflate(R.layout.item_doc_list, parent, false); - } else if (state.mode == DisplayState.MODE_GRID) { - return inflater.inflate(R.layout.item_doc_grid, parent, false); - } else { - throw new IllegalStateException(); - } + public void swapDocuments(List<Document> documents) { + mDocuments = documents; + notifyDataSetChanged(); } @Override - public void bindView(View view, Context context, Cursor cursor) { - final TextView title = (TextView) view.findViewById(android.R.id.title); - final TextView summary = (TextView) view.findViewById(android.R.id.summary); - final ImageView icon = (ImageView) view.findViewById(android.R.id.icon); - - final String docId = getCursorString(cursor, DocumentColumns.DOC_ID); - final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); - final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); - final int flags = getCursorInt(cursor, DocumentColumns.FLAGS); - - final Uri uri = getArguments().getParcelable(EXTRA_URI); - if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) { - final Uri childUri = DocumentsContract.buildDocumentUri(uri, docId); - icon.setImageURI(childUri); + public View getView(int position, View convertView, ViewGroup parent) { + final Context context = parent.getContext(); + + if (convertView == null) { + final LayoutInflater inflater = LayoutInflater.from(context); + final DisplayState state = getDisplayState(DirectoryFragment.this); + if (state.mode == DisplayState.MODE_LIST) { + convertView = inflater.inflate(R.layout.item_doc_list, parent, false); + } else if (state.mode == DisplayState.MODE_GRID) { + convertView = inflater.inflate(R.layout.item_doc_grid, parent, false); + } else { + throw new IllegalStateException(); + } + } + + final Document doc = getItem(position); + + final TextView title = (TextView) convertView.findViewById(android.R.id.title); + final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); + final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); + + if (doc.isThumbnailSupported()) { + // TODO: load thumbnails async + icon.setImageURI(doc.uri); } else { icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon( - context, uri.getAuthority(), mimeType)); + context, doc.uri.getAuthority(), doc.mimeType)); } - title.setText(displayName); + title.setText(doc.displayName); if (summary != null) { - summary.setText(DateUtils.getRelativeTimeSpanString(lastModified)); + summary.setText(DateUtils.getRelativeTimeSpanString(doc.lastModified)); } + + return convertView; } - } - public static String getCursorString(Cursor cursor, String columnName) { - return cursor.getString(cursor.getColumnIndex(columnName)); - } + @Override + public int getCount() { + return mDocuments != null ? mDocuments.size() : 0; + } - public static long getCursorLong(Cursor cursor, String columnName) { - return cursor.getLong(cursor.getColumnIndex(columnName)); - } + @Override + public Document getItem(int position) { + return mDocuments.get(position); + } - public static int getCursorInt(Cursor cursor, String columnName) { - return cursor.getInt(cursor.getColumnIndex(columnName)); + @Override + public long getItemId(int position) { + return getItem(position).uri.hashCode(); + } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java new file mode 100644 index 0000000..a50c312 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -0,0 +1,90 @@ +/* + * 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; + +import static com.android.documentsui.DirectoryFragment.TYPE_NORMAL; +import static com.android.documentsui.DirectoryFragment.TYPE_RECENT_OPEN; +import static com.android.documentsui.DirectoryFragment.TYPE_SEARCH; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.CancellationSignal; + +import com.android.documentsui.model.Document; +import com.android.internal.util.Predicate; +import com.google.android.collect.Lists; + +import libcore.io.IoUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class DirectoryLoader extends UriDerivativeLoader<List<Document>> { + + private final int mType; + private Predicate<Document> mFilter; + private Comparator<Document> mSortOrder; + + public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter, + Comparator<Document> sortOrder) { + super(context, uri); + mType = type; + mFilter = filter; + mSortOrder = sortOrder; + } + + @Override + public List<Document> loadInBackground(Uri uri, CancellationSignal signal) { + final ArrayList<Document> result = Lists.newArrayList(); + + // TODO: send selection and sorting hints to backend + final ContentResolver resolver = getContext().getContentResolver(); + final Cursor cursor = resolver.query(uri, null, null, null, null, signal); + try { + while (cursor != null && cursor.moveToNext()) { + final Document doc; + switch (mType) { + case TYPE_NORMAL: + case TYPE_SEARCH: + doc = Document.fromDirectoryCursor(uri, cursor); + break; + case TYPE_RECENT_OPEN: + doc = Document.fromRecentOpenCursor(resolver, cursor); + break; + default: + throw new IllegalArgumentException("Unknown type"); + } + + if (mFilter == null || mFilter.apply(doc)) { + result.add(doc); + } + } + } finally { + IoUtils.closeQuietly(cursor); + } + + if (mSortOrder != null) { + Collections.sort(result, mSortOrder); + } + + return result; + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 8f2e61d..0cbd1cb 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -16,10 +16,6 @@ package com.android.documentsui; -import static com.android.documentsui.DirectoryFragment.getCursorInt; -import static com.android.documentsui.DirectoryFragment.getCursorLong; -import static com.android.documentsui.DirectoryFragment.getCursorString; - import android.app.ActionBar; import android.app.ActionBar.OnNavigationListener; import android.app.Activity; @@ -28,6 +24,7 @@ import android.app.Dialog; import android.app.DialogFragment; import android.app.FragmentManager; import android.content.ClipData; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -38,10 +35,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; import android.database.Cursor; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -49,14 +42,12 @@ import android.net.Uri; import android.os.Bundle; import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; -import android.provider.DocumentsContract.RootColumns; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout.DrawerListener; -import android.util.AttributeSet; import android.util.Log; -import android.util.Xml; +import android.util.Pair; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -74,17 +65,16 @@ import android.widget.SearchView.OnQueryTextListener; import android.widget.TextView; import android.widget.Toast; +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.google.android.collect.Lists; import com.google.android.collect.Maps; -import libcore.io.IoUtils; - import org.json.JSONArray; import org.json.JSONException; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -92,7 +82,7 @@ import java.util.LinkedList; import java.util.List; public class DocumentsActivity extends Activity { - private static final String TAG = "Documents"; + public static final String TAG = "Documents"; // TODO: share backend root cache with recents provider @@ -102,19 +92,24 @@ public class DocumentsActivity extends Activity { private static final int ACTION_CREATE = 2; private int mAction; - private String[] mAcceptMimes; private SearchView mSearchView; private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; + private Root mCurrentRoot; + + /** Map from authority to cached info */ private static HashMap<String, DocumentsProviderInfo> sProviders = Maps.newHashMap(); - private static HashMap<String, Root> sRoots = Maps.newHashMap(); + /** Map from (authority+rootId) to cached info */ + private static HashMap<Pair<String, String>, Root> sRoots = Maps.newHashMap(); // TODO: remove once adapter split by type private static ArrayList<Root> sRootsList = Lists.newArrayList(); + private static Root sRecentOpenRoot; + private RootsAdapter mRootsAdapter; private ListView mRootsList; @@ -130,19 +125,20 @@ public class DocumentsActivity extends Activity { final String action = intent.getAction(); if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) { mAction = ACTION_OPEN; - mDisplayState.allowMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); + mDisplayState.allowMultiple = intent.getBooleanExtra( + Intent.EXTRA_ALLOW_MULTIPLE, false); } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) { mAction = ACTION_CREATE; mDisplayState.allowMultiple = false; } if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) { - mAcceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); + mDisplayState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES); } else { - mAcceptMimes = new String[] { intent.getType() }; + mDisplayState.acceptMimes = new String[] { intent.getType() }; } - if (mimeMatches("image/*", mAcceptMimes)) { + if (MimePredicate.mimeMatches("image/*", mDisplayState.acceptMimes)) { mDisplayState.mode = DisplayState.MODE_GRID; } else { mDisplayState.mode = DisplayState.MODE_LIST; @@ -169,6 +165,8 @@ public class DocumentsActivity extends Activity { mDrawerLayout.setDrawerListener(mDrawerListener); mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + mDrawerLayout.openDrawer(mRootsList); + updateRoots(); // Restore last stack for calling package @@ -186,6 +184,11 @@ public class DocumentsActivity extends Activity { cursor.close(); } + // Start in recents if no restored stack + if (mStack.isEmpty()) { + onRootPicked(sRecentOpenRoot); + } + updateDirectoryFragment(); } @@ -236,11 +239,18 @@ public class DocumentsActivity extends Activity { } } else { - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); final Root root = getCurrentRoot(); actionBar.setIcon(root != null ? root.icon : null); - actionBar.setTitle(null); - actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener); + + if (getCurrentRoot().isRecents) { + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + actionBar.setTitle(root.title); + } else { + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + actionBar.setTitle(null); + actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener); + actionBar.setSelectedNavigationItem(mDisplayState.sortOrder); + } if (mStack.size() > 1) { mDrawerToggle.setDrawerIndicatorEnabled(false); @@ -395,18 +405,14 @@ public class DocumentsActivity extends Activity { private OnNavigationListener mSortListener = new OnNavigationListener() { @Override public boolean onNavigationItemSelected(int itemPosition, long itemId) { - // TODO: request updated sort order + mDisplayState.sortOrder = itemPosition; + DirectoryFragment.get(getFragmentManager()).updateSortOrder(); return true; } }; public Root getCurrentRoot() { - final Document cwd = getCurrentDirectory(); - if (cwd != null) { - return sRoots.get(DocumentsContract.getRootId(cwd.uri)); - } else { - return null; - } + return mCurrentRoot; } public Document getCurrentDirectory() { @@ -422,15 +428,19 @@ public class DocumentsActivity extends Activity { final Document cwd = getCurrentDirectory(); if (cwd != null) { DirectoryFragment.show(fm, cwd.uri); - mDrawerLayout.closeDrawer(mRootsList); - } else { - mDrawerLayout.openDrawer(mRootsList); } updateActionBar(); invalidateOptionsMenu(); dumpStack(); } + public void onRootPicked(Root root) { + // Clear entire backstack and start in new root + mStack.clear(); + mCurrentRoot = root; + onDocumentPicked(Document.fromRoot(getContentResolver(), root)); + } + public void onDocumentPicked(Document doc) { final FragmentManager fm = getFragmentManager(); if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) { @@ -471,6 +481,8 @@ public class DocumentsActivity extends Activity { } private String saveStack() { + if (mCurrentRoot.isRecents) return null; + final JSONArray stack = new JSONArray(); for (int i = 0; i < mStack.size(); i++) { stack.put(mStack.get(i).uri); @@ -481,6 +493,8 @@ public class DocumentsActivity extends Activity { private void restoreStack(String rawStack) { Log.d(TAG, "restoreStack: " + rawStack); mStack.clear(); + + if (rawStack == null) return; try { final JSONArray stack = new JSONArray(rawStack); for (int i = 0; i < stack.length(); i++) { @@ -491,6 +505,14 @@ public class DocumentsActivity extends Activity { } catch (JSONException e) { Log.w(TAG, "Failed to decode stack", e); } + + // TODO: handle roots that have gone missing + final Document cwd = getCurrentDirectory(); + if (cwd != null) { + final String authority = cwd.uri.getAuthority(); + final String rootId = DocumentsContract.getRootId(cwd.uri); + mCurrentRoot = sRoots.get(Pair.create(authority, rootId)); + } } private void onFinished(Uri... uris) { @@ -525,7 +547,8 @@ public class DocumentsActivity extends Activity { if (uris.length == 1) { intent.setData(uris[0]); } else if (uris.length > 1) { - final ClipData clipData = new ClipData(null, mAcceptMimes, new ClipData.Item(uris[0])); + final ClipData clipData = new ClipData( + null, mDisplayState.acceptMimes, new ClipData.Item(uris[0])); for (int i = 1; i < uris.length; i++) { clipData.addItem(new ClipData.Item(uris[i])); } @@ -542,162 +565,16 @@ public class DocumentsActivity extends Activity { } public static class DisplayState { - public int mode; - public int sortBy; - public boolean allowMultiple; + public int mode = MODE_LIST; + public String[] acceptMimes; + public int sortOrder = SORT_ORDER_NAME; + public boolean allowMultiple = false; public static final int MODE_LIST = 0; public static final int MODE_GRID = 1; - public static final int SORT_BY_NAME = 0; - public static final int SORT_BY_DATE = 1; - } - - public static class Root { - public DocumentsProviderInfo info; - public String rootId; - public int rootType; - public Uri uri; - public Drawable icon; - public String title; - public String summary; - - public static Root fromCursor( - Context context, DocumentsProviderInfo info, Cursor cursor) { - final PackageManager pm = context.getPackageManager(); - - final Root root = new Root(); - root.info = info; - 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, DocumentsContract.ROOT_DOC_ID); - root.icon = info.providerInfo.loadIcon(pm); - root.title = info.providerInfo.loadLabel(pm).toString(); - 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)); - - return root; - } - } - - public static class DocumentsProviderInfo { - public ProviderInfo providerInfo; - public boolean customRoots; - public List<Icon> customIcons; - } - - public static class Icon { - public String mimeType; - public Drawable icon; - } - - public static class Document { - public Uri uri; - public String mimeType; - public String displayName; - public long lastModified; - public int flags; - - public static Document fromCursor(Uri parent, Cursor cursor) { - final String authority = parent.getAuthority(); - final String rootId = DocumentsContract.getRootId(parent); - final String docId = getCursorString(cursor, DocumentColumns.DOC_ID); - - final Document doc = new Document(); - doc.uri = DocumentsContract.buildDocumentUri(authority, rootId, docId); - doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); - doc.lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); - doc.flags = getCursorInt(cursor, DocumentColumns.FLAGS); - return doc; - } - - public static Document fromUri(ContentResolver resolver, Uri uri) { - final Document doc = new Document(); - doc.uri = uri; - - final Cursor cursor = resolver.query(uri, null, null, null, null); - try { - if (!cursor.moveToFirst()) { - throw new IllegalArgumentException("Missing details for " + uri); - } - doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); - doc.lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); - doc.flags = getCursorInt(cursor, DocumentColumns.FLAGS); - } finally { - cursor.close(); - } - - return doc; - } - - public static Document fromSearch(Uri relatedUri, String query) { - final Document doc = new Document(); - doc.uri = DocumentsContract.buildSearchUri(relatedUri, query); - doc.mimeType = DocumentsContract.MIME_TYPE_DIRECTORY; - doc.displayName = query; - doc.lastModified = System.currentTimeMillis(); - doc.flags = 0; - return doc; - } - - @Override - public String toString() { - return "'" + displayName + "' " + uri; - } - - public boolean isCreateSupported() { - return (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0; - } - - public boolean isSearchSupported() { - return (flags & DocumentsContract.FLAG_SUPPORTS_SEARCH) != 0; - } - - public boolean isThumbnailSupported() { - return (flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0; - } - } - - public static boolean mimeMatches(String filter, String[] tests) { - for (String test : tests) { - if (mimeMatches(filter, test)) { - return true; - } - } - return false; - } - - public static boolean mimeMatches(String filter, String test) { - if (filter.equals(test)) { - return true; - } else if ("*/*".equals(filter)) { - return true; - } else if (filter.endsWith("/*")) { - return filter.regionMatches(0, test, 0, filter.indexOf('/')); - } else { - return false; - } + public static final int SORT_ORDER_NAME = 0; + public static final int SORT_ORDER_DATE = 1; } public static Drawable resolveDocumentIcon(Context context, String authority, String mimeType) { @@ -705,7 +582,7 @@ public class DocumentsActivity extends Activity { final DocumentsProviderInfo info = sProviders.get(authority); if (info != null) { for (Icon icon : info.customIcons) { - if (mimeMatches(icon.mimeType, mimeType)) { + if (MimePredicate.mimeMatches(icon.mimeType, mimeType)) { return icon.icon; } } @@ -728,9 +605,6 @@ public class DocumentsActivity extends Activity { } } - private static final String TAG_DOCUMENTS_PROVIDER = "documents-provider"; - private static final String TAG_ICON = "icon"; - /** * Gather roots from all known storage providers. */ @@ -739,13 +613,24 @@ public class DocumentsActivity extends Activity { sRoots.clear(); sRootsList.clear(); + final Context context = this; final PackageManager pm = getPackageManager(); + + // Create special roots, like recents + { + final Root root = Root.buildRecentOpen(context); + sRootsList.add(root); + sRecentOpenRoot = root; + } + + // Query for other storage backends final List<ProviderInfo> providers = pm.queryContentProviders( null, -1, PackageManager.GET_META_DATA); for (ProviderInfo providerInfo : providers) { if (providerInfo.metaData != null && providerInfo.metaData.containsKey( DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) { - final DocumentsProviderInfo info = parseInfo(this, providerInfo); + final DocumentsProviderInfo info = DocumentsProviderInfo.parseInfo( + this, providerInfo); if (info == null) { Log.w(TAG, "Missing info for " + providerInfo); continue; @@ -760,7 +645,7 @@ public class DocumentsActivity extends Activity { try { while (cursor.moveToNext()) { final Root root = Root.fromCursor(this, info, cursor); - sRoots.put(root.rootId, root); + sRoots.put(Pair.create(info.providerInfo.authority, root.rootId), root); sRootsList.add(root); } } finally { @@ -770,72 +655,11 @@ public class DocumentsActivity extends Activity { } } - private 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; - } - private OnItemClickListener mRootsListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - // Clear entire backstack and start in new root - mStack.clear(); - final Root root = mRootsAdapter.getItem(position); - - final ContentResolver resolver = getContentResolver(); - final Document doc = Document.fromUri(resolver, root.uri); - onDocumentPicked(doc); - + onRootPicked(root); mDrawerLayout.closeDrawers(); } }; diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java new file mode 100644 index 0000000..0d2f381 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java @@ -0,0 +1,64 @@ +/* + * 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; + +import android.provider.DocumentsContract; + +import com.android.documentsui.model.Document; +import com.android.internal.util.Predicate; + +public class MimePredicate implements Predicate<Document> { + private final String[] mFilters; + + public MimePredicate(String[] filters) { + mFilters = filters; + } + + @Override + public boolean apply(Document doc) { + if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) { + return true; + } + for (String filter : mFilters) { + if (mimeMatches(filter, doc.mimeType)) { + return true; + } + } + return false; + } + + public static boolean mimeMatches(String filter, String[] tests) { + for (String test : tests) { + if (mimeMatches(filter, test)) { + return true; + } + } + return false; + } + + public static boolean mimeMatches(String filter, String test) { + if (filter.equals(test)) { + return true; + } else if ("*/*".equals(filter)) { + return true; + } else if (filter.endsWith("/*")) { + return filter.regionMatches(0, test, 0, filter.indexOf('/')); + } else { + return false; + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/UriDerivativeLoader.java b/packages/DocumentsUI/src/com/android/documentsui/UriDerivativeLoader.java new file mode 100644 index 0000000..1b88af4 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/UriDerivativeLoader.java @@ -0,0 +1,144 @@ +/* + * 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; + +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; + +/** + * Loader that derives its data from a Uri. Watches for {@link ContentObserver} + * changes while started, manages {@link CancellationSignal}, and caches + * returned results. + */ +public abstract class UriDerivativeLoader<T> extends AsyncTaskLoader<T> { + private final ForceLoadContentObserver mObserver; + private boolean mObserving; + + private final Uri mUri; + + private T mResult; + private CancellationSignal mCancellationSignal; + + @Override + public final T loadInBackground() { + synchronized (this) { + if (isLoadInBackgroundCanceled()) { + throw new OperationCanceledException(); + } + mCancellationSignal = new CancellationSignal(); + } + try { + return loadInBackground(mUri, mCancellationSignal); + } finally { + synchronized (this) { + mCancellationSignal = null; + } + } + } + + public abstract T loadInBackground(Uri uri, CancellationSignal signal); + + @Override + public void cancelLoadInBackground() { + super.cancelLoadInBackground(); + + synchronized (this) { + if (mCancellationSignal != null) { + mCancellationSignal.cancel(); + } + } + } + + @Override + public void deliverResult(T result) { + if (isReset()) { + closeQuietly(result); + return; + } + T oldResult = mResult; + mResult = result; + + if (isStarted()) { + super.deliverResult(result); + } + + if (oldResult != null && oldResult != result) { + closeQuietly(oldResult); + } + } + + public UriDerivativeLoader(Context context, Uri uri) { + super(context); + mObserver = new ForceLoadContentObserver(); + mUri = uri; + } + + @Override + protected void onStartLoading() { + if (!mObserving) { + getContext().getContentResolver().registerContentObserver(mUri, false, mObserver); + mObserving = true; + } + if (mResult != null) { + deliverResult(mResult); + } + if (takeContentChanged() || mResult == null) { + forceLoad(); + } + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void onCanceled(T result) { + closeQuietly(result); + } + + @Override + protected void onReset() { + super.onReset(); + + // Ensure the loader is stopped + onStopLoading(); + + closeQuietly(mResult); + mResult = null; + + if (mObserving) { + getContext().getContentResolver().unregisterContentObserver(mObserver); + mObserving = false; + } + } + + private void closeQuietly(T result) { + if (result instanceof AutoCloseable) { + try { + ((AutoCloseable) result).close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java new file mode 100644 index 0000000..94b9093 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java @@ -0,0 +1,169 @@ +/* + * 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.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.provider.DocumentsContract; +import android.provider.DocumentsContract.DocumentColumns; + +import com.android.documentsui.RecentsProvider; + +import java.util.Comparator; + +/** + * Representation of a single document. + */ +public class Document { + public final Uri uri; + public final String mimeType; + public final String displayName; + public final long lastModified; + public final int flags; + + private Document(Uri uri, String mimeType, String displayName, long lastModified, int flags) { + this.uri = uri; + this.mimeType = mimeType; + this.displayName = displayName; + this.lastModified = lastModified; + this.flags = flags; + } + + public static Document fromRoot(ContentResolver resolver, Root root) { + if (root.isRecents) { + final Uri uri = root.uri; + final String mimeType = DocumentsContract.MIME_TYPE_DIRECTORY; + final String displayName = root.title; + final long lastModified = -1; + final int flags = 0; + return new Document(uri, mimeType, displayName, lastModified, flags); + } else { + 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 String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); + final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); + final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); + final int flags = getCursorInt(cursor, DocumentColumns.FLAGS); + + return new Document(uri, mimeType, displayName, lastModified, flags); + } + + public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor cursor) { + final Uri uri = Uri.parse(getCursorString(cursor, RecentsProvider.COL_URI)); + final long lastModified = getCursorLong(cursor, RecentsProvider.COL_TIMESTAMP); + + final Cursor itemCursor = resolver.query(uri, null, null, null, null); + try { + if (!itemCursor.moveToFirst()) { + throw new IllegalArgumentException("Missing details for " + uri); + } + final String mimeType = getCursorString(itemCursor, DocumentColumns.MIME_TYPE); + final String displayName = getCursorString(itemCursor, DocumentColumns.DISPLAY_NAME); + final int flags = getCursorInt(itemCursor, DocumentColumns.FLAGS) + & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL; + + return new Document(uri, mimeType, displayName, lastModified, flags); + } finally { + itemCursor.close(); + } + } + + public static Document fromUri(ContentResolver resolver, Uri uri) { + final Cursor cursor = resolver.query(uri, null, null, null, null); + try { + if (!cursor.moveToFirst()) { + throw new IllegalArgumentException("Missing details for " + uri); + } + final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); + final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); + final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); + final int flags = getCursorInt(cursor, DocumentColumns.FLAGS); + + return new Document(uri, mimeType, displayName, lastModified, flags); + } finally { + cursor.close(); + } + } + + public static Document fromSearch(Uri relatedUri, String query) { + final Uri uri = DocumentsContract.buildSearchUri(relatedUri, query); + final String mimeType = DocumentsContract.MIME_TYPE_DIRECTORY; + final String displayName = query; + final long lastModified = System.currentTimeMillis(); + final int flags = 0; + return new Document(uri, mimeType, displayName, lastModified, flags); + } + + @Override + public String toString() { + return "Document{name=" + displayName + ", uri=" + uri + "}"; + } + + public boolean isCreateSupported() { + return (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0; + } + + public boolean isSearchSupported() { + return (flags & DocumentsContract.FLAG_SUPPORTS_SEARCH) != 0; + } + + public boolean isThumbnailSupported() { + return (flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0; + } + + private static String getCursorString(Cursor cursor, String columnName) { + return cursor.getString(cursor.getColumnIndexOrThrow(columnName)); + } + + private static long getCursorLong(Cursor cursor, String columnName) { + return cursor.getLong(cursor.getColumnIndexOrThrow(columnName)); + } + + private static int getCursorInt(Cursor cursor, String columnName) { + return cursor.getInt(cursor.getColumnIndexOrThrow(columnName)); + } + + public static class NameComparator implements Comparator<Document> { + @Override + public int compare(Document lhs, Document rhs) { + final boolean leftDir = DocumentsContract.MIME_TYPE_DIRECTORY.equals(lhs.mimeType); + final boolean rightDir = DocumentsContract.MIME_TYPE_DIRECTORY.equals(rhs.mimeType); + + if (leftDir != rightDir) { + return leftDir ? -1 : 1; + } else { + return lhs.displayName.compareToIgnoreCase(rhs.displayName); + } + } + } + + public static class DateComparator implements Comparator<Document> { + @Override + public int compare(Document lhs, Document rhs) { + return Long.compare(rhs.lastModified, lhs.lastModified); + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java new file mode 100644 index 0000000..96eb58e --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentsProviderInfo.java @@ -0,0 +1,121 @@ +/* + * 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 new file mode 100644 index 0000000..ef3b8d7 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/Root.java @@ -0,0 +1,92 @@ +/* + * 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.RootColumns; + +import com.android.documentsui.R; +import com.android.documentsui.RecentsProvider; + +/** + * 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 boolean isRecents; + + public static Root buildRecentOpen(Context context) { + final PackageManager pm = context.getPackageManager(); + final Root root = new Root(); + root.rootId = null; + root.rootType = DocumentsContract.ROOT_TYPE_SHORTCUT; + root.uri = RecentsProvider.buildRecentOpen(); + root.icon = context.getResources().getDrawable(R.drawable.ic_dir); + root.title = context.getString(R.string.root_recent); + root.summary = null; + 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, DocumentsContract.ROOT_DOC_ID); + root.icon = info.providerInfo.loadIcon(pm); + root.title = info.providerInfo.loadLabel(pm).toString(); + 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; + } +} |