upload android base code part7

This commit is contained in:
August 2018-08-08 18:09:17 +08:00
parent 4e516ec6ed
commit 841ae54672
25229 changed files with 1709508 additions and 0 deletions

View file

@ -0,0 +1,20 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := samples
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := xmladapters
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SDK_VERSION := 8
LOCAL_DEX_PREOPT := false
include $(BUILD_PACKAGE)
# Use the following include to make our test apk.
#include $(call all-makefiles-under,$(LOCAL_PATH))

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.xmladapters">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application android:label="@string/app_name">
<activity android:name="ContactsListActivity"
android:label="@string/contacts_list_activity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="PhotosListActivity"
android:label="@string/photos_list_activity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="RssReaderActivity"
android:label="@string/rss_reader_activity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider android:name="XmlDocumentProvider" android:authorities="xmldocument" />
</application>
</manifest>

View file

@ -0,0 +1,31 @@
<p>This sample demonstrates the use of XML adapters.</p>
<p>An XML Adapter is an XML file which defines the bindings between the data
retrieved from a
<a href="../../../reference/android/content/ContentProvider.html"><code>ContentProvider</code></a>
and the different views of a layout. They are provided by the
<a href="../../../reference/android/widget/Adapters.html"><code>Adapters</code></a>
class.</p>
Three list activities are provided which illustrate this:
<ul>
<li><a href="src/com/example/android/xmladapters/ContactsListActivity.html"><strong>
ContactsListActivity</strong></a> uses the device's Contacts provider as its input source.
Contacts with a phone number are displayed, their photo being retrieved by a dedicated
<a href="src/com/example/android/xmladapters/ContactPhotoBinder.html"><code>ContactPhotoBinder
</code></a>.</li>
<li><a href="src/com/example/android/xmladapters/PhotosListActivity.html"><strong>
PhotosListActivity</strong></a> retrieves an RSS photo feed and displays the images and their
titles. The images are downloaded from the URL found in the feed using the
<a href="src/com/example/android/xmladapters/ImageDownloader.html"><code>ImageDownloader</code>
</a> helper class.</li>
<li><a href="src/com/example/android/xmladapters/RssReaderActivity.html"><strong>
RssReaderActivity</strong></a> also displays items extracted from an RSS feed. An additional
<a href="src/com/example/android/xmladapters/UrlIntentListener.html"><code>UrlIntentListener
</code></a> is used to open a browser when one of the news item is tapped.</li>
</ul>
<img alt="XmlPhotosAdapter" src="../images/XmlPhotosAdapter.png" />
<img alt="XmlRssReader" src="../images/XmlRssReader.png" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,003 B

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight">
<TextView
android:id="@+id/name"
android:layout_width="0px"
android:layout_weight="1.0"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:drawablePadding="6dip"
android:paddingLeft="6dip"
android:paddingRight="6dip" />
<ImageView
android:id="@+id/star"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/no_contacts"
android:visibility="gone" />
</merge>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight">
<ImageView
android:id="@+id/photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="6dip"
android:paddingTop="4dip"
android:paddingBottom="4dip" />
<TextView
android:id="@+id/title"
android:layout_width="0px"
android:layout_weight="1.0"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:paddingRight="6dip" />
</LinearLayout>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/no_photos"
android:visibility="gone" />
</merge>

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:minHeight="?android:attr/listPreferredItemHeight">
<TextView
android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_weight="1.0"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="left"
android:paddingLeft="6dip"
android:paddingRight="6dip" />
<TextView
android:id="@+id/date"
android:layout_width="fill_parent"
android:layout_weight="1.0"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="left"
android:paddingLeft="6dip"
android:paddingRight="6dip" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:minHeight="?android:attr/listPreferredItemHeight">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:paddingLeft="6dip" />
<TextView
android:id="@+id/description"
android:layout_width="fill_parent"
android:layout_weight="1.0"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="left"
android:paddingLeft="6dip"
android:paddingRight="6dip" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/no_rss_feed"
android:visibility="gone" />
</merge>

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<resources>
<!-- Adapter used to bind cursors. -->
<declare-styleable name="CursorAdapter">
<!-- URI to get the cursor from. Optional. -->
<attr name="uri" format="string" />
<!-- Selection statement for the query. Optional. -->
<attr name="selection" format="string" />
<!-- Sort order statement for the query. Optional. -->
<attr name="sortOrder" format="string" />
<!-- Layout resource used to display each row from the cursor. Mandatory. -->
<attr name="layout" format="reference" />
</declare-styleable>
<!-- Attributes used in bind items for XML cursor adapters. -->
<declare-styleable name="CursorAdapter_BindItem">
<!-- The name of the column to bind from. Mandatory. -->
<attr name="from" format="string" />
<!-- The resource id of the view to bind to. Mandatory. -->
<attr name="to" format="reference" />
<!-- The type of binding. If this value is not specified, the type will be
inferred from the type of the "to" target view. Mandatory.
The type can be one of:
<ul>
<li>string, The content of the column is interpreted as a string.</li>
<li>image, The content of the column is interpreted as a blob describing an image.</li>
<li>image-uri, The content of the column is interpreted as a URI to an image.</li>
<li>drawable, The content of the column is interpreted as a resource id to a drawable.</li>
<li>A fully qualified class name, corresponding to an implementation of
android.widget.Adapters.CursorBinder.</li>
</ul>
-->
<attr name="as" format="string" />
</declare-styleable>
<!-- Attributes used in select items for XML cursor adapters.-->
<declare-styleable name="CursorAdapter_SelectItem">
<!-- The name of the column to select. Mandatory. -->
<attr name="column" format="string" />
</declare-styleable>
<!-- Attributes used to map values to new values in XML cursor adapters' bind items. -->
<declare-styleable name="CursorAdapter_MapItem">
<!-- The original value from the column. Mandatory. -->
<attr name="fromValue" format="string" />
<!-- The new value from the column. Mandatory. -->
<attr name="toValue" format="string" />
</declare-styleable>
<!-- Attributes used to map values to new values in XML cursor adapters' bind items. -->
<declare-styleable name="CursorAdapter_TransformItem">
<!-- The transformation expression. Mandatory if "withClass" is not specified. -->
<attr name="withExpression" format="string" />
<!-- The transformation class, an implementation of
android.widget.Adapters.CursorTransformation. Mandatory if "withExpression"
is not specified. -->
<attr name="withClass" format="string" />
</declare-styleable>
</resources>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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" BASI
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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Xml Adapters</string>
<string name="contacts_list_activity">Xml Contacts Adapter</string>
<string name="photos_list_activity">Xml Photos Adapter</string>
<string name="rss_reader_activity">Xml RSS Reader</string>
<string name="no_contacts">No contacts available</string>
<string name="no_photos">Loading photos...</string>
<string name="no_rss_feed">Loading RSS feed...</string>
</resources>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example.android.xmladapters"
app:uri="content://com.android.contacts/contacts"
app:selection="has_phone_number=1"
app:layout="@layout/contact_item">
<bind app:from="display_name" app:to="@id/name" app:as="string" />
<bind app:from="starred" app:to="@id/star" app:as="drawable">
<map app:fromValue="0" app:toValue="@android:drawable/star_big_off" />
<map app:fromValue="1" app:toValue="@android:drawable/star_big_on" />
</bind>
<bind app:from="_id" app:to="@id/name" app:as="com.example.android.xmladapters.ContactPhotoBinder" />
</cursor-adapter>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example.android.xmladapters"
app:selection="/feed/entry"
app:layout="@layout/photo_item">
<bind app:from="/summary" app:to="@id/title" app:as="string" />
<bind app:from="/media:group/media:thumbnail\@url" app:to="@id/photo"
app:as="com.example.android.xmladapters.UrlImageBinder" />
</cursor-adapter>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example.android.xmladapters"
app:selection="/rss/channel/item"
app:layout="@layout/rss_feed_item">
<bind app:from="/title" app:to="@id/title" app:as="string" />
<bind app:from="/media:content@url" app:to="@id/image" app:as="com.example.android.xmladapters.UrlImageBinder"/>
<bind app:from="/media:description" app:to="@id/description" app:as="string" />
<bind app:from="/guid" app:to="@id/item_layout" app:as="tag" />
<bind app:from="/pubDate" app:to="@id/date" app:as="string">
<transform app:withExpression="Published on {/pubDate}." />
</bind>
</cursor-adapter>

