summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGilles Debunne <debunne@google.com>2010-06-09 14:11:45 -0700
committerGilles Debunne <debunne@google.com>2010-06-23 10:43:00 -0700
commit03f0292744094ec107ffce71301c394503a31ded (patch)
treecd01b60e4ed28aef6b5e9abc7b86d90dcaba83e1
parent84d000e3c4d8883afec1e47662f719c6119cfefc (diff)
downloadframeworks_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.xml139
-rw-r--r--core/java/android/content/ContentResolver.java9
-rw-r--r--core/java/android/content/XmlDocumentProvider.java436
-rw-r--r--core/java/android/widget/Adapters.java255
-rw-r--r--graphics/java/android/graphics/BitmapFactory.java6
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">
+ * &lt;provider android:name="android.content.XmlDocumentProvider" android:authorities="xmldocument" /&gt;
+ * </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">
+ * &lt;library&gt;
+ * &lt;book id="EH94"&gt;
+ * &lt;title&gt;The Old Man and the Sea&lt;/title&gt;
+ * &lt;author&gt;Ernest Hemingway&lt;/author&gt;
+ * &lt;/book&gt;
+ * &lt;book id="XX10"&gt;
+ * &lt;title&gt;The Arabian Nights: Tales of 1,001 Nights&lt;/title&gt;
+ * &lt;/book&gt;
+ * &lt;no-id&gt;
+ * &lt;book&gt;
+ * &lt;title&gt;Animal Farm&lt;/title&gt;
+ * &lt;author&gt;George Orwell&lt;/author&gt;
+ * &lt;/book&gt;
+ * &lt;/no-id&gt;
+ * &lt;/library&gt;
+ * </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>&lt;bind /&gt;</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>&lt;bind /&gt;</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);