diff options
| author | Gilles Debunne <debunne@google.com> | 2010-06-09 14:11:45 -0700 |
|---|---|---|
| committer | Gilles Debunne <debunne@google.com> | 2010-06-23 10:43:00 -0700 |
| commit | 03f0292744094ec107ffce71301c394503a31ded (patch) | |
| tree | cd01b60e4ed28aef6b5e9abc7b86d90dcaba83e1 | |
| parent | 84d000e3c4d8883afec1e47662f719c6119cfefc (diff) | |
| download | frameworks_base-03f0292744094ec107ffce71301c394503a31ded.zip frameworks_base-03f0292744094ec107ffce71301c394503a31ded.tar.gz frameworks_base-03f0292744094ec107ffce71301c394503a31ded.tar.bz2 | |
New XmlDocumentProvider class.
Minor changes in the Adapters.java helper class.
Extracts data out of a XML document using an XPath-like syntax.
Change-Id: I0617b0783f11c86118b42cd8485d54440810c805
| -rw-r--r-- | api/current.xml | 139 | ||||
| -rw-r--r-- | core/java/android/content/ContentResolver.java | 9 | ||||
| -rw-r--r-- | core/java/android/content/XmlDocumentProvider.java | 436 | ||||
| -rw-r--r-- | core/java/android/widget/Adapters.java | 255 | ||||
| -rw-r--r-- | graphics/java/android/graphics/BitmapFactory.java | 6 |
5 files changed, 729 insertions, 116 deletions
diff --git a/api/current.xml b/api/current.xml index c80c9ed..e634dac 100644 --- a/api/current.xml +++ b/api/current.xml @@ -48438,6 +48438,145 @@ > </field> </class> +<class name="XmlDocumentProvider" + extends="android.content.ContentProvider" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="XmlDocumentProvider" + type="android.content.XmlDocumentProvider" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="delete" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="selection" type="java.lang.String"> +</parameter> +<parameter name="selectionArgs" type="java.lang.String[]"> +</parameter> +</method> +<method name="getResourceXmlPullParser" + return="org.xmlpull.v1.XmlPullParser" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="protected" +> +<parameter name="resourceUri" type="android.net.Uri"> +</parameter> +</method> +<method name="getType" + return="java.lang.String" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +</method> +<method name="getUriXmlPullParser" + return="org.xmlpull.v1.XmlPullParser" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="protected" +> +<parameter name="url" type="java.lang.String"> +</parameter> +</method> +<method name="insert" + return="android.net.Uri" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="values" type="android.content.ContentValues"> +</parameter> +</method> +<method name="onCreate" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="query" + return="android.database.Cursor" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="projection" type="java.lang.String[]"> +</parameter> +<parameter name="selection" type="java.lang.String"> +</parameter> +<parameter name="selectionArgs" type="java.lang.String[]"> +</parameter> +<parameter name="sortOrder" type="java.lang.String"> +</parameter> +</method> +<method name="update" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +<parameter name="values" type="android.content.ContentValues"> +</parameter> +<parameter name="selection" type="java.lang.String"> +</parameter> +<parameter name="selectionArgs" type="java.lang.String[]"> +</parameter> +</method> +</class> </package> <package name="android.content.pm" > diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index b4718ab..5437ea1 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -43,9 +43,9 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.List; import java.util.Random; -import java.util.ArrayList; /** @@ -400,8 +400,7 @@ public abstract class ContentResolver { /** * Open a raw file descriptor to access data under a "content:" URI. This * interacts with the underlying {@link ContentProvider#openAssetFile} - * ContentProvider.openAssetFile()} method of the provider associated with the - * given URI, to retrieve any file stored there. + * method of the provider associated with the given URI, to retrieve any file stored there. * * <h5>Accepts the following URI schemes:</h5> * <ul> @@ -1341,7 +1340,7 @@ public abstract class ContentResolver { } private final class CursorWrapperInner extends CursorWrapper { - private IContentProvider mContentProvider; + private final IContentProvider mContentProvider; public static final String TAG="CursorWrapperInner"; private boolean mCloseFlag = false; @@ -1370,7 +1369,7 @@ public abstract class ContentResolver { } private final class ParcelFileDescriptorInner extends ParcelFileDescriptor { - private IContentProvider mContentProvider; + private final IContentProvider mContentProvider; public static final String TAG="ParcelFileDescriptorInner"; private boolean mReleaseProviderFlag = false; diff --git a/core/java/android/content/XmlDocumentProvider.java b/core/java/android/content/XmlDocumentProvider.java new file mode 100644 index 0000000..153ad38 --- /dev/null +++ b/core/java/android/content/XmlDocumentProvider.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2010 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 android.content; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpGet; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import android.content.ContentResolver.OpenResourceIdResult; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.net.http.AndroidHttpClient; +import android.util.Log; +import android.widget.CursorAdapter; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.BitSet; +import java.util.Stack; +import java.util.regex.Pattern; + +/** + * A read-only content provider which extracts data out of an XML document. + * + * <p>A XPath-like selection pattern is used to select some nodes in the XML document. Each such + * node will create a row in the {@link Cursor} result.</p> + * + * Each row is then populated with columns that are also defined as XPath-like projections. These + * projections fetch attributes values or text in the matching row node or its children. + * + * <p>To add this provider in your application, you should add its declaration to your application + * manifest: + * <pre class="prettyprint"> + * <provider android:name="android.content.XmlDocumentProvider" android:authorities="xmldocument" /> + * </pre> + * </p> + * + * <h2>Node selection syntax</h2> + * The node selection syntax is made of the concatenation of an arbitrary number (at least one) of + * <code>/node_name</code> node selection patterns. + * + * <p>The <code>/root/child1/child2</code> pattern will for instance match all nodes named + * <code>child2</code> which are children of a node named <code>child1</code> which are themselves + * children of a root node named <code>root</code>.</p> + * + * Any <code>/</code> separator in the previous expression can be replaced by a <code>//</code> + * separator instead, which indicated a <i>descendant</i> instead of a child. + * + * <p>The <code>//node1//node2</code> pattern will for instance match all nodes named + * <code>node2</code> which are descendant of a node named <code>node1</code> located anywhere in + * the document hierarchy.</p> + * + * Node names can contain namespaces in the form <code>namespace:node</code>. + * + * <h2>Projection syntax</h2> + * For every selected node, the projection will then extract actual data from this node and its + * descendant. + * + * <p>Use a syntax similar to the selection syntax described above to select the text associated + * with a child of the selected node. The implicit root of this projection pattern is the selected + * node. <code>/</code> will hence refer to the text of the selected node, while + * <code>/child1</code> will fetch the text of its child named <code>child1</code> and + * <code>//child1</code> will match any <i>descendant</i> named <code>child1</code>. If several + * nodes match the projection pattern, their texts are appended as a result.</p> + * + * A projection can also fetch any node attribute by appending a <code>@attribute_name</code> + * pattern to the previously described syntax. <code>//child1@price</code> will for instance match + * the attribute <code>price</code> of any <code>child1</code> descendant. + * + * <p>If a projection does not match any node/attribute, its associated value will be an empty + * string.</p> + * + * <h2>Example</h2> + * Using the following XML document: + * <pre class="prettyprint"> + * <library> + * <book id="EH94"> + * <title>The Old Man and the Sea</title> + * <author>Ernest Hemingway</author> + * </book> + * <book id="XX10"> + * <title>The Arabian Nights: Tales of 1,001 Nights</title> + * </book> + * <no-id> + * <book> + * <title>Animal Farm</title> + * <author>George Orwell</author> + * </book> + * </no-id> + * </library> + * </pre> + * A selection pattern of <code>/library//book</code> will match the three book entries (while + * <code>/library/book</code> will only match the first two ones). + * + * <p>Defining the projections as <code>/title</code>, <code>/author</code> and <code>@id</code> + * will retrieve the associated data. Note that the author of the second book as well as the id of + * the third are empty strings. + */ +public class XmlDocumentProvider extends ContentProvider { + /* + * Ideas for improvement: + * - Expand XPath-like syntax to allow for [nb] child number selector + * - Address the starting . bug in AbstractCursor which prevents a true XPath syntax. + * - Provide an alternative to concatenation when several node match (list-like). + * - Support namespaces in attribute names. + * - Incremental Cursor creation, pagination + */ + private static final String LOG_TAG = "XmlDocumentProvider"; + private AndroidHttpClient mHttpClient; + + @Override + public boolean onCreate() { + return true; + } + + /** + * Query data from the XML document referenced in the URI. + * + * <p>The XML document can be a local resource or a file that will be downloaded from the + * Internet. In the latter case, your application needs to request the INTERNET permission in + * its manifest.</p> + * + * The URI will be of the form <code>content://xmldocument/?resource=R.xml.myFile</code> for a + * local resource. <code>xmldocument</code> should match the authority declared for this + * provider in your manifest. Internet documents are referenced using + * <code>content://xmldocument/?url=</code> followed by an encoded version of the URL of your + * document (see {@link Uri#encode(String)}). + * + * <p>The number of columns of the resulting Cursor is equal to the size of the projection + * array plus one, named <code>_id</code> which will contain a unique row id (allowing the + * Cursor to be used with a {@link CursorAdapter}). The other columns' names are the projection + * patterns.</p> + * + * @param uri The URI of your local resource or Internet document. + * @param projection A set of patterns that will be used to extract data from each selected + * node. See class documentation for pattern syntax. + * @param selection A selection pattern which will select the nodes that will create the + * Cursor's rows. See class documentation for pattern syntax. + * @param selectionArgs This parameter is ignored. + * @param sortOrder The row order in the resulting cursor is determined from the node order in + * the XML document. This parameter is ignored. + * @return A Cursor or null in case of error. + */ + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + + XmlPullParser parser = null; + mHttpClient = null; + + final String url = uri.getQueryParameter("url"); + if (url != null) { + parser = getUriXmlPullParser(url); + } else { + final String resource = uri.getQueryParameter("resource"); + if (resource != null) { + Uri resourceUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + + getContext().getPackageName() + "/" + resource); + parser = getResourceXmlPullParser(resourceUri); + } + } + + if (parser != null) { + XMLCursor xmlCursor = new XMLCursor(selection, projection); + try { + xmlCursor.parseWith(parser); + return xmlCursor; + } catch (IOException e) { + Log.w(LOG_TAG, "I/O error while parsing XML " + uri, e); + } catch (XmlPullParserException e) { + Log.w(LOG_TAG, "Error while parsing XML " + uri, e); + } finally { + if (mHttpClient != null) { + mHttpClient.close(); + } + } + } + + return null; + } + + /** + * Creates an XmlPullParser for the provided URL. Can be overloaded to provide your own parser. + * @param url The URL of the XML document that is to be parsed. + * @return An XmlPullParser on this document. + */ + protected XmlPullParser getUriXmlPullParser(String url) { + XmlPullParser parser = null; + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + parser = factory.newPullParser(); + } catch (XmlPullParserException e) { + Log.e(LOG_TAG, "Unable to create XmlPullParser", e); + return null; + } + + InputStream inputStream = null; + try { + final HttpGet get = new HttpGet(url); + mHttpClient = AndroidHttpClient.newInstance("Android"); + HttpResponse response = mHttpClient.execute(get); + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + final HttpEntity entity = response.getEntity(); + if (entity != null) { + inputStream = entity.getContent(); + } + } + } catch (IOException e) { + Log.w(LOG_TAG, "Error while retrieving XML file " + url, e); + return null; + } + + try { + parser.setInput(inputStream, null); + } catch (XmlPullParserException e) { + Log.w(LOG_TAG, "Error while reading XML file from " + url, e); + return null; + } + + return parser; + } + + /** + * Creates an XmlPullParser for the provided local resource. Can be overloaded to provide your + * own parser. + * @param resourceUri A fully qualified resource name referencing a local XML resource. + * @return An XmlPullParser on this resource. + */ + protected XmlPullParser getResourceXmlPullParser(Uri resourceUri) { + OpenResourceIdResult resourceId; + try { + resourceId = getContext().getContentResolver().getResourceId(resourceUri); + return resourceId.r.getXml(resourceId.id); + } catch (FileNotFoundException e) { + Log.w(LOG_TAG, "XML resource not found: " + resourceUri.toString(), e); + return null; + } + } + + /** + * Returns "vnd.android.cursor.dir/xmldoc". + */ + @Override + public String getType(Uri uri) { + return "vnd.android.cursor.dir/xmldoc"; + } + + /** + * This ContentProvider is read-only. This method throws an UnsupportedOperationException. + **/ + @Override + public Uri insert(Uri uri, ContentValues values) { + throw new UnsupportedOperationException(); + } + + /** + * This ContentProvider is read-only. This method throws an UnsupportedOperationException. + **/ + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + + /** + * This ContentProvider is read-only. This method throws an UnsupportedOperationException. + **/ + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + + private static class XMLCursor extends MatrixCursor { + private final Pattern mSelectionPattern; + private Pattern[] mProjectionPatterns; + private String[] mAttributeNames; + private String[] mCurrentValues; + private BitSet[] mActiveTextDepthMask; + private final int mNumberOfProjections; + + public XMLCursor(String selection, String[] projections) { + super(projections); + // The first column in projections is used for the _ID + mNumberOfProjections = projections.length - 1; + mSelectionPattern = createPattern(selection); + createProjectionPattern(projections); + } + + private Pattern createPattern(String input) { + String pattern = input.replaceAll("//", "/(.*/|)").replaceAll("^/", "^/") + "$"; + return Pattern.compile(pattern); + } + + private void createProjectionPattern(String[] projections) { + mProjectionPatterns = new Pattern[mNumberOfProjections]; + mAttributeNames = new String[mNumberOfProjections]; + mActiveTextDepthMask = new BitSet[mNumberOfProjections]; + // Add a column to store _ID + mCurrentValues = new String[mNumberOfProjections + 1]; + + for (int i=0; i<mNumberOfProjections; i++) { + mActiveTextDepthMask[i] = new BitSet(); + String projection = projections[i + 1]; // +1 to skip the _ID column + int atIndex = projection.lastIndexOf('@', projection.length()); + if (atIndex >= 0) { + mAttributeNames[i] = projection.substring(atIndex+1); + projection = projection.substring(0, atIndex); + } else { + mAttributeNames[i] = null; + } + + // Conforms to XPath standard: reference to local context starts with a . + if (projection.charAt(0) == '.') { + projection = projection.substring(1); + } + mProjectionPatterns[i] = createPattern(projection); + } + } + + public void parseWith(XmlPullParser parser) throws IOException, XmlPullParserException { + StringBuilder path = new StringBuilder(); + Stack<Integer> pathLengthStack = new Stack<Integer>(); + + // There are two parsing mode: in root mode, rootPath is updated and nodes matching + // selectionPattern are searched for and currentNodeDepth is negative. + // When a node matching selectionPattern is found, currentNodeDepth is set to 0 and + // updated as children are parsed and projectionPatterns are searched in nodePath. + int currentNodeDepth = -1; + + // Index where local selected node path starts from in path + int currentNodePathStartIndex = 0; + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + + if (eventType == XmlPullParser.START_TAG) { + // Update path + pathLengthStack.push(path.length()); + path.append('/'); + String prefix = null; + try { + // getPrefix is not supported by local Xml resource parser + prefix = parser.getPrefix(); + } catch (RuntimeException e) { + prefix = null; + } + if (prefix != null) { + path.append(prefix); + path.append(':'); + } + path.append(parser.getName()); + + if (currentNodeDepth >= 0) { + currentNodeDepth++; + } else { + // A node matching selection is found: initialize child parsing mode + if (mSelectionPattern.matcher(path.toString()).matches()) { + currentNodeDepth = 0; + currentNodePathStartIndex = path.length(); + mCurrentValues[0] = Integer.toString(getCount()); // _ID + for (int i = 0; i < mNumberOfProjections; i++) { + // Reset values to default (empty string) + mCurrentValues[i + 1] = ""; + mActiveTextDepthMask[i].clear(); + } + } + } + + // This test has to be separated from the previous one as currentNodeDepth can + // be modified above (when a node matching selection is found). + if (currentNodeDepth >= 0) { + final String localNodePath = path.substring(currentNodePathStartIndex); + for (int i = 0; i < mNumberOfProjections; i++) { + if (mProjectionPatterns[i].matcher(localNodePath).matches()) { + String attribute = mAttributeNames[i]; + if (attribute != null) { + mCurrentValues[i + 1] = + parser.getAttributeValue(null, attribute); + } else { + mActiveTextDepthMask[i].set(currentNodeDepth, true); + } + } + } + } + + } else if (eventType == XmlPullParser.END_TAG) { + // Pop last node from path + final int length = pathLengthStack.pop(); + path.setLength(length); + + if (currentNodeDepth >= 0) { + if (currentNodeDepth == 0) { + // Leaving a selection matching node: add a new row with results + addRow(mCurrentValues); + } else { + for (int i = 0; i < mNumberOfProjections; i++) { + mActiveTextDepthMask[i].set(currentNodeDepth, false); + } + } + currentNodeDepth--; + } + + } else if ((eventType == XmlPullParser.TEXT) && (!parser.isWhitespace())) { + for (int i = 0; i < mNumberOfProjections; i++) { + if ((currentNodeDepth >= 0) && + (mActiveTextDepthMask[i].get(currentNodeDepth))) { + mCurrentValues[i + 1] += parser.getText(); + } + } + } + + eventType = parser.next(); + } + } + } +} diff --git a/core/java/android/widget/Adapters.java b/core/java/android/widget/Adapters.java index 05e501a..7fd7fb5 100644 --- a/core/java/android/widget/Adapters.java +++ b/core/java/android/widget/Adapters.java @@ -16,6 +16,9 @@ package android.widget; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import android.app.Activity; import android.content.Context; import android.content.res.Resources; @@ -28,8 +31,6 @@ import android.os.AsyncTask; import android.util.AttributeSet; import android.util.Xml; import android.view.View; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.lang.reflect.Constructor; @@ -37,8 +38,6 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; -import static com.android.internal.R.*; - /** * <p>This class can be used to load {@link android.widget.Adapter adapters} defined in * XML resources. XML-defined adapters can be used to easily create adapters in your @@ -80,7 +79,8 @@ import static com.android.internal.R.*; * {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}. * This attribute is optional.</li> * <li><code>android:uri</code>: URI of the content provider to query to retrieve a cursor. - * Specifying this attribute is equivalent to calling {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}. + * Specifying this attribute is equivalent to calling + * {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}. * If you call this method, the value of the XML attribute is ignored. This attribute is * optional.</li> * </ul> @@ -102,7 +102,7 @@ import static com.android.internal.R.*; * <li><code>android:column</code>: Name of the column to select in the cursor during the * query operation</li> * </ul> - * <p><strong>Note:</strong> The column named <code>_id</code> is always implicitely + * <p><strong>Note:</strong> The column named <code>_id</code> is always implicitly * selected.</p> * * <a name="xml-cursor-adapter-bind-tag" /> @@ -123,14 +123,15 @@ import static com.android.internal.R.*; * <p>The <code><bind /></code> tag supports the following attributes:</p> * <ul> * <li><code>android:from</code>: The name of the column to bind from. - * This attribute is mandatory.</li> + * This attribute is mandatory. Note that <code>@</code> which are not used to reference resources + * should be backslash protected as in <code>\@</code>.</li> * <li><code>android:to</code>: The id of the view to bind to. This attribute is mandatory.</li> * <li><code>android:as</code>: The <a href="#xml-cursor-adapter-bind-data-types">data type</a> * of the binding. This attribute is mandatory.</li> * </ul> * * <p>In addition, a <code><bind /></code> can contain zero or more instances of - * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> chilren + * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> children * tags.</p> * * <a name="xml-cursor-adapter-bind-data-types" /> @@ -146,11 +147,14 @@ import static com.android.internal.R.*; * and must be bound to an {@link android.widget.ImageView}</li> * <li><code>drawable</code>: The content of the column is interpreted as a resource id to a * drawable and must be bound to an {@link android.widget.ImageView}</li> + * <li><code>tag</code>: The content of the column is interpreted as a string and will be set as + * the tag (using {@link View#setTag(Object)} of the associated View. This can be used to + * associate meta-data to your view, that can be used for instance by a listener.</li> * <li>A fully qualified class name: The name of a class corresponding to an implementation of * {@link android.widget.Adapters.CursorBinder}. Cursor binders can be used to provide * bindings not supported by default. Custom binders cannot be used with * {@link android.content.Context#isRestricted() restricted contexts}, for instance in an - * app widget</li> + * application widget</li> * </ul> * * <a name="xml-cursor-adapter-bind-transformation" /> @@ -193,7 +197,7 @@ import static com.android.internal.R.*; * is specified</li> * <li><code>android:withClass</code>: A fully qualified class name corresponding to an * implementation of {@link android.widget.Adapters.CursorTransformation}. Custom - * transformationscannot be used with + * transformations cannot be used with * {@link android.content.Context#isRestricted() restricted contexts}, for instance in * an app widget This attribute is mandatory if <code>android:withExpression</code> is * not specified</li> @@ -409,7 +413,7 @@ public class Adapters { return adapter; } - + /** * <p>Loads the {@link android.widget.CursorAdapter} defined in the specified * XML resource. The content of the adapter is loaded from the specified cursor. @@ -488,7 +492,7 @@ public class Adapters { * @return An instance of {@link android.widget.BaseAdapter}. */ private static BaseAdapter loadAdapter(Context context, int id, String assertName, - Object... parameters) { + Object... parameters) { XmlResourceParser parser = null; try { @@ -498,13 +502,13 @@ public class Adapters { } catch (XmlPullParserException ex) { Resources.NotFoundException rnf = new Resources.NotFoundException( "Can't load adapter resource ID " + - context.getResources().getResourceEntryName(id)); + context.getResources().getResourceEntryName(id)); rnf.initCause(ex); throw rnf; } catch (IOException ex) { Resources.NotFoundException rnf = new Resources.NotFoundException( "Can't load adapter resource ID " + - context.getResources().getResourceEntryName(id)); + context.getResources().getResourceEntryName(id)); rnf.initCause(ex); throw rnf; } finally { @@ -524,9 +528,9 @@ public class Adapters { private static BaseAdapter createAdapterFromXml(Context c, XmlPullParser parser, AttributeSet attrs, int id, Object[] parameters, String assertName) throws XmlPullParserException, IOException { - + BaseAdapter adapter = null; - + // Make sure we are on a start tag. int type; int depth = parser.getDepth(); @@ -543,7 +547,7 @@ public class Adapters { throw new IllegalArgumentException("The adapter defined in " + c.getResources().getResourceEntryName(id) + " must be a <" + name + " />"); } - + if (ADAPTER_CURSOR.equals(name)) { adapter = createCursorAdapter(c, parser, attrs, id, parameters); } else { @@ -551,7 +555,7 @@ public class Adapters { " in " + c.getResources().getResourceEntryName(id)); } } - + return adapter; } @@ -575,6 +579,7 @@ public class Adapters { private static final String ADAPTER_CURSOR_SELECT = "select"; private static final String ADAPTER_CURSOR_AS_STRING = "string"; private static final String ADAPTER_CURSOR_AS_IMAGE = "image"; + private static final String ADAPTER_CURSOR_AS_TAG = "tag"; private static final String ADAPTER_CURSOR_AS_IMAGE_URI = "image-uri"; private static final String ADAPTER_CURSOR_AS_DRAWABLE = "drawable"; private static final String ADAPTER_CURSOR_MAP = "map"; @@ -605,45 +610,45 @@ public class Adapters { } public XmlCursorAdapter parse(Object[] parameters) - throws IOException, XmlPullParserException { + throws IOException, XmlPullParserException { Resources resources = mResources; - TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter); - - String uri = a.getString(styleable.CursorAdapter_uri); - String selection = a.getString(styleable.CursorAdapter_selection); - String sortOrder = a.getString(styleable.CursorAdapter_sortOrder); - int layout = a.getResourceId(styleable.CursorAdapter_layout, 0); + TypedArray a = resources.obtainAttributes(mAttrs, android.R.styleable.CursorAdapter); + + String uri = a.getString(android.R.styleable.CursorAdapter_uri); + String selection = a.getString(android.R.styleable.CursorAdapter_selection); + String sortOrder = a.getString(android.R.styleable.CursorAdapter_sortOrder); + int layout = a.getResourceId(android.R.styleable.CursorAdapter_layout, 0); if (layout == 0) { throw new IllegalArgumentException("The layout specified in " + resources.getResourceEntryName(mId) + " does not exist"); } a.recycle(); - + XmlPullParser parser = mParser; int type; int depth = parser.getDepth(); - + while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - + if (type != XmlPullParser.START_TAG) { continue; } - + String name = parser.getName(); - + if (ADAPTER_CURSOR_BIND.equals(name)) { parseBindTag(); } else if (ADAPTER_CURSOR_SELECT.equals(name)) { parseSelectTag(); } else { throw new RuntimeException("Unknown tag name " + parser.getName() + " in " + - resources.getResourceEntryName(mId)); + resources.getResourceEntryName(mId)); } } - + String[] fromArray = mFrom.toArray(new String[mFrom.size()]); int[] toArray = new int[mTo.size()]; for (int i = 0; i < toArray.length; i++) { @@ -661,70 +666,73 @@ public class Adapters { return new XmlCursorAdapter(mContext, layout, uri, fromArray, toArray, selection, selectionArgs, sortOrder, mBinders); } - + private void parseSelectTag() { - TypedArray a = mResources.obtainAttributes(mAttrs, styleable.CursorAdapter_SelectItem); - - String fromName = a.getString(styleable.CursorAdapter_SelectItem_column); + TypedArray a = mResources.obtainAttributes(mAttrs, + android.R.styleable.CursorAdapter_SelectItem); + + String fromName = a.getString(android.R.styleable.CursorAdapter_SelectItem_column); if (fromName == null) { throw new IllegalArgumentException("A select item in " + - mResources.getResourceEntryName(mId) + " does not have a 'column' attribute"); + mResources.getResourceEntryName(mId) + + " does not have a 'column' attribute"); } - + a.recycle(); - + mFrom.add(fromName); mTo.add(View.NO_ID); } - + private void parseBindTag() throws IOException, XmlPullParserException { Resources resources = mResources; - TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_BindItem); - - String fromName = a.getString(styleable.CursorAdapter_BindItem_from); + TypedArray a = resources.obtainAttributes(mAttrs, + android.R.styleable.CursorAdapter_BindItem); + + String fromName = a.getString(android.R.styleable.CursorAdapter_BindItem_from); if (fromName == null) { throw new IllegalArgumentException("A bind item in " + - resources.getResourceEntryName(mId) + " does not have a 'from' attribute"); + resources.getResourceEntryName(mId) + " does not have a 'from' attribute"); } - - int toName = a.getResourceId(styleable.CursorAdapter_BindItem_to, 0); + + int toName = a.getResourceId(android.R.styleable.CursorAdapter_BindItem_to, 0); if (toName == 0) { throw new IllegalArgumentException("A bind item in " + - resources.getResourceEntryName(mId) + " does not have a 'to' attribute"); + resources.getResourceEntryName(mId) + " does not have a 'to' attribute"); } - - String asType = a.getString(styleable.CursorAdapter_BindItem_as); + + String asType = a.getString(android.R.styleable.CursorAdapter_BindItem_as); if (asType == null) { throw new IllegalArgumentException("A bind item in " + - resources.getResourceEntryName(mId) + " does not have an 'as' attribute"); + resources.getResourceEntryName(mId) + " does not have an 'as' attribute"); } - + mFrom.add(fromName); mTo.add(toName); mBinders.put(fromName, findBinder(asType)); - + a.recycle(); } - + private CursorBinder findBinder(String type) throws IOException, XmlPullParserException { final XmlPullParser parser = mParser; final Context context = mContext; CursorTransformation transformation = mIdentity; - + int tagType; int depth = parser.getDepth(); - + final boolean isDrawable = ADAPTER_CURSOR_AS_DRAWABLE.equals(type); - + while (((tagType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && tagType != XmlPullParser.END_DOCUMENT) { - + if (tagType != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); - + if (ADAPTER_CURSOR_TRANSFORM.equals(name)) { transformation = findTransformation(); } else if (ADAPTER_CURSOR_MAP.equals(name)) { @@ -734,12 +742,14 @@ public class Adapters { findMap(((MapTransformation) transformation), isDrawable); } else { throw new RuntimeException("Unknown tag name " + parser.getName() + " in " + - context.getResources().getResourceEntryName(mId)); + context.getResources().getResourceEntryName(mId)); } } - + if (ADAPTER_CURSOR_AS_STRING.equals(type)) { return new StringBinder(context, transformation); + } else if (ADAPTER_CURSOR_AS_TAG.equals(type)) { + return new TagBinder(context, transformation); } else if (ADAPTER_CURSOR_AS_IMAGE.equals(type)) { return new ImageBinder(context, transformation); } else if (ADAPTER_CURSOR_AS_IMAGE_URI.equals(type)) { @@ -763,19 +773,19 @@ public class Adapters { } } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + type, e); + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + type, e); + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + type, e); + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } catch (InstantiationException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + type, e); + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + type, e); + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } return null; @@ -784,26 +794,30 @@ public class Adapters { private void findMap(MapTransformation transformation, boolean drawable) { Resources resources = mResources; - TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_MapItem); + TypedArray a = resources.obtainAttributes(mAttrs, + android.R.styleable.CursorAdapter_MapItem); - String from = a.getString(styleable.CursorAdapter_MapItem_fromValue); + String from = a.getString(android.R.styleable.CursorAdapter_MapItem_fromValue); if (from == null) { throw new IllegalArgumentException("A map item in " + - resources.getResourceEntryName(mId) + " does not have a 'fromValue' attribute"); + resources.getResourceEntryName(mId) + + " does not have a 'fromValue' attribute"); } if (!drawable) { - String to = a.getString(styleable.CursorAdapter_MapItem_toValue); + String to = a.getString(android.R.styleable.CursorAdapter_MapItem_toValue); if (to == null) { throw new IllegalArgumentException("A map item in " + - resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute"); + resources.getResourceEntryName(mId) + + " does not have a 'toValue' attribute"); } transformation.addStringMapping(from, to); } else { - int to = a.getResourceId(styleable.CursorAdapter_MapItem_toValue, 0); + int to = a.getResourceId(android.R.styleable.CursorAdapter_MapItem_toValue, 0); if (to == 0) { throw new IllegalArgumentException("A map item in " + - resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute"); + resources.getResourceEntryName(mId) + + " does not have a 'toValue' attribute"); } transformation.addResourceMapping(from, to); } @@ -814,40 +828,41 @@ public class Adapters { private CursorTransformation findTransformation() { Resources resources = mResources; CursorTransformation transformation = null; - TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_TransformItem); - - String className = a.getString(styleable.CursorAdapter_TransformItem_withClass); + TypedArray a = resources.obtainAttributes(mAttrs, + android.R.styleable.CursorAdapter_TransformItem); + + String className = a.getString(android.R.styleable.CursorAdapter_TransformItem_withClass); if (className == null) { String expression = a.getString( - styleable.CursorAdapter_TransformItem_withExpression); + android.R.styleable.CursorAdapter_TransformItem_withExpression); transformation = createExpressionTransformation(expression); } else if (!mContext.isRestricted()) { try { - final Class<?> klass = Class.forName(className, true, mContext.getClassLoader()); - if (CursorTransformation.class.isAssignableFrom(klass)) { - final Constructor<?> c = klass.getDeclaredConstructor(Context.class); + final Class<?> klas = Class.forName(className, true, mContext.getClassLoader()); + if (CursorTransformation.class.isAssignableFrom(klas)) { + final Constructor<?> c = klas.getDeclaredConstructor(Context.class); transformation = (CursorTransformation) c.newInstance(mContext); } } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + className, e); + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + className, e); + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + className, e); + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } catch (InstantiationException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + className, e); + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + - mContext.getResources().getResourceEntryName(mId) + ": " + className, e); + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } } a.recycle(); - + if (transformation == null) { throw new IllegalArgumentException("A transform item in " + resources.getResourceEntryName(mId) + " must have a 'withClass' or " + @@ -877,7 +892,6 @@ public class Adapters { * of a SimpleCursorAdapter. The main difference is the ability to handle CursorBinders. */ private static class XmlCursorAdapter extends SimpleCursorAdapter implements ManagedAdapter { - private final Context mContext; private String mUri; private final String mSelection; private final String[] mSelectionArgs; @@ -911,14 +925,14 @@ public class Adapters { mBinders[i] = binder; } } - + @Override public void bindView(View view, Context context, Cursor cursor) { final int count = mTo.length; final int[] from = mFrom; final int[] to = mTo; final CursorBinder[] binders = mBinders; - + for (int i = 0; i < count; i++) { final View v = view.findViewById(to[i]); if (v != null) { @@ -926,7 +940,7 @@ public class Adapters { } } } - + public void load() { if (mUri != null) { mLoadTask = new QueryTask().execute(); @@ -984,16 +998,16 @@ public class Adapters { /** * An expression transformation is a simple template based replacement utility. - * In an expression, each segment of the form <code>{(^[}]+)}</code> is replaced + * In an expression, each segment of the form <code>{([^}]+)}</code> is replaced * with the value of the column of name $1. */ private static class ExpressionTransformation extends CursorTransformation { private final ExpressionNode mFirstNode = new ConstantExpressionNode(""); private final StringBuilder mBuilder = new StringBuilder(); - + public ExpressionTransformation(Context context, String expression) { super(context); - + parse(expression); } @@ -1033,7 +1047,7 @@ public class Adapters { public String transform(Cursor cursor, int columnIndex) { final StringBuilder builder = mBuilder; builder.delete(0, builder.length()); - + ExpressionNode node = mFirstNode; // Skip the first node while ((node = node.next) != null) { @@ -1042,13 +1056,13 @@ public class Adapters { return builder.toString(); } - + static abstract class ExpressionNode { public ExpressionNode next; public abstract String asString(Cursor cursor); } - + static class ConstantExpressionNode extends ExpressionNode { private final String mConstant; @@ -1061,7 +1075,7 @@ public class Adapters { return mConstant; } } - + static class ColumnExpressionNode extends ExpressionNode { private final String mColumnName; private Cursor mSignature; @@ -1134,8 +1148,12 @@ public class Adapters { @Override public boolean bind(View view, Cursor cursor, int columnIndex) { - ((TextView) view).setText(mTransformation.transform(cursor, columnIndex)); - return true; + if (view instanceof TextView) { + final String text = mTransformation.transform(cursor, columnIndex); + ((TextView) view).setText(text); + return true; + } + return false; } } @@ -1149,8 +1167,25 @@ public class Adapters { @Override public boolean bind(View view, Cursor cursor, int columnIndex) { - final byte[] data = cursor.getBlob(columnIndex); - ((ImageView) view).setImageBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)); + if (view instanceof ImageView) { + final byte[] data = cursor.getBlob(columnIndex); + ((ImageView) view).setImageBitmap(BitmapFactory.decodeByteArray(data, 0, + data.length)); + return true; + } + return false; + } + } + + private static class TagBinder extends CursorBinder { + public TagBinder(Context context, CursorTransformation transformation) { + super(context, transformation); + } + + @Override + public boolean bind(View view, Cursor cursor, int columnIndex) { + final String text = mTransformation.transform(cursor, columnIndex); + view.setTag(text); return true; } } @@ -1165,9 +1200,12 @@ public class Adapters { @Override public boolean bind(View view, Cursor cursor, int columnIndex) { - ((ImageView) view).setImageURI(Uri.parse( - mTransformation.transform(cursor, columnIndex))); - return true; + if (view instanceof ImageView) { + ((ImageView) view).setImageURI(Uri.parse( + mTransformation.transform(cursor, columnIndex))); + return true; + } + return false; } } @@ -1181,11 +1219,14 @@ public class Adapters { @Override public boolean bind(View view, Cursor cursor, int columnIndex) { - final int resource = mTransformation.transformToResource(cursor, columnIndex); - if (resource == 0) return false; + if (view instanceof ImageView) { + final int resource = mTransformation.transformToResource(cursor, columnIndex); + if (resource == 0) return false; - ((ImageView) view).setImageResource(resource); - return true; + ((ImageView) view).setImageResource(resource); + return true; + } + return false; } } } diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 2313f4c..5394530 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -81,7 +81,7 @@ public class BitmapFactory { /** * The pixel density to use for the bitmap. This will always result * in the returned bitmap having a density set for it (see - * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)). In addition, + * {@link Bitmap#setDensity(int) Bitmap.setDensity(int))}. In addition, * if {@link #inScaled} is set (which it is by default} and this * density does not match {@link #inTargetDensity}, then the bitmap * will be scaled to the target density before being returned. @@ -507,9 +507,7 @@ public class BitmapFactory { * * @param is The input stream that holds the raw data to be decoded into a * bitmap. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) + * @return The decoded bitmap, or null if the image data could not be decoded. */ public static Bitmap decodeStream(InputStream is) { return decodeStream(is, null, null); |