View file

@ -0,0 +1,96 @@
/*
* 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 com.example.android.xmladapters;
import android.content.ContentUris;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.ContactsContract;
import android.view.View;
import android.widget.TextView;
import java.io.InputStream;
import java.util.HashMap;
/**
* This custom cursor binder is used by the adapter defined in res/xml to
* bind contacts photos to their respective list item. This binder simply
* queries a contact's photo based on the contact's id and sets the
* photo as a compound drawable on the TextView used to display the contact's
* name.
*/
public class ContactPhotoBinder extends Adapters.CursorBinder {
private static final int PHOTO_SIZE_DIP = 54;
private final Drawable mDefault;
private final HashMap<Long, Drawable> mCache;
private final Resources mResources;
private final int mPhotoSize;
public ContactPhotoBinder(Context context, Adapters.CursorTransformation transformation) {
super(context, transformation);
mResources = mContext.getResources();
// Default picture used when a contact does not provide one
mDefault = mResources.getDrawable(R.drawable.ic_contact_picture);
// Cache used to avoid re-querying contacts photos every time
mCache = new HashMap<Long, Drawable>();
// Compute the size of the photo based on the display's density
mPhotoSize = (int) (PHOTO_SIZE_DIP * mResources.getDisplayMetrics().density + 0.5f);
}
@Override
public boolean bind(View view, Cursor cursor, int columnIndex) {
final long id = cursor.getLong(columnIndex);
// First check whether we have already cached the contact's photo
Drawable d = mCache.get(id);
if (d == null) {
// If the photo wasn't in the cache, ask the contacts provider for
// an input stream we can use to load the photo
Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
InputStream stream = ContactsContract.Contacts.openContactPhotoInputStream(
mContext.getContentResolver(), uri);
// Creates the drawable for the contact's photo or use our fallback drawable
if (stream != null) {
// decoding the bitmap could be done in a worker thread too.
Bitmap bitmap = BitmapFactory.decodeStream(stream);
d = new BitmapDrawable(mResources, bitmap);
} else {
d = mDefault;
}
d.setBounds(0, 0, mPhotoSize, mPhotoSize);
((TextView) view).setCompoundDrawables(d, null, null, null);
// Remember the photo associated with this contact
mCache.put(id, d);
} else {
((TextView) view).setCompoundDrawables(d, null, null, null);
}
return true;
}
}

