package de.sdw.android.weather.widget;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.XPatherException;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.text.Html;
import android.util.Log;
import android.widget.RemoteViews;
import de.sdw.android.weather.R;
public class WeatherWidget extends AppWidgetProvider {
private static final String PLZS_FILENAME = "plzs.store";
public static final String TAG_WEATHER = "weather";
public static final String EXTRA_PLZ = "PLZ";
private static final String URL_WETTERONLINE = "http://m.wetteronline.de/cgi-bin/city?L=dep";
private static final String URL_PLZ = "54294";
private static final String EXTRA_WIDGET_IDS = "EXTRA_WIDGET_IDS";
private static RemoteViews views;
private static HashMap<Integer, Integer> plzs;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// To prevent any ANR timeouts, we perform the update in a service
Log.d(TAG_WEATHER, "PLZs: " + getPLZs());
ctx = context;
WeatherWidget.updateView(context, appWidgetIds);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
ctx = context;
for (int i = 0; i <= appWidgetIds.length; i++) {
Log.d(TAG_WEATHER, "Delete the plz(" + getPLZs().get(appWidgetIds[i])
+ ") for the following widget: " + appWidgetIds[i]);
getPLZs().remove(appWidgetIds[i]);
}
savePlZsToFile();
}
private static Context ctx;
/**
* Method to tricker an update of the view for all given appWidgetIds.
*
* @param context
* @param appWidgetIds
*/
public static void updateView(Context context, int[] appWidgetIds) {
Intent intent = new Intent(context, UpdateService.class);
intent.putExtra(EXTRA_WIDGET_IDS, appWidgetIds);
context.startService(intent);
}
public static void setPLZ(Context context, int widgetId, int plz) {
ctx = context;
Log.d(TAG_WEATHER, "Adding PLZ(" + plz + ") for the following widget: " + widgetId);
getPLZs().put(widgetId, plz);
savePlZsToFile();
}
private static void savePlZsToFile() {
FileOutputStream fos = null;
try {
fos = ctx.openFileOutput(PLZS_FILENAME, Context.MODE_PRIVATE);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(getPLZs());
oos.close();
} catch (Exception e) {
Log.e(TAG_WEATHER, "could not write plzs to settingsfile", e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) { /* I don't care! */}
}
}
}
private static HashMap<Integer, Integer> getPLZs() {
if (plzs == null) {
plzs = readPLZsFromFile();
}
return plzs;
}
/**
* Open the
*
* @return
*/
@SuppressWarnings("unchecked")
private static HashMap<Integer, Integer> readPLZsFromFile() {
FileInputStream fis = null;
try {
if (ctx == null) {
Log.d(TAG_WEATHER, "Context is null!");
return null;
}
fis = ctx.openFileInput(PLZS_FILENAME);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
if (obj != null) {
if (obj instanceof HashMap) {
plzs = (HashMap<Integer, Integer>) obj;
Log.d(TAG_WEATHER, "PLZs contained in settings file." + plzs);
} else {
Log.e(TAG_WEATHER, "Unexpected object stored in settings-file.");
}
} else {
Log.d(TAG_WEATHER, "No PLZs in settings found.");
plzs = new HashMap<Integer, Integer>();
}
ois.close();
} catch (Exception e) {
Log.e(TAG_WEATHER, "Failed to read PLZs from settingsfile", e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) { /* I don't care! */}
}
}
return plzs;
}
public static class UpdateService extends Service {
private static final String TABLE_ENTRIES_XPATH = "/body/div[@class='board']/div[@class='content']/table//tr";
private static final String DAY_XPATH = "/body/div[@class='board']/div[@class='content']/div[@class='city']";
private static final String FORECAST_XPATH = "/td[3]";
private static final String FORECAST_TEMP_XPATH = "/td[2]";
private static final String URL_DAY1 = "00";
private static final String URL_DAY2 = "01";
private static final String URL_DAY3 = "02";
private String combinedUrl = URL_WETTERONLINE + URL_PLZ;
public class Day {
String title = new String();
String min = new String();
String max = new String();
String morning = new String();
String midday = new String();
String evening = new String();
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onStart(Intent intent, int startId) {
int[] intArray = intent.getIntArrayExtra(EXTRA_WIDGET_IDS);
Log.d(TAG_WEATHER, "Service for updating the widgets started.");
for (int i = 0; i < intArray.length; i++) {
if (intArray.length < 1 || getPLZs().get(intArray[i]) == null) {
return;
} else {
int plz = getPLZs().get(intArray[i]);
combinedUrl = URL_WETTERONLINE + plz;
Log.d(TAG_WEATHER, "The URL for widget(" + intArray[i] + "): " + combinedUrl);
RemoteViews updateViews = buildUpdate(this, intent);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(intArray[i], updateViews);
}
}
}
public RemoteViews buildUpdate(Context context, Intent intent) {
views = new RemoteViews(context.getPackageName(), R.layout.widget);
try {
extractCleaner();
} catch (Throwable e) {
Log.e(TAG_WEATHER, "failure during extraction", e);
}
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
views.setOnClickPendingIntent(R.layout.widget_config, pendingIntent);
return views;
}
private void extractCleaner() throws IOException, XPatherException {
fillDayOne(getForecast(new URL(combinedUrl + URL_DAY1)));
fillDayTwo(getForecast(new URL(combinedUrl + URL_DAY2)));
fillDayThree(getForecast(new URL(combinedUrl + URL_DAY3)));
views.setTextViewText(R.id.Status, "Stand: " + new Date().toLocaleString());
}
private Day getForecast(URL url) throws IOException, XPatherException {
HtmlCleaner cleaner = getHtmlCleaner();
Log.d(TAG_WEATHER, "Get forecast from url: /n" + url.toString());
TagNode node = cleaner.clean(url.openStream());
Log.d(TAG_WEATHER, "cleanedString: " + node);
Object[] objRows = node.evaluateXPath(TABLE_ENTRIES_XPATH);
ArrayList<TagNode> rows = new ArrayList<TagNode>();
for (Object obj : objRows) {
if(obj instanceof TagNode) {
rows.add((TagNode) obj);
}
}
Log.d(TAG_WEATHER, "rows: " + rows.size());
Day day = new Day();
fillDayForecast(day, rows);
fillDayName(day, node);
return day;
}
private HtmlCleaner getHtmlCleaner() {
HtmlCleaner cleaner = new HtmlCleaner();
CleanerProperties props = cleaner.getProperties();
props.setAllowHtmlInsideAttributes(true);
props.setAllowMultiWordAttributes(true);
props.setRecognizeUnicodeChars(true);
props.setOmitComments(true);
props.setPruneTags("img");
return cleaner;
}
void fillDayForecast(Day day, List<TagNode> rows) {
try {
day.min = extractTemperature(rows.get(0));
day.max = extractTemperature(rows.get(1));
day.morning = extractForecast(rows.get(2));
day.midday = extractForecast(rows.get(3));
day.evening = extractForecast(rows.get(4));
} catch (XPatherException e) {
Log.e(TAG_WEATHER, "Couldn't map the http result to day information", e);
}
}
public void fillDayName(Day day, TagNode node) throws XPatherException {
String dayName = ((TagNode) node.evaluateXPath(DAY_XPATH)[0]).getText().toString();
day.title = cleanUpName(dayName);
}
String cleanUpName(String text) {
text = text.substring(20, text.indexOf(","));
return text;
}
public String extractTemperature(TagNode row) throws XPatherException {
TagNode contentColumn = (TagNode) row.evaluateXPath(FORECAST_TEMP_XPATH)[0];
String encodedText = contentColumn.getText().toString().trim();
return Html.fromHtml(encodedText).toString();
}
public String extractForecast(TagNode row) throws XPatherException {
TagNode contentColumn = (TagNode) row.evaluateXPath(FORECAST_XPATH)[0];
String encodedText = contentColumn.getText().toString().trim();
return Html.fromHtml(encodedText).toString();
}
private void fillDayOne(Day day) {
views.setTextViewText(R.id.Header1, day.title);
views.setTextViewText(R.id.Temperature1Min, day.min);
views.setTextViewText(R.id.Temperature1Max, day.max);
views.setTextViewText(R.id.Morning1, day.morning);
views.setTextViewText(R.id.Midday1, day.midday);
views.setTextViewText(R.id.Evening1, day.evening);
}
private void fillDayTwo(Day day) {
views.setTextViewText(R.id.Header2, day.title);
views.setTextViewText(R.id.Temperature2Min, day.min);
views.setTextViewText(R.id.Temperature2Max, day.max);
views.setTextViewText(R.id.Morning2, day.morning);
views.setTextViewText(R.id.Midday2, day.midday);
views.setTextViewText(R.id.Evening2, day.evening);
}
private void fillDayThree(Day day) {
views.setTextViewText(R.id.Header3, day.title);
views.setTextViewText(R.id.Temperature3Min, day.min);
views.setTextViewText(R.id.Temperature3Max, day.max);
views.setTextViewText(R.id.Morning3, day.morning);
views.setTextViewText(R.id.Midday3, day.midday);
views.setTextViewText(R.id.Evening3, day.evening);
}
}
}
|