Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 03f02927 authored by Gilles Debunne's avatar Gilles Debunne
Browse files

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
parent 84d000e3
Loading
Loading
Loading
Loading
+139 −0
Original line number Diff line number Diff line
@@ -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"
>
+4 −5
Original line number Diff line number Diff line
@@ -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;

+436 −0
Original line number Diff line number Diff line
/*
 * 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();
            }
        }
    }
}
+148 −107

File changed.

Preview size limit exceeded, changes collapsed.

+2 −4
Original line number Diff line number Diff line
@@ -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);