diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
| commit | 5c11852110eeb03dc5a69111354b383f98d15336 (patch) | |
| tree | 7b26fde6effb80c241f28fc3e620016e7f86418e /samples/MySampleRss | |
| download | android_development-5c11852110eeb03dc5a69111354b383f98d15336.tar.gz android_development-5c11852110eeb03dc5a69111354b383f98d15336.tar.bz2 android_development-5c11852110eeb03dc5a69111354b383f98d15336.zip | |
Initial Contribution
Diffstat (limited to 'samples/MySampleRss')
17 files changed, 1728 insertions, 0 deletions
diff --git a/samples/MySampleRss/Android.mk b/samples/MySampleRss/Android.mk new file mode 100644 index 000000000..fea9729a2 --- /dev/null +++ b/samples/MySampleRss/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := samples + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := MyRSSReader + +LOCAL_SDK_VERSION := current + +##include $(BUILD_PACKAGE) diff --git a/samples/MySampleRss/AndroidManifest.xml b/samples/MySampleRss/AndroidManifest.xml new file mode 100755 index 000000000..575c81e29 --- /dev/null +++ b/samples/MySampleRss/AndroidManifest.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2007 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. +--> + +<!-- Declare the contents of this Android application. The namespace + attribute brings in the Android platform namespace, and the package + supplies a unique name for the application. When writing your + own application, the package name must be changed from "com.example.*" + to come from a domain that you own or have control over. --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.codelab.rssexample"> + <application android:name="MyRssReader" android:label="My Rss Reader"> + <activity> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name="MyRssReader2" android:label="My Rss Reader V2"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name="MyRssReader3" android:label="@string/reader_3_label"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name="MyRssReader4" android:label="@string/reader_4_label"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name="MyRssReader5" android:label="@string/reader_5_label"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name="AddRssItem" android:label="@string/add_item_label" android:theme="@android:style/Theme.Dialog"/> + <provider android:name="RssContentProvider" android:authorities="my_rss_item" android:multiprocess="true" /> + <service android:name="RssService" android:process=":myrss"/> + </application> +</manifest> + + diff --git a/samples/MySampleRss/res/drawable/rss_icon.PNG b/samples/MySampleRss/res/drawable/rss_icon.PNG Binary files differnew file mode 100755 index 000000000..214a38db3 --- /dev/null +++ b/samples/MySampleRss/res/drawable/rss_icon.PNG diff --git a/samples/MySampleRss/res/layout/add_item.xml b/samples/MySampleRss/res/layout/add_item.xml new file mode 100644 index 000000000..8191bf582 --- /dev/null +++ b/samples/MySampleRss/res/layout/add_item.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2007 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. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="fill_parent" + android:layout_width="fill_parent"> + + <TextView android:id="@+id/title_label" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_alignParentTop="true" + android:layout_alignParentLeft="true" + android:text="@string/add_item_title"/> + <EditText android:id="@+id/title_textbox" + android:layout_height="wrap_content" + android:layout_width="fill_parent" + android:layout_toRightOf="@id/title_label"/> + <EditText android:id="@+id/url_textbox" + android:layout_height="wrap_content" + android:layout_width="fill_parent" + android:layout_below="@id/title_textbox" + android:layout_alignLeft="@id/title_textbox"/> + + <!-- Note that we declare url_label *after* url_textbox in this file + because it references url_textbox, and a resource file is + evaluated sequentially. --> + <TextView android:id="@+id/url_label" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_toRightOf="url_textbox" + android:layout_alignTop="@id/url_textbox" + android:text="@string/add_item_url"/> + <Button android:id="@+id/submit" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignParentBottom="true" + android:text="@string/submit"/> + <Button android:id="@+id/cancel" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_toLeftOf="@id/submit" + android:layout_alignTop="@id/submit" + android:text="@string/cancel"/> +</RelativeLayout> + diff --git a/samples/MySampleRss/res/layout/list_element.xml b/samples/MySampleRss/res/layout/list_element.xml new file mode 100644 index 000000000..4255d8654 --- /dev/null +++ b/samples/MySampleRss/res/layout/list_element.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2007 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. +--> + + <TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list_item" + android:layout_height="wrap_content" + android:layout_width="wrap_content"/> diff --git a/samples/MySampleRss/res/layout/main_screen.xml b/samples/MySampleRss/res/layout/main_screen.xml new file mode 100755 index 000000000..5292bb273 --- /dev/null +++ b/samples/MySampleRss/res/layout/main_screen.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2007 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_height="fill_parent" + android:layout_width="fill_parent" + android:orientation="vertical" + android:scrollbars="vertical"> + + <ListView android:id="@+id/rssListView" + android:layout_height="0px" + android:layout_width="fill_parent" + android:layout_weight="1" + android:background="#00CC00"/> + + <WebView android:id="@+id/rssWebView" + android:background="#77CC0000" + android:layout_height="0px" + android:layout_width="fill_parent" + android:layout_weight="1"/> + +</LinearLayout> diff --git a/samples/MySampleRss/res/layout/main_screen2.xml b/samples/MySampleRss/res/layout/main_screen2.xml new file mode 100644 index 000000000..36b0af4dd --- /dev/null +++ b/samples/MySampleRss/res/layout/main_screen2.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2007 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_height="fill_parent" + android:layout_width="fill_parent" + android:orientation="vertical" + android:scrollbars="vertical"> + + <ListView android:id="@+id/rssListView" + android:layout_height="0px" + android:layout_width="fill_parent" + android:layout_weight="1" + android:background="#9900FF00"/> + + <WebView android:id="@+id/rssWebView" + android:layout_height="0px" + android:layout_width="fill_parent" + android:layout_weight="2"/> +</LinearLayout> diff --git a/samples/MySampleRss/res/values/strings.xml b/samples/MySampleRss/res/values/strings.xml new file mode 100644 index 000000000..86559955e --- /dev/null +++ b/samples/MySampleRss/res/values/strings.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources> + <string name="menu_option_start">Start RSS Service</string> + <string name="menu_option_stop">Stop RSS Service</string> + <string name="menu_option_add">Add New Feed</string> + <string name="menu_option_delete">Delete Feed</string> + <string name="menu_option_update">Update All Feeds</string> + <string name="add_item_title">Title</string> + <string name="add_item_url">URL</string> + <string name="submit">Submit</string> + <string name="cancel">Cancel</string> + <string name="reader_3_label">MyRssReader3</string> + <string name="reader_4_label">MyRssReader4</string> + <string name="reader_5_label">MyRssReader5</string> + <string name="add_item_label">Add a new Feed</string> +</resources>
\ No newline at end of file diff --git a/samples/MySampleRss/src/com/example/codelab/rssexample/AddRssItem.java b/samples/MySampleRss/src/com/example/codelab/rssexample/AddRssItem.java new file mode 100644 index 000000000..980eec6c1 --- /dev/null +++ b/samples/MySampleRss/src/com/example/codelab/rssexample/AddRssItem.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2007 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.codelab.rssexample; + +import android.app.Activity; +import android.content.Intent; +import android.view.View.OnClickListener; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; +import android.text.TextUtils; +import android.os.Bundle; + +/*** Form to add a new RSS feed. + It is a dialog form,**/ +public class AddRssItem extends Activity { + + // Button handler for Submit/Cancel. + // It is a dialog style form because it's declared as such + // in the manifest. + private OnClickListener mClickListener = new OnClickListener(){ + public void onClick(View v){ + if(v.getId() == R.id.submit){ + String title = ((TextView) findViewById(R.id.title_textbox)).getText().toString(); + String url = ((TextView) findViewById(R.id.url_textbox)).getText().toString(); + if(TextUtils.isEmpty(title) || TextUtils.isEmpty(url)){ + showAlert("Missing Values", + "You must specify both a title and a URL value", + "OK", + null, false, null); + return; + } + Intent res = new Intent("Accepted"); + res.putExtra(RssContentProvider.TITLE, title); + res.putExtra(RssContentProvider.URL, url); + res.putExtra(RssContentProvider.LAST_UPDATED, 0); + res.putExtra(RssContentProvider.CONTENT, "<html><body><h2>Not updated yet.</h2></body></html>"); + setResult(RESULT_OK, res); + } + else + setResult(RESULT_CANCELED, (new Intent()).setAction("Canceled" + v.getId())); + + finish(); + + } + }; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + setContentView(R.layout.add_item); + setTitle(getString(R.string.add_item_label)); + + Button btn = (Button) findViewById(R.id.cancel); + btn.setOnClickListener(mClickListener); + + btn = (Button) findViewById(R.id.submit); + btn.setOnClickListener(mClickListener); + } + +} diff --git a/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader.java b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader.java new file mode 100644 index 000000000..d30655436 --- /dev/null +++ b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2007 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.codelab.rssexample; + +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +//BEGIN_INCLUDE(1_1) +public class MyRssReader extends Activity { + /** Called with the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + + // Load screen layout. + setContentView(R.layout.main_screen); + +//END_INCLUDE(1_1) +//BEGIN_INCLUDE(1_2) + // Load some simple values into the ListView + mRssList = (ListView) findViewById(R.id.rssListView); + mRssList.setAdapter( + new ArrayAdapter<String>( + this, + R.layout.list_element, + new String[] { "Scientific American", "BBC", "The Onion", "Engadget" })); +//END_INCLUDE(1_2) +} + + // Store our state before we are potentially bumped from memory. + // We'd like to store the current ListView selection. + @Override + protected void onSaveInstanceState(Bundle outState){ + int index = mRssList.getSelectedItemIndex(); + if(index > -1){ + outState.putInteger("lastIndexItem", index); + } + } + + // Add our initial menu options. We will tweak this menu when it's loaded swap out + // "start service" or "stop service", depending on whether the service is currently running. + @Override + public boolean onCreateOptionsMenu(Menu menu){ + // Always call the superclass implementation to + // provide standard items. + super.onCreateOptionsMenu(menu); + + menu.add(0, 0, "Start RSS Service", null); + menu.add(0, 1, "Stop RSS Service", null); + menu.add(0, 2, "Add New Feed", null); + menu.add(0, 3, "Delete Feed", null); + menu.add(0, 4, "Update All Feeds", null); + + return true; + } + + // Toggle out start service/stop service depending on whether the service is running. + @Override + public boolean onPrepareOptionsMenu(Menu menu){ + return true; + } + + // Handle our menu clicks. + @Override + public boolean onOptionsItemSelected(Menu.Item item){ + switch (item.getId()) { + case 0: + showAlert(null, "You clicked 'start'!", "ok", null, false, null); + break; + case 1: + showAlert(null, "You clicked stop!", "ok", null, false, null); + break; + case 2: + showAlert(null, "You clicked 'Add'!", "ok", null, false, null); + break; + case 3: + showAlert(null, "You clicked 'Delete'!", "ok", null, false, null); + break; + case 4: + showAlert(null, "You clicked 'Update'!", "ok", null, false, null); + break; + default: + showAlert(null, "I have no idea what you clicked!", "ok", null, false, null); + break; + } + return true; + } + + ListView mRssList; +} diff --git a/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader2.java b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader2.java new file mode 100644 index 000000000..352167a32 --- /dev/null +++ b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader2.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2007 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.codelab.rssexample; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Typeface; +import android.os.Bundle; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +public class MyRssReader2 extends Activity{ + private ArrayList<RssItem> mFeeds = null; + ListView mRssList = null; + private Logger mLogger = Logger.getLogger("com.example.codelab.rssexample"); + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + + // Load screen layout. + setContentView(R.layout.main_screen2); + + // Populate our list + mFeeds = initializeList(); + mLogger.info("MyRssReader.onCreate-1 mFeeds value:" + mFeeds.size()); +// BEGIN_INCLUDE(2_2) + // Populate ArrayAdapter and bind it to ListView + mRssList = (ListView)findViewById(R.id.rssListView); + if(mRssList == null){ + // Note: Calling showAlert() would fail here because dialogs opened + // in onCreate won't be displayed properly, if at all. + mLogger.warning("MyRssReader.onCreate-2 -- Couldn't instantiate a ListView!"); + } + RssDataAdapter<RssItem> adap = new RssDataAdapter<RssItem>(this, R.layout.add_item, mFeeds); + if(adap == null){ + mLogger.warning("MyRssReader.onCreate-3 -- Couldn't instantiate RssDataAdapter!"); + } + if(mFeeds == null){ + mLogger.warning("MyRssReader.onCreate-4 -- Couldn't instantiate a ListView!"); + } + mRssList.setAdapter(adap); +// END_INCLUDE(2_2) + + mLogger.info("MyRssReader.onCreate-5 -- Loading preferences."); + // Set the last selected item. + // (icicle is only set if this is being restarted). + if(savedInstanceState != null && savedInstanceState.containsKey("lastIndexItem")) + { + Integer selectedItem = savedInstanceState.getInteger("lastIndexItem"); + if(selectedItem >= 0 && selectedItem < mRssList.getChildCount()){ + mRssList.setSelection(savedInstanceState.getInteger("lastIndexItem")); + } + mLogger.info("MyRssReader.onCreate-6 -- Last selected item:" + selectedItem); + } + } + + // Store our state before we are potentially bumped from memory. + // We'd like to store the current ListView selection. + @Override + protected void onSaveInstanceState(Bundle outState){ + int index = mRssList.getSelectedItemIndex(); + if(index > -1){ + outState.putInteger("lastIndexItem", index); + } + } + + + + // Add our initial menu options. We will tweak this menu when it's loaded swap out + // "start service" or "stop service", depending on whether the service is currently running. + @Override + public boolean onCreateOptionsMenu(Menu menu){ + // Always call the superclass implementation to + // provide standard items. + super.onCreateOptionsMenu(menu); + + menu.add(0, 0, "Start RSS Service", null); + menu.add(0, 1, "Stop RSS Service", null); + menu.add(0, 2, "Add New Feed", null); + menu.add(0, 3, "Delete Feed", null); + menu.add(0, 4, "Update All Feeds", null); + + return true; + } + + // Toggle out start service/stop service depending on whether the service is running. + @Override + public boolean onPrepareOptionsMenu(Menu menu){ + return true; + } + + // Handle our menu clicks. + @Override + public boolean onOptionsItemSelected(Menu.Item item){ + switch (item.getId()){ + case 0: + showAlert(null, "You clicked 'start'!", "ok", null, false, null); + break; + case 1: + showAlert(null, "You clicked stop!", "ok", null, false, null); + break; + case 2: + showAlert(null, "You clicked 'Add'!", "ok", null, false, null); + break; + case 3: + showAlert(null, "You clicked 'Delete'!", "ok", null, false, null); + break; + case 4: + showAlert(null, "You clicked 'Update'!", "ok", null, false, null); + break; + default: + showAlert(null, "I have no idea what you clicked!", "ok", null, false, null); + break; + } + return true; + } + + + // Our private ArrayAdapter implementation that returns a bold TextView for + // RSS items that are unread, or a normal TextView for items that have been read. +// BEGIN_INCLUDE(2_3) + private class RssDataAdapter<T> extends ArrayAdapter<T> { + public RssDataAdapter(Context context, int resource, List objects) { + super(context, resource, objects); + } +// END_INCLUDE(2_3) + // Here's our only important override--returning the list item. + @Override + public View getView(int position, View convertView, ViewGroup parent){ + TextView view = null; + // Get the item from the underlying array, + // Create a TextView wrapper, and change the typeface, if necessary. + RssItem item = (RssItem)this.getItem(position); + if(item != null) + { + view = new TextView(parent.getContext()); + view.setText(item.toString()); + + if(! item.hasBeenRead){ + Typeface type = view.getTypeface(); + view.setTypeface(Typeface.create(type, Typeface.BOLD_ITALIC)); + } + } + return view; + } + } + +//BEGIN_INCLUDE(2_1) + // Method to initialize our list of RSS items. + private ArrayList<RssItem> initializeList(){ + ArrayList<RssItem> list = new ArrayList<RssItem>(); + list.add(new RssItem("http://www.sciam.com/xml/sciam.xml", "Scientific American")); + list.add(new RssItem("http://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml", "BBC")); + list.add(new RssItem("http://www.theonion.com/content/feeds/daily.", "The Onion")); + list.add(new RssItem("http://feeds.engadget.com/weblogsinc/engadget", "Engadget")); + return list; + } +//END_INCLUDE(2_1) +} diff --git a/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader3.java b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader3.java new file mode 100644 index 000000000..1ffe33ede --- /dev/null +++ b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader3.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2007 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.codelab.rssexample; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Typeface; +import android.os.Bundle; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +public class MyRssReader3 extends Activity{ + + private ArrayList<RssItem> mFeeds; + ListView mRssList; + ArrayAdapter mAdap; + private static final int ADD_ELEMENT_REQUEST = 1; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + + // Load screen layout. + setContentView(R.layout.main_screen2); + + // Populate our list + mFeeds = initializeList(); + + // Populate ArrayAdapter and bind it to ListView + mRssList = (ListView)findViewById(R.id.rssListView); + mAdap = new RssDataAdapter<RssItem>(this, R.layout.list_element, mFeeds); + mRssList.setAdapter(mAdap); + + // Set the last selected item. + // (icicle is only set if this is being restarted). + if(savedInstanceState != null && savedInstanceState.containsKey("lastIndexItem")) + mRssList.setSelection(savedInstanceState.getInteger("lastIndexItem")); + } + + // Store our state before we are potentially bumped from memory. + // We'd like to store the current ListView selection. + @Override + protected void onSaveInstanceState(Bundle outState){ + int index = mRssList.getSelectedItemIndex(); + if(index > -1){ + outState.putInteger("lastIndexItem", index); + } + } + + + // Add our initial menu options. We will tweak this menu when it's loaded swap out + // "start service" or "stop service", depending on whether the service is currently running. + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + // Always call the superclass implementation to + // provide standard items. + super.onCreateOptionsMenu(menu); + + menu.add(0, 0, R.string.menu_option_start, null); + menu.add(0, 1, R.string.menu_option_stop, null); + menu.add(0, 2, R.string.menu_option_add, null); + menu.add(0, 3, R.string.menu_option_delete, null); + menu.add(0, 4, R.string.menu_option_update, null); + + return true; + } + + // Toggle out start service/stop service depending on whether the service is running. + @Override + public boolean onPrepareOptionsMenu(Menu menu){ + return true; + } + + // Handle our menu clicks. + @Override + public boolean onOptionsItemSelected(Menu.Item item){ + super.onOptionsItemSelected(item); + + switch (item.getId()){ + case 0: // Start service + showAlert(null, "You clicked 'start'!", "ok", null, false, null); + break; + case 1: // Stop service + showAlert(null, "You clicked stop!", "ok", null, false, null); + break; + case 2: // Add Item + Intent addIntent = new Intent(AddRssItem.class); + + // Use an ID so that if we create a "remove item" form we + // can tell which form is returning a value. + startActivityForResult(addIntent, ADD_ELEMENT_REQUEST); + break; + case 3: // Delete item. + if(mRssList.hasFocus()){ + Object selectedItem = mRssList.getSelectedItem(); + mAdap.removeObject(mRssList.getSelectedItem()); + } + break; + case 4: // Update all + showAlert(null, "You clicked 'Update'!", "ok", null, false, null); + break; + default: + showAlert(null, "I have no idea what you clicked!", "ok", null, false, null); + break; + } + return true; + } + + // Called by the "Add RSS Item" floating screen when it closes. + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data){ + if(resultCode == RESULT_OK){ + switch (requestCode){ + case ADD_ELEMENT_REQUEST: + RssItem newIt = new RssItem( + data.getStringExtra("url").toString(), + data.getStringExtra("title").toString()); + mAdap.addObject(newIt); + mRssList.setSelection(mRssList.getCount() - 1); + break; + default: + break; + } + } + } + + // Our private ArrayAdapter implementation that returns a bold TextView for + // RSS items that are unread, or a normal TextView for items that have been read. + private class RssDataAdapter<T> extends ArrayAdapter<T> { + public RssDataAdapter(Context context, int resource, List objects) { + super(context, resource, objects); + } + + // Here's our only important override--returning the list item. + @Override + public View getView(int position, View convertView, ViewGroup parent){ + + // Get the item from the underlying array, + // Create a TextView wrapper, and change the typeface, if necessary. + RssItem item = (RssItem)this.getItem(position); + TextView view = new TextView(parent.getContext()); + view.setText(item.toString()); + + if(! item.hasBeenRead){ + Typeface type = view.getTypeface(); + view.setTypeface(Typeface.create(type, Typeface.BOLD_ITALIC)); + } + return view; + } + } + + // Method to initialize our list of RSS items. + private ArrayList<RssItem> initializeList(){ + ArrayList<RssItem> list = new ArrayList<RssItem>(); + list.add(new RssItem("http://www.sciam.com/xml/sciam.xml", "Scientific American")); + list.add(new RssItem("http://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml", "BBC")); + list.add(new RssItem("http://feeds.theonion.com/theonion/daily", "The Onion")); + list.add(new RssItem("http://feeds.engadget.com/weblogsinc/engadget", "Engadget")); + return list; + } +} diff --git a/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader4.java b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader4.java new file mode 100644 index 000000000..bc37786f1 --- /dev/null +++ b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader4.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2007 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.codelab.rssexample; + +import android.app.Activity; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.graphics.Typeface; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.SimpleCursorAdapter; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; + + +public class MyRssReader4 extends Activity { + + ListView mRssList; + Cursor mCur; + RssCursorAdapter mAdap; + private static final int ADD_ELEMENT_REQUEST = 1; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + + // Load screen layout. + setContentView(R.layout.main_screen2); + + // Populate ArrayAdapter and bind it to ListView + mRssList = (ListView)findViewById(R.id.rssListView); + + mCur = managedQuery(RssContentProvider.CONTENT_URI, // Query for all items. + null, + null, + RssContentProvider.DEFAULT_SORT_ORDER); +// BEGIN_INCLUDE(4_1) + mAdap = new RssCursorAdapter( + this, + R.layout.list_element, // Our layout resource. + mCur, + new String[]{RssContentProvider.TITLE}, // Columns to retrieve. + new int[]{R.id.list_item}); // IDs of widgets to display + mRssList.setAdapter(mAdap); // the corresponding column. +// END_INCLUDE(4_1) + + // Set the last selected item. + // (icicle is only set if this is being restarted). + if(savedInstanceState != null && savedInstanceState.containsKey("lastIndexItem")){ + mRssList.setSelection(savedInstanceState.getInteger("lastIndexItem")); + } + } + + // Store our state before we are potentially bumped from memory. + // We'd like to store the current ListView selection. + @Override + protected void onSaveInstanceState(Bundle outState){ + int index = mRssList.getSelectedItemIndex(); + if(index > -1){ + outState.putInteger("lastIndexItem", index); + } + } + + + // Add our initial menu options. We will tweak this menu when it's loaded swap out + // "start service" or "stop service", depending on whether the service is currently running. + @Override + public boolean onCreateOptionsMenu(Menu menu){ + // Always call the superclass implementation to + // provide standard items. + super.onCreateOptionsMenu(menu); + + menu.add(0, 0, R.string.menu_option_start, null); + menu.add(0, 1, R.string.menu_option_stop, null); + menu.add(0, 2, R.string.menu_option_add, null); + menu.add(0, 3, R.string.menu_option_delete, null); + menu.add(0, 4, R.string.menu_option_update, null); + + return true; + } + + // Toggle out start service/stop service depending on whether the service is running. + @Override + public boolean onPrepareOptionsMenu(Menu menu){ + return true; + } + + // Handle our menu clicks. + @Override + public boolean onOptionsItemSelected(Menu.Item item){ + super.onOptionsItemSelected(item); + + switch (item.getId()){ + case 0: // Start service + showAlert(null, "You clicked 'start'!", "ok", null, false, null); + break; + case 1: // Stop service + showAlert(null, "You clicked stop!", "ok", null, false, null); + break; + case 2: // Add Item + Intent addIntent = new Intent(AddRssItem.class); + + // Use an ID so that if we create a "remove item" form we + // can tell which form is returning a value. + startActivityForResult(addIntent, ADD_ELEMENT_REQUEST); + break; + case 3: // Delete item. + if(mRssList.hasFocus()){ + int currentSelectionIndex = mRssList.getSelectedItemIndex(); + + // Create our content URI by adding the ID of the currently selected item using a + // convenience method. + Long itemID = mAdap.getItemId(currentSelectionIndex); + getContentResolver().delete(RssContentProvider.CONTENT_URI.addId(itemID), null); + } + break; + case 4: // Update all + showAlert(null, "You clicked 'Update'!", "ok", null, false, null); + break; + default: + showAlert(null, "I have no idea what you clicked!", "ok", null, false, null); + break; + } + return true; + } + + // Called by the "Add RSS Item" floating screen when it closes. + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data){ + if(resultCode == RESULT_OK){ + switch (requestCode){ + case ADD_ELEMENT_REQUEST: + ContentValues vals = new ContentValues(4); + vals.put(RssContentProvider.TITLE, data.getStringExtra(RssContentProvider.TITLE)); + vals.put(RssContentProvider.URL, data.getStringExtra(RssContentProvider.URL)); + vals.put(RssContentProvider.CONTENT, data.getStringExtra(RssContentProvider.CONTENT)); + vals.put(RssContentProvider.LAST_UPDATED, data.getIntExtra(RssContentProvider.LAST_UPDATED, 0)); + Uri uri = getContentResolver().insert( + RssContentProvider.CONTENT_URI, + vals); + if(uri != null){ + mRssList.setSelection(mRssList.getCount() - 1); + } + break; + default: + break; + } + } + } + + // Our private ArrayAdapter implementation that returns a bold TextView for + // RSS items that are unread, or a normal TextView for items that have been read. + private class RssCursorAdapter extends SimpleCursorAdapter { + public RssCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to){ + super(context, layout, c, from, to); + } + + // Here's our only important override--returning the list item. + @Override + public View getView(int position, View convertView, ViewGroup parent){ + TextView view = (TextView)super.getView(position, convertView, parent); + + if(view != null){ + + // Now get the hasBeenRead value to determine the font. + int hasBeenReadColumnIndex = getCursor().getColumnIndex( + RssContentProvider.HAS_BEEN_READ); + boolean hasBeenRead = (getCursor().getInt(hasBeenReadColumnIndex) == 1 ? true : false); + if(! hasBeenRead){ + Typeface type = view.getTypeface(); + view.setTypeface(Typeface.create(type, Typeface.BOLD_ITALIC)); + } + } + return view; + } + } +} + diff --git a/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader5.java b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader5.java new file mode 100644 index 000000000..0d59bfd00 --- /dev/null +++ b/samples/MySampleRss/src/com/example/codelab/rssexample/MyRssReader5.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2007 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.codelab.rssexample; + +import android.app.Activity; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Bundle; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.widget.AdapterView; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; +import android.widget.AdapterView.OnItemSelectedListener; + +import java.util.logging.Logger; + +public class MyRssReader5 extends Activity implements OnItemSelectedListener { + + private ListView mRssList; + private Cursor mCur; + private RssCursorAdapter mAdap; + private WebView mWebView; + private static final int ADD_ELEMENT_REQUEST = 1; + private Logger mLogger = Logger.getLogger(this.getPackageName()); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // + // Load screen layout. + setContentView(R.layout.main_screen2); + + // Populate ArrayAdapter and bind it to ListView + mRssList = (ListView)findViewById(R.id.rssListView); + mRssList.setOnItemSelectedListener(this); + + mWebView = (WebView)findViewById(R.id.rssWebView); + + mCur = managedQuery(RssContentProvider.CONTENT_URI, // Query for all items. + null, + null, + RssContentProvider.DEFAULT_SORT_ORDER); + + mAdap = new RssCursorAdapter( + this, + R.layout.list_element, // Our layout resource. + mCur, + new String[]{RssContentProvider.TITLE}, // Columns to retrieve. + new int[]{R.id.list_item}); // IDs of widgets to display + mRssList.setAdapter(mAdap); // the corresponding column. + + // Set the last selected item. + // (icicle is only set if this is being restarted). + if(savedInstanceState != null && savedInstanceState.containsKey("lastIndexItem")){ + mRssList.setSelection(savedInstanceState.getInteger("lastIndexItem")); + } + } + +//BEGIN_INCLUDE(5_4) + // Listener to listen for list selection changes, and send the new text to + // the web view. + public void onItemSelected(AdapterView parent, View v, int position, long id){ + // Need to nest this in a try block because we get called sometimes + // with the index of a recently deleted item. + String content = ""; + try{ + content = mCur.getString(mCur.getColumnIndex(RssContentProvider.CONTENT)); + mLogger.info("MyRssReader5 content string:" + content); + } + catch (Exception e){ + // This method is sometimes called after a backing data item + // is deleted. In that case, we don't want to throw an error. + mLogger.warning("MyRssReader5.onItemSelected() couldn't get the content" + + "from the cursor " + e.getMessage()); + } + mWebView.loadData(content, "text/html", "utf-8"); + } +//END_INCLUDE(5_4) + + public void onNothingSelected(AdapterView parent){ + mWebView.loadData("<html><body><p>No selection chosen</p></body></html>", "text/html", "utf-8"); + } + + // Store our state before we are potentially bumped from memory. + // We'd like to store the current ListView selection. + @Override + protected void onSaveInstanceState(Bundle outState){ + int index = mRssList.getSelectedItemIndex(); + if(index > -1){ + outState.putInteger("lastIndexItem", index); + } + } + + // Add our initial menu options. We will tweak this menu when it's loaded swap out + // "start service" or "stop service", depending on whether the service is currently running. + @Override + public boolean onCreateOptionsMenu(Menu menu){ + // Always call the superclass implementation to + // provide standard items. + super.onCreateOptionsMenu(menu); + + menu.add(0, 0, R.string.menu_option_start, null); + menu.add(0, 1, R.string.menu_option_stop, null); + menu.add(0, 2, R.string.menu_option_add, null); + menu.add(0, 3, R.string.menu_option_delete, null); + menu.add(0, 4, R.string.menu_option_update, null); + + return true; + } + + // Toggle out start service/stop service depending on whether the service is running. + @Override + public boolean onPrepareOptionsMenu(Menu menu){ + return true; + } + + // Handle our menu clicks. + @Override + public boolean onOptionsItemSelected(Menu.Item item){ + super.onOptionsItemSelected(item); + + switch (item.getId()){ + case 0: // Start service + Intent basicStartIntent = new Intent(RssService.class); + startService(basicStartIntent); + break; + case 1: // Stop service + Intent stopIntent = new Intent(RssService.class); + stopService(stopIntent); + break; + case 2: // Add Item + Intent addIntent = new Intent(AddRssItem.class); + // Use an ID so that if we create a "remove item" form we + // can tell which form is returning a value. + startActivityForResult(addIntent, ADD_ELEMENT_REQUEST); + break; + case 3: // Delete item. + if(mRssList.hasFocus()){ + int currentSelectionIndex = mRssList.getSelectedItemIndex(); + mLogger.info("MyRssReader5.onOptionsItemSelected(): Deleting list member:" + + mRssList.getSelectedItemIndex()); + // Create our content URI by adding the ID of the currently selected item using a + // convenience method. + Long itemID = mAdap.getItemId(currentSelectionIndex); + getContentResolver().delete(RssContentProvider.CONTENT_URI.addId(itemID), null); + } + break; + case 4: // Requery all + Bundle startupVals = new Bundle(1); + startupVals.putBoolean(RssService.REQUERY_KEY, true); + Intent requeryIntent = new Intent(RssService.class); + startService(requeryIntent, startupVals); + break; + default: + showAlert(null, "I have no idea what you clicked!", "ok", null, false, null); + break; + } + return true; + } + + // Called by the "Add RSS Item" floating screen when it closes. + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data){ + if(resultCode == RESULT_OK){ + switch (requestCode){ + case ADD_ELEMENT_REQUEST: + ContentValues vals = new ContentValues(5); + vals.put(RssContentProvider.TITLE, data.getStringExtra(RssContentProvider.TITLE)); + vals.put(RssContentProvider.URL, data.getStringExtra(RssContentProvider.URL)); + vals.put(RssContentProvider.CONTENT, data.getStringExtra(RssContentProvider.CONTENT)); + vals.put(RssContentProvider.LAST_UPDATED, data.getIntExtra(RssContentProvider.LAST_UPDATED, 0)); + Uri uri = getContentResolver().insert( + RssContentProvider.CONTENT_URI, + vals); + if(uri != null){ + // Tell the service to requery the service, then set + // it as the active selection. + Bundle startupVals = new Bundle(1); + startupVals.putString(RssService.RSS_URL, data.getStringExtra("URL")); + Intent startIntent = new Intent(RssService.class); + startIntent.putExtras(startupVals); + startService(startIntent); + mRssList.setSelection(mRssList.getCount() - 1); + } + break; + default: + break; + } + } + } + + // Our private ArrayAdapter implementation that returns a bold TextView for + // RSS items that are unread, or a normal TextView for items that have been read. + private class RssCursorAdapter extends SimpleCursorAdapter { + public RssCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) { + super(context, layout, c, from, to); + } + + // Here's our only important override--returning the list item. + @Override + public View getView(int position, View convertView, ViewGroup parent){ + TextView view = (TextView)super.getView(position, convertView, parent); + + if(view != null){ + + // Now get the hasBeenRead value to determine the font. + int hasBeenReadColumnIndex = getCursor().getColumnIndex(RssContentProvider.HAS_BEEN_READ); + boolean hasBeenRead = (getCursor().getInt(hasBeenReadColumnIndex) == 1 ? true : false); + if(! hasBeenRead){ + Typeface type = view.getTypeface(); + view.setTypeface(Typeface.create(type, Typeface.BOLD_ITALIC)); + } + } + return view; + } + } + +} + diff --git a/samples/MySampleRss/src/com/example/codelab/rssexample/RssContentProvider.java b/samples/MySampleRss/src/com/example/codelab/rssexample/RssContentProvider.java new file mode 100644 index 000000000..5d523dccb --- /dev/null +++ b/samples/MySampleRss/src/com/example/codelab/rssexample/RssContentProvider.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2007 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.codelab.rssexample; + +import android.content.ContentProvider; +import android.content.ContentProviderDatabaseHelper; +import android.content.UriMatcher; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.content.ContentValues; +import android.text.TextUtils; + +import java.util.logging.Logger; + +// Content Provider for RSS feed information. Each row describes a single +// RSS feed. See the public static constants at the end of this class +// to learn what each record contains. +public class RssContentProvider extends ContentProvider { + private Logger mLogger = Logger.getLogger("com.example.codelab.rssexample"); + private SQLiteDatabase mDb; + private DatabaseHelper mDbHelper = new DatabaseHelper(); + private static final String DATABASE_NAME = "rssitems.db"; + private static final String DATABASE_TABLE_NAME = "rssItems"; + private static final int DB_VERSION = 1; + private static final int ALL_MESSAGES = 1; + private static final int SPECIFIC_MESSAGE = 2; + + // Set up our URL matchers to help us determine what an + // incoming URI parameter is. + private static final UriMatcher URI_MATCHER; + static{ + URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + URI_MATCHER.addURI("my_rss_item", "rssitem", ALL_MESSAGES); + URI_MATCHER.addURI("my_rss_item", "rssitem/#", SPECIFIC_MESSAGE); + } + + // Here's the public URI used to query for RSS items. + public static final Uri CONTENT_URI = Uri.parse( "content://my_rss_item/rssitem"); + + // Here are our column name constants, used to query for field values. + public static final String ID = "_id"; + public static final String URL = "url"; + public static final String TITLE = "title"; + public static final String HAS_BEEN_READ = "hasbeenread"; + public static final String CONTENT = "rawcontent"; + public static final String LAST_UPDATED = "lastupdated"; + public static final String DEFAULT_SORT_ORDER = TITLE + " DESC"; + + // Database creation/version management helper. + // Create it statically because we don't need to have customized instances. + private static class DatabaseHelper extends ContentProviderDatabaseHelper{ + + @Override + public void onCreate(SQLiteDatabase db){ + try{ + String sql = "CREATE TABLE " + DATABASE_TABLE_NAME + "(" + + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + URL + " TEXT," + + TITLE + " TEXT," + + HAS_BEEN_READ + " BOOLEAN DEFAULT 0," + + CONTENT + " TEXT," + + LAST_UPDATED + " INTEGER DEFAULT 0);"; + Logger.getLogger("com.example.codelab.rssexample").info("DatabaseHelper.onCreate(): SQL statement: " + sql); + db.execSQL(sql); + Logger.getLogger("com.example.codelab.rssexample").info("DatabaseHelper.onCreate(): Created a database"); + } catch (SQLException e) { + Logger.getLogger("com.example.codelab.rssexample").warning("DatabaseHelper.onCreate(): Couldn't create a database!"); + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){ + // Don't have any upgrades yet, so if this gets called for some reason we'll + // just drop the existing table, and recreate the database with the + // standard method. + db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE_NAME + ";"); + + } + } + + @Override + public boolean onCreate() { + // First we need to open the database. If this is our first time, + // the attempt to retrieve a database will throw + // FileNotFoundException, and we will then create the database. + final Context con = getContext(); + try{ + mDb = mDbHelper.openDatabase(getContext(), DATABASE_NAME, null, DB_VERSION); + mLogger.info("RssContentProvider.onCreate(): Opened a database"); + } catch (Exception ex) { + return false; + } + if(mDb == null){ + return false; + } else { + return true; + } + } + + // Convert the URI into a custom MIME type. + // Our UriMatcher will parse the URI to decide whether the + // URI is for a single item or a list. + @Override + public String getType(Uri uri) { + switch (URI_MATCHER.match(uri)){ + case ALL_MESSAGES: + return "vnd.android.cursor.dir/rssitem"; // List of items. + case SPECIFIC_MESSAGE: + return "vnd.android.cursor.item/rssitem"; // Specific item. + default: + return null; + } + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String groupBy, String having, String sortOrder) { + // We won't bother checking the validity of params here, but you should! + + // SQLiteQueryBuilder is the helper class that creates the + // proper SQL syntax for us. + SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder(); + + // Set the table we're querying. + qBuilder.setTables(DATABASE_TABLE_NAME); + + // If the query ends in a specific record number, we're + // being asked for a specific record, so set the + // WHERE clause in our query. + if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){ + qBuilder.appendWhere("_id=" + uri.getPathLeafId()); + } + + // Set sort order. If none specified, use default. + if(TextUtils.isEmpty(sortOrder)){ + sortOrder = DEFAULT_SORT_ORDER; + } + + // Make the query. + Cursor c = qBuilder.query(mDb, + projection, + selection, + selectionArgs, + groupBy, + having, + sortOrder); + c.setNotificationUri(getContext().getContentResolver(), uri); + return c; + } + + @Override + public int update(Uri uri, ContentValues values, String whereClause) { + // NOTE Argument checking code omitted. Check your parameters! + int updateCount = mDb.update(DATABASE_TABLE_NAME, values, whereClause); + + // Notify any listeners and return the updated row count. + getContext().getContentResolver().notifyUpdate(uri, null); + return updateCount; + } + + @Override + public Uri insert(Uri requestUri, ContentValues initialValues) { + // NOTE Argument checking code omitted. Check your parameters! Check that + // your row addition request succeeded! + + long rowId = -1; + rowId = mDb.insert(DATABASE_TABLE_NAME, "rawcontent", initialValues); + Uri newUri = CONTENT_URI.addId(rowId); + + // Notify any listeners and return the URI of the new row. + getContext().getContentResolver().notifyInsert(CONTENT_URI, null); + return newUri; + } + + @Override + public int delete(Uri uri, String where) { + // NOTE Argument checking code omitted. Check your parameters! + int rowCount = mDb.delete(DATABASE_TABLE_NAME, ID + " = " + uri.getPathLeafId()); + + // Notify any listeners and return the deleted row count. + getContext().getContentResolver().notifyDelete(uri, null); + return rowCount; + } +} diff --git a/samples/MySampleRss/src/com/example/codelab/rssexample/RssItem.java b/samples/MySampleRss/src/com/example/codelab/rssexample/RssItem.java new file mode 100644 index 000000000..f13357d8d --- /dev/null +++ b/samples/MySampleRss/src/com/example/codelab/rssexample/RssItem.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 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.codelab.rssexample; + +import java.util.Date; + +// Custom class to hold an RSS item. +public class RssItem{ + public String url; + public String title; + public boolean hasBeenRead = false; + public String content; + public Date lastUpdated; + + public RssItem(String url, String title){ + this.url = url; + this.title = title; + } + + @Override public String toString(){ + return title; + } +} diff --git a/samples/MySampleRss/src/com/example/codelab/rssexample/RssService.java b/samples/MySampleRss/src/com/example/codelab/rssexample/RssService.java new file mode 100644 index 000000000..e84f89d22 --- /dev/null +++ b/samples/MySampleRss/src/com/example/codelab/rssexample/RssService.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2007 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.codelab.rssexample; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Bundle; +import android.database.Cursor; +import android.content.ContentResolver; +import android.os.Handler; +import android.text.TextUtils; +import java.io.BufferedReader; +import java.net.URL; +import java.net.MalformedURLException; +import java.lang.StringBuilder; +import java.io.InputStreamReader; +import java.io.IOException; +import java.util.GregorianCalendar; +import java.text.SimpleDateFormat; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.text.ParseException; + +public class RssService extends Service implements Runnable{ + private Logger mLogger = Logger.getLogger(this.getPackageName()); + public static final String REQUERY_KEY = "Requery_All"; // Sent to tell us force a requery. + public static final String RSS_URL = "RSS_URL"; // Sent to tell us to requery a specific item. + private NotificationManager mNM; + private Cursor mCur; // RSS content provider cursor. + private GregorianCalendar mLastCheckedTime; // Time we last checked our feeds. + private final String LAST_CHECKED_PREFERENCE = "last_checked"; + static final int UPDATE_FREQUENCY_IN_MINUTES = 60; + private Handler mHandler; // Handler to trap our update reminders. + private final int NOTIFY_ID = 1; // Identifies our service icon in the icon tray. + + @Override + protected void onCreate(){ + // Display an icon to show that the service is running. + mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + Intent clickIntent = new Intent(Intent.ACTION_MAIN); + clickIntent.setClassName(MyRssReader5.class.getName()); + Notification note = new Notification(this, R.drawable.rss_icon, "RSS Service", + clickIntent, null); + mNM.notify(NOTIFY_ID, note); + mHandler = new Handler(); + + // Create the intent that will be launched if the user clicks the + // icon on the status bar. This will launch our RSS Reader app. + Intent intent = new Intent(MyRssReader.class); + + // Get a cursor over the RSS items. + ContentResolver rslv = getContentResolver(); + mCur = rslv.query(RssContentProvider.CONTENT_URI, null, null, null, null); + + // Load last updated value. + // We store last updated value in preferences. + SharedPreferences pref = getSharedPreferences("", 0); + mLastCheckedTime = new GregorianCalendar(); + mLastCheckedTime.setTimeInMillis(pref.getLong(LAST_CHECKED_PREFERENCE, 0)); + +//BEGIN_INCLUDE(5_1) + // Need to run ourselves on a new thread, because + // we will be making resource-intensive HTTP calls. + // Our run() method will check whether we need to requery + // our sources. + Thread thr = new Thread(null, this, "rss_service_thread"); + thr.start(); +//END_INCLUDE(5_1) + mLogger.info("RssService created"); + } + +//BEGIN_INCLUDE(5_3) + // A cheap way to pass a message to tell the service to requery. + @Override + protected void onStart(Intent intent, int startId){ + super.onStart(startId, arguments); + Bundle arguments = intent.getExtras(); + if(arguments != null) { + if(arguments.containsKey(REQUERY_KEY)) { + queryRssItems(); + } + if(arguments.containsKey(RSS_URL)) { + // Typically called after adding a new RSS feed to the list. + queryItem(arguments.getString(RSS_URL)); + } + } + } +//END_INCLUDE(5_3) + + // When the service is destroyed, get rid of our persistent icon. + @Override + protected void onDestroy(){ + mNM.cancel(NOTIFY_ID); + } + + // Determines whether the next scheduled check time has passed. + // Loads this value from a stored preference. If it has (or if no + // previous value has been stored), it will requery all RSS feeds; + // otherwise, it will post a delayed reminder to check again after + // now - next_check_time milliseconds. + public void queryIfPeriodicRefreshRequired() { + GregorianCalendar nextCheckTime = new GregorianCalendar(); + nextCheckTime = (GregorianCalendar) mLastCheckedTime.clone(); + nextCheckTime.add(GregorianCalendar.MINUTE, UPDATE_FREQUENCY_IN_MINUTES); + mLogger.info("last checked time:" + mLastCheckedTime.toString() + " Next checked time: " + nextCheckTime.toString()); + + if(mLastCheckedTime.before(nextCheckTime)) { + queryRssItems(); + } else { + // Post a message to query again when we get to the next check time. + long timeTillNextUpdate = mLastCheckedTime.getTimeInMillis() - GregorianCalendar.getInstance().getTimeInMillis(); + mHandler.postDelayed(this, 1000 * 60 * UPDATE_FREQUENCY_IN_MINUTES); + } + + } + + // Query all feeds. If the new feed has a newer pubDate than the previous, + // then update it. + void queryRssItems(){ + mLogger.info("Querying Rss feeds..."); + + // The cursor might have gone stale. Requery to be sure. + // We need to call next() after a requery to get to the + // first record. + mCur.requery(); + while (mCur.next()){ + // Get the URL for the feed from the cursor. + int urlColumnIndex = mCur.getColumnIndex(RssContentProvider.URL); + String url = mCur.getString(urlColumnIndex); + queryItem(url); + } + // Reset the global "last checked" time + mLastCheckedTime.setTimeInMillis(System.currentTimeMillis()); + + // Post a message to query again in [update_frequency] minutes + mHandler.postDelayed(this, 1000 * 60 * UPDATE_FREQUENCY_IN_MINUTES); + } + + + // Query an individual RSS feed. Returns true if successful, false otherwise. + private boolean queryItem(String url) { + try { + URL wrappedUrl = new URL(url); + String rssFeed = readRss(wrappedUrl); + mLogger.info("RSS Feed " + url + ":\n " + rssFeed); + if(TextUtils.isEmpty(rssFeed)) { + return false; + } + + // Parse out the feed update date, and compare to the current version. + // If feed update time is newer, or zero (if never updated, for new + // items), then update the content, date, and hasBeenRead fields. + // lastUpdated = <rss><channel><pubDate>value</pubDate></channel></rss>. + // If that value doesn't exist, the current date is used. + GregorianCalendar feedPubDate = parseRssDocPubDate(rssFeed); + GregorianCalendar lastUpdated = new GregorianCalendar(); + int lastUpdatedColumnIndex = mCur.getColumnIndex(RssContentProvider.LAST_UPDATED); + lastUpdated.setTimeInMillis(mCur.getLong(lastUpdatedColumnIndex)); + if(lastUpdated.getTimeInMillis() == 0 || + lastUpdated.before(feedPubDate) && !TextUtils.isEmpty(rssFeed)) { + // Get column indices. + int contentColumnIndex = mCur.getColumnIndex(RssContentProvider.CONTENT); + int updatedColumnIndex = mCur.getColumnIndex(RssContentProvider.HAS_BEEN_READ); + + // Update values. + mCur.updateString(contentColumnIndex, rssFeed); + mCur.updateLong(lastUpdatedColumnIndex, feedPubDate.getTimeInMillis()); + mCur.updateInt(updatedColumnIndex, 0); + mCur.commitUpdates(); + } + } catch (MalformedURLException ex) { + mLogger.warning("Error in queryItem: Bad url"); + return false; + } + return true; + } + + // BEGIN_INCLUDE(5_2) + // Get the <pubDate> content from a feed and return a + // GregorianCalendar version of the date. + // If the element doesn't exist or otherwise can't be + // found, return a date of 0 to force a refresh. + private GregorianCalendar parseRssDocPubDate(String xml){ + GregorianCalendar cal = new GregorianCalendar(); + cal.setTimeInMillis(0); + String patt ="<[\\s]*pubDate[\\s]*>(.+?)</pubDate[\\s]*>"; + Pattern p = Pattern.compile(patt); + Matcher m = p.matcher(xml); + try { + if(m.find()) { + mLogger.info("pubDate: " + m.group()); + SimpleDateFormat pubDate = new SimpleDateFormat(); + cal.setTime(pubDate.parse(m.group(1))); + } + } catch(ParseException ex) { + mLogger.warning("parseRssDocPubDate couldn't find a <pubDate> tag. Returning default value."); + } + return cal; + } + + // Read the submitted RSS page. + String readRss(URL url){ + String html = "<html><body><h2>No data</h2></body></html>"; + try { + mLogger.info("URL is:" + url.toString()); + BufferedReader inStream = + new BufferedReader(new InputStreamReader(url.openStream()), + 1024); + String line; + StringBuilder rssFeed = new StringBuilder(); + while ((line = inStream.readLine()) != null){ + rssFeed.append(line); + } + html = rssFeed.toString(); + } catch(IOException ex) { + mLogger.warning("Couldn't open an RSS stream"); + } + return html; + } +//END_INCLUDE(5_2) + + // Callback we send to ourself to requery all feeds. + public void run() { + queryIfPeriodicRefreshRequired(); + } + + // Required by Service. We won't implement it here, but need to + // include this basic code. + @Override + public IBinder onBind(Intent intent){ + return mBinder; + } + + // This is the object that receives RPC calls from clients.See + // RemoteService for a more complete example. + private final IBinder mBinder = new Binder() { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { + return super.onTransact(code, data, reply, flags); + } + }; +} |
