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,14 @@
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
LOCAL_DEX_PREOPT := false
##include $(BUILD_PACKAGE)

View file

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

View file

@ -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="match_parent"
android:layout_width="match_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="match_parent"
android:layout_toRightOf="@id/title_label"/>
<EditText android:id="@+id/url_textbox"
android:layout_height="wrap_content"
android:layout_width="match_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>

View file

@ -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"/>

View file

@ -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="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:scrollbars="vertical">
<ListView android:id="@+id/rssListView"
android:layout_height="0px"
android:layout_width="match_parent"
android:layout_weight="1"
android:background="#00CC00"/>
<WebView android:id="@+id/rssWebView"
android:background="#77CC0000"
android:layout_height="0px"
android:layout_width="match_parent"
android:layout_weight="1"/>
</LinearLayout>

View file

@ -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="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:scrollbars="vertical">
<ListView android:id="@+id/rssListView"
android:layout_height="0px"
android:layout_width="match_parent"
android:layout_weight="1"
android:background="#9900FF00"/>
<WebView android:id="@+id/rssWebView"
android:layout_height="0px"
android:layout_width="match_parent"
android:layout_weight="2"/>
</LinearLayout>

View file

@ -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>

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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)
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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", null);
}
//END_INCLUDE(5_4)
public void onNothingSelected(AdapterView parent){
mWebView.loadData("<html><body><p>No selection chosen</p></body></html>", "text/html", null);
}
// 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;
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
};
}