View file

@ -0,0 +1,37 @@
/*
* 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 com.example.android.xmladapters;
import android.app.ListActivity;
import android.os.Bundle;
/**
* This activity demonstrates how to create a complex UI using a ListView
* and an adapter defined in XML.
*
* The following activity shows a list of contacts, their starred status
* and their photos, using the adapter defined in res/xml.
*/
public class ContactsListActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.contacts_list);
setListAdapter(Adapters.loadAdapter(this, R.xml.contacts));
}
}

View file

@ -0,0 +1,367 @@
/*
* 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 com.example.android.xmladapters;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* This helper class download images from the Internet and binds those with the provided ImageView.
*
* <p>It requires the INTERNET permission, which should be added to your application's manifest
* file.</p>
*
* A local cache of downloaded images is maintained internally to improve performance.
*/
public class ImageDownloader {
private static final String LOG_TAG = "ImageDownloader";
private static final int HARD_CACHE_CAPACITY = 40;
private static final int DELAY_BEFORE_PURGE = 30 * 1000; // in milliseconds
// Hard cache, with a fixed maximum capacity and a life duration
private final static HashMap<String, Bitmap> sHardBitmapCache =
new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {
private static final long serialVersionUID = -7190622541619388252L;
@Override
protected boolean removeEldestEntry(Map.Entry<String, Bitmap> eldest) {
if (size() > HARD_CACHE_CAPACITY) {
// Entries push-out of hard reference cache are transferred to soft reference cache
sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
return true;
} else {
return false;
}
}
};
// Soft cache for bitmap kicked out of hard cache
private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache =
new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);
private final Handler purgeHandler = new Handler();
private final Runnable purger = new Runnable() {
public void run() {
clearCache();
}
};
/**
* Download the specified image from the Internet and binds it to the provided ImageView. The
* binding is immediate if the image is found in the cache and will be done asynchronously
* otherwise. A null bitmap will be associated to the ImageView if an error occurs.
*
* @param url The URL of the image to download.
* @param imageView The ImageView to bind the downloaded image to.
*/
public void download(String url, ImageView imageView) {
download(url, imageView, null);
}
/**
* Same as {@link #download(String, ImageView)}, with the possibility to provide an additional
* cookie that will be used when the image will be retrieved.
*
* @param url The URL of the image to download.
* @param imageView The ImageView to bind the downloaded image to.
* @param cookie A cookie String that will be used by the http connection.
*/
public void download(String url, ImageView imageView, String cookie) {
resetPurgeTimer();
Bitmap bitmap = getBitmapFromCache(url);
if (bitmap == null) {
forceDownload(url, imageView, cookie);
} else {
cancelPotentialDownload(url, imageView);
imageView.setImageBitmap(bitmap);
}
}
/*
* Same as download but the image is always downloaded and the cache is not used.
* Kept private at the moment as its interest is not clear.
private void forceDownload(String url, ImageView view) {
forceDownload(url, view, null);
}
*/
/**
* Same as download but the image is always downloaded and the cache is not used.
* Kept private at the moment as its interest is not clear.
*/
private void forceDownload(String url, ImageView imageView, String cookie) {
// State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys.
if (url == null) {
imageView.setImageDrawable(null);
return;
}
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url, cookie);
}
}
/**
* Clears the image cache used internally to improve performance. Note that for memory
* efficiency reasons, the cache will automatically be cleared after a certain inactivity delay.
*/
public void clearCache() {
sHardBitmapCache.clear();
sSoftBitmapCache.clear();
}
private void resetPurgeTimer() {
purgeHandler.removeCallbacks(purger);
purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
}
/**
* Returns true if the current download has been canceled or if there was no download in
* progress on this image view.
* Returns false if the download in progress deals with the same url. The download is not
* stopped in that case.
*/
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
// The same URL is already being downloaded.
return false;
}
}
return true;
}
/**
* @param imageView Any imageView
* @return Retrieve the currently active download task (if any) associated with this imageView.
* null if there is no such task.
*/
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
/**
* @param url The URL of the image that will be retrieved from the cache.
* @return The cached bitmap or null if it was not found.
*/
private Bitmap getBitmapFromCache(String url) {
// First try the hard reference cache
synchronized (sHardBitmapCache) {
final Bitmap bitmap = sHardBitmapCache.get(url);
if (bitmap != null) {
// Bitmap found in hard cache
// Move element to first position, so that it is removed last
sHardBitmapCache.remove(url);
sHardBitmapCache.put(url, bitmap);
return bitmap;
}
}
// Then try the soft reference cache
SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
if (bitmapReference != null) {
final Bitmap bitmap = bitmapReference.get();
if (bitmap != null) {
// Bitmap found in soft cache
return bitmap;
} else {
// Soft reference has been Garbage Collected
sSoftBitmapCache.remove(url);
}
}
return null;
}
/**
* The actual AsyncTask that will asynchronously download the image.
*/
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private static final int IO_BUFFER_SIZE = 4 * 1024;
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
/**
* Actual download method.
*/
@Override
protected Bitmap doInBackground(String... params) {
final AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
url = params[0];
final HttpGet getRequest = new HttpGet(url);
String cookie = params[1];
if (cookie != null) {
getRequest.setHeader("cookie", cookie);
}
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w("ImageDownloader", "Error " + statusCode +
" while retrieving bitmap from " + url);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = entity.getContent();
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
outputStream = new BufferedOutputStream(dataStream, IO_BUFFER_SIZE);
copy(inputStream, outputStream);
outputStream.flush();
final byte[] data = dataStream.toByteArray();
final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
// FIXME : Should use BitmapFactory.decodeStream(inputStream) instead.
//final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
entity.consumeContent();
}
}
} catch (IOException e) {
getRequest.abort();
Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
} catch (IllegalStateException e) {
getRequest.abort();
Log.w(LOG_TAG, "Incorrect URL: " + url);
} catch (Exception e) {
getRequest.abort();
Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
} finally {
if (client != null) {
client.close();
}
}
return null;
}
/**
* Once the image is downloaded, associates it to the imageView
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
// Add bitmap to cache
if (bitmap != null) {
synchronized (sHardBitmapCache) {
sHardBitmapCache.put(url, bitmap);
}
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with it
if (this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
}
}
}
public void copy(InputStream in, OutputStream out) throws IOException {
byte[] b = new byte[IO_BUFFER_SIZE];
int read;
while ((read = in.read(b)) != -1) {
out.write(b, 0, read);
}
}
}
/**
* A fake Drawable that will be attached to the imageView while the download is in progress.
*
* <p>Contains a reference to the actual download task, so that a download task can be stopped
* if a new binding is required, and makes sure that only the last started download process can
* bind its result, independently of the download finish order.</p>
*/
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(Color.BLACK);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
}

