diff options
| -rw-r--r-- | api/current.txt | 3 | ||||
| -rw-r--r-- | core/java/android/content/res/XmlResourceParser.java | 2 | ||||
| -rw-r--r-- | core/res/res/values/attrs.xml | 5 | ||||
| -rw-r--r-- | core/res/res/values/public.xml | 1 | ||||
| -rw-r--r-- | packages/DocumentsUI/res/values/strings.xml | 6 | ||||
| -rw-r--r-- | packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java | 9 | ||||
| -rw-r--r-- | packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java | 192 | ||||
| -rw-r--r-- | packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java | 2 | ||||
| -rw-r--r-- | packages/ExternalStorageProvider/AndroidManifest.xml | 2 | ||||
| -rw-r--r-- | packages/ExternalStorageProvider/res/drawable-hdpi/ic_pdf.png | bin | 0 -> 1326 bytes | |||
| -rw-r--r-- | packages/ExternalStorageProvider/res/xml/document_provider.xml | 22 |
11 files changed, 202 insertions, 42 deletions
diff --git a/api/current.txt b/api/current.txt index 620e5412d99d..944b1c24c127 100644 --- a/api/current.txt +++ b/api/current.txt @@ -387,6 +387,7 @@ package android { field public static final int cropToPadding = 16843043; // 0x1010123 field public static final int cursorVisible = 16843090; // 0x1010152 field public static final int customNavigationLayout = 16843474; // 0x10102d2 + field public static final int customRoots = 16843751; // 0x10103e7 field public static final int customTokens = 16843579; // 0x101033b field public static final int cycles = 16843220; // 0x10101d4 field public static final int dashGap = 16843175; // 0x10101a7 @@ -7624,7 +7625,7 @@ package android.content.res { method public void recycle(); } - public abstract interface XmlResourceParser implements android.util.AttributeSet org.xmlpull.v1.XmlPullParser { + public abstract interface XmlResourceParser implements android.util.AttributeSet java.lang.AutoCloseable org.xmlpull.v1.XmlPullParser { method public abstract void close(); } diff --git a/core/java/android/content/res/XmlResourceParser.java b/core/java/android/content/res/XmlResourceParser.java index c59e6d4e0e01..5af49d4d46a2 100644 --- a/core/java/android/content/res/XmlResourceParser.java +++ b/core/java/android/content/res/XmlResourceParser.java @@ -26,7 +26,7 @@ import android.util.AttributeSet; * an additional close() method on this interface for the client to indicate * when it is done reading the resource. */ -public interface XmlResourceParser extends XmlPullParser, AttributeSet { +public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable { /** * Close this interface to the resource. Calls on the interface are no * longer value after this call. diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 4cad23231b59..2096f6635c76 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -6007,4 +6007,9 @@ <attr name="digit" format="integer" /> <attr name="textView" format="reference" /> </declare-styleable> + + <declare-styleable name="DocumentsProviderInfo"> + <attr name="customRoots" format="boolean" /> + </declare-styleable> + </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index b9c36382ddaf..0eaab656cae0 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2070,4 +2070,5 @@ <public type="attr" name="vendor" /> <public type="attr" name="category" /> <public type="attr" name="isAsciiCapable" /> + <public type="attr" name="customRoots" /> </resources> diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index 18e486dc9519..665f3b13628d 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -34,7 +34,9 @@ <string name="sort_name">By name</string> <string name="sort_date">By date modified</string> - <string name="drawer_open">Open navigation drawer</string> - <string name="drawer_close">Close navigation drawer</string> + <string name="drawer_open">Show roots</string> + <string name="drawer_close">Hide roots</string> + + <string name="save_error">Failed to save document</string> </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 8b3dd99179c3..1f22613a4bd8 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -342,12 +342,15 @@ public class DirectoryFragment extends Fragment { final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); final int flags = getCursorInt(cursor, DocumentColumns.FLAGS); + final Uri uri = getArguments().getParcelable(EXTRA_URI); + final String authority = uri.getAuthority(); + if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) { - final Uri uri = getArguments().getParcelable(EXTRA_URI); - final Uri childUri = DocumentsContract.buildDocumentUri(uri.getAuthority(), guid); + final Uri childUri = DocumentsContract.buildDocumentUri(authority, guid); icon.setImageURI(childUri); } else { - icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon(context, mimeType)); + icon.setImageDrawable( + DocumentsActivity.resolveDocumentIcon(context, authority, mimeType)); } title.setText(displayName); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 13def57e5931..dcd02d24a4a1 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -37,7 +37,10 @@ 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; @@ -50,7 +53,9 @@ 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.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -66,11 +71,20 @@ import android.widget.ListView; import android.widget.SearchView; import android.widget.SearchView.OnQueryTextListener; import android.widget.TextView; +import android.widget.Toast; import com.google.android.collect.Lists; +import com.google.android.collect.Maps; +import libcore.io.IoUtils; + +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; import java.util.List; public class DocumentsActivity extends Activity { @@ -92,7 +106,9 @@ public class DocumentsActivity extends Activity { private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; - private ArrayList<Root> mRoots = Lists.newArrayList(); + private static HashMap<String, DocumentsProviderInfo> sProviders = Maps.newHashMap(); + private static ArrayList<Root> sRoots = Lists.newArrayList(); + private RootsAdapter mRootsAdapter; private ListView mRootsList; @@ -142,7 +158,7 @@ public class DocumentsActivity extends Activity { } mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - mRootsAdapter = new RootsAdapter(this, mRoots); + mRootsAdapter = new RootsAdapter(this, sRoots); mRootsList = (ListView) findViewById(R.id.roots_list); mRootsList.setAdapter(mRootsAdapter); mRootsList.setOnItemClickListener(mRootsListener); @@ -406,9 +422,13 @@ public class DocumentsActivity extends Activity { values.put(DocumentColumns.MIME_TYPE, mimeType); values.put(DocumentColumns.DISPLAY_NAME, displayName); - // TODO: handle errors from remote side final Uri uri = getContentResolver().insert(mCurrentDir, values); - onFinished(uri); + if (uri != null) { + onFinished(uri); + } else { + // TODO: ask for overwrite confirmation + Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show(); + } } private void onFinished(Uri... uris) { @@ -448,37 +468,52 @@ public class DocumentsActivity extends Activity { } public static class Root { + public DocumentsProviderInfo info; public int rootType; public Uri uri; public Drawable icon; public String title; public String summary; - public static Root fromCursor(Context context, ProviderInfo info, Cursor cursor) { + public static Root fromInfo(Context context, DocumentsProviderInfo info) { final Root root = new Root(); + final PackageManager pm = context.getPackageManager(); - root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE)); + root.info = info; + root.rootType = DocumentsContract.ROOT_TYPE_SERVICE; root.uri = DocumentsContract.buildDocumentUri( - info.authority, cursor.getString(cursor.getColumnIndex(RootColumns.GUID))); + info.providerInfo.authority, DocumentsContract.ROOT_GUID); + root.icon = info.providerInfo.loadIcon(pm); + root.title = info.providerInfo.loadLabel(pm).toString(); + root.summary = null; + + return root; + } + + public static Root fromCursor( + Context context, DocumentsProviderInfo info, Cursor cursor) { + final Root root = fromInfo(context, info); + + root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE)); + root.uri = DocumentsContract.buildDocumentUri(info.providerInfo.authority, + cursor.getString(cursor.getColumnIndex(RootColumns.GUID))); final PackageManager pm = context.getPackageManager(); final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON)); if (icon != 0) { try { - root.icon = pm.getResourcesForApplication(info.applicationInfo) + root.icon = pm.getResourcesForApplication(info.providerInfo.applicationInfo) .getDrawable(icon); } catch (NotFoundException e) { throw new RuntimeException(e); } catch (NameNotFoundException e) { throw new RuntimeException(e); } - } else { - root.icon = info.loadIcon(pm); } - root.title = cursor.getString(cursor.getColumnIndex(RootColumns.TITLE)); - if (root.title == null) { - root.title = info.loadLabel(pm).toString(); + final String title = cursor.getString(cursor.getColumnIndex(RootColumns.TITLE)); + if (title != null) { + root.title = title; } root.summary = cursor.getString(cursor.getColumnIndex(RootColumns.SUMMARY)); @@ -487,6 +522,17 @@ public class DocumentsActivity extends Activity { } } + 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; @@ -541,8 +587,17 @@ public class DocumentsActivity extends Activity { } } - public static Drawable resolveDocumentIcon(Context context, String mimeType) { - // TODO: allow backends to provide custom MIME icons + public static Drawable resolveDocumentIcon(Context context, String authority, String mimeType) { + // Custom icons take precedence + final DocumentsProviderInfo info = sProviders.get(authority); + if (info != null) { + for (Icon icon : info.customIcons) { + if (mimeMatches(icon.mimeType, mimeType)) { + return icon.icon; + } + } + } + if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) { return context.getResources().getDrawable(R.drawable.ic_dir); } else { @@ -550,39 +605,110 @@ public class DocumentsActivity extends Activity { final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setType(mimeType); - final ResolveInfo info = pm.resolveActivity( + final ResolveInfo activityInfo = pm.resolveActivity( intent, PackageManager.MATCH_DEFAULT_ONLY); - if (info != null) { - return info.loadIcon(pm); + if (activityInfo != null) { + return activityInfo.loadIcon(pm); } else { return null; } } } + private static final String TAG_DOCUMENTS_PROVIDER = "documents-provider"; + private static final String TAG_ICON = "icon"; + /** * Gather roots from all known storage providers. */ private void updateRoots() { - mRoots.clear(); - - final List<ProviderInfo> providers = getPackageManager() - .queryContentProviders(null, -1, PackageManager.GET_META_DATA); - for (ProviderInfo info : providers) { - if (info.metaData != null - && info.metaData.containsKey(DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) { - // TODO: populate roots on background thread, and cache results - final Uri uri = DocumentsContract.buildRootsUri(info.authority); - final Cursor cursor = getContentResolver().query(uri, null, null, null, null); - try { - while (cursor.moveToNext()) { - mRoots.add(Root.fromCursor(this, info, cursor)); + sProviders.clear(); + sRoots.clear(); + + final PackageManager pm = getPackageManager(); + 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); + if (info == null) { + Log.w(TAG, "Missing info for " + providerInfo); + continue; + } + + sProviders.put(info.providerInfo.authority, info); + + if (info.customRoots) { + // TODO: populate roots on background thread, and cache results + final Uri uri = DocumentsContract.buildRootsUri(providerInfo.authority); + final Cursor cursor = getContentResolver().query(uri, null, null, null, null); + try { + while (cursor.moveToNext()) { + sRoots.add(Root.fromCursor(this, info, cursor)); + } + } finally { + cursor.close(); } - } finally { - cursor.close(); + } else if (info != null) { + sRoots.add(Root.fromInfo(this, info)); + } + } + } + } + + 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() { diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java index a2a4f7cb285f..cdc399d00a6a 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java @@ -66,7 +66,7 @@ public class SaveFragment extends Fragment { final ImageView icon = (ImageView) view.findViewById(android.R.id.icon); icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon( - context, getArguments().getString(EXTRA_MIME_TYPE))); + context, null, getArguments().getString(EXTRA_MIME_TYPE))); mDisplayName = (EditText) view.findViewById(android.R.id.title); mDisplayName.setText(getArguments().getString(EXTRA_DISPLAY_NAME)); diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 37dc5b101e4b..afdb6bb0be24 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -13,7 +13,7 @@ android:permission="android.permission.MANAGE_DOCUMENTS"> <meta-data android:name="android.content.DOCUMENT_PROVIDER" - android:value="true" /> + android:resource="@xml/document_provider" /> </provider> </application> </manifest> diff --git a/packages/ExternalStorageProvider/res/drawable-hdpi/ic_pdf.png b/packages/ExternalStorageProvider/res/drawable-hdpi/ic_pdf.png Binary files differnew file mode 100644 index 000000000000..961a9bbfcfcf --- /dev/null +++ b/packages/ExternalStorageProvider/res/drawable-hdpi/ic_pdf.png diff --git a/packages/ExternalStorageProvider/res/xml/document_provider.xml b/packages/ExternalStorageProvider/res/xml/document_provider.xml new file mode 100644 index 000000000000..929a2734a0f2 --- /dev/null +++ b/packages/ExternalStorageProvider/res/xml/document_provider.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<documents-provider xmlns:android="http://schemas.android.com/apk/res/android" + android:customRoots="true"> + + <icon android:mimeType="application/pdf" android:icon="@drawable/ic_pdf" /> + <icon android:mimeType="text/*" android:icon="@drawable/ic_pdf" /> +</documents-provider> |