View file

@ -0,0 +1,39 @@
/*
* 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 com.example.android.xmladapters;
import android.app.ListActivity;
import android.net.Uri;
import android.os.Bundle;
/**
* This activity uses a custom cursor adapter which fetches a XML photo feed and parses the XML to
* extract the images' URL and their title.
*/
public class PhotosListActivity extends ListActivity {
private static final String PICASA_FEED_URL =
"http://picasaweb.google.com/data/feed/api/featured?max-results=50&thumbsize=144c";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.photos_list);
setListAdapter(Adapters.loadCursorAdapter(this, R.xml.photos,
"content://xmldocument/?url=" + Uri.encode(PICASA_FEED_URL)));
}
}

View file

@ -0,0 +1,43 @@
/*
* 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 com.example.android.xmladapters;
import android.app.ListActivity;
import android.net.Uri;
import android.os.Bundle;
import android.widget.AdapterView.OnItemClickListener;
/**
* This example demonstrate the creation of a simple RSS feed reader using the XML adapter syntax.
* The different elements of the feed are extracted using an {@link XmlDocumentProvider} and are
* binded to the different views. An {@link OnItemClickListener} is also added, which will open a
* browser on the associated news item page.
*/
public class RssReaderActivity extends ListActivity {
private static final String FEED_URI = "http://feeds.nytimes.com/nyt/rss/HomePage";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.rss_feeds_list);
setListAdapter(Adapters.loadCursorAdapter(this, R.xml.rss_feed,
"content://xmldocument/?url=" + Uri.encode(FEED_URI)));
getListView().setOnItemClickListener(new UrlIntentListener());
}
}

View file

@ -0,0 +1,47 @@
/*
* 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 com.example.android.xmladapters;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.widget.ImageView;
/**
* This CursorBinder binds the provided image URL to an ImageView by downloading the image from the
* Internet.
*/
public class UrlImageBinder extends Adapters.CursorBinder {
private final ImageDownloader imageDownloader;
public UrlImageBinder(Context context, Adapters.CursorTransformation transformation) {
super(context, transformation);
imageDownloader = new ImageDownloader();
}
@Override
public boolean bind(View view, Cursor cursor, int columnIndex) {
if (view instanceof ImageView) {
final String url = mTransformation.transform(cursor, columnIndex);
imageDownloader.download(url, (ImageView) view);
return true;
}
return false;
}
}

View file

@ -0,0 +1,40 @@
/*
* 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 com.example.android.xmladapters;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
/**
* A listener which expects a URL as a tag of the view it is associated with. It then opens the URL
* in the browser application.
*/
public class UrlIntentListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final String url = view.getTag().toString();
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Context context = parent.getContext();
context.startActivity(intent);
}
}

View file

@ -0,0 +1,475 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.xmladapters;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.net.http.AndroidHttpClient;
import android.text.TextUtils;
import android.util.Log;
import android.widget.CursorAdapter;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.BitSet;
import java.util.List;
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="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 {
String authority = resourceUri.getAuthority();
Resources r;
if (TextUtils.isEmpty(authority)) {
throw new FileNotFoundException("No authority: " + resourceUri);
} else {
try {
r = getContext().getPackageManager().getResourcesForApplication(authority);
} catch (NameNotFoundException ex) {
throw new FileNotFoundException("No package found for authority: " + resourceUri);
}
}
List<String> path = resourceUri.getPathSegments();
if (path == null) {
throw new FileNotFoundException("No path: " + resourceUri);
}
int len = path.size();
int id;
if (len == 1) {
try {
id = Integer.parseInt(path.get(0));
} catch (NumberFormatException e) {
throw new FileNotFoundException("Single path segment is not a resource ID: " + resourceUri);
}
} else if (len == 2) {
id = r.getIdentifier(path.get(1), path.get(0), authority);
} else {
throw new FileNotFoundException("More than two path segments: " + resourceUri);
}
if (id == 0) {
throw new FileNotFoundException("No resource found for: " + resourceUri);
}
return r.getXml(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();
}
}
}
}