public static final String LOG_TAG = TestDb.class.getSimpleName();
+ static final String TEST_LOCATION = "99705";
+ static final String TEST_DATE = "20141205";
+
public void testCreateDb() throws Throwable {
mContext.deleteDatabase(WeatherDbHelper.DATABASE_NAME);
SQLiteDatabase db = new WeatherDbHelper(
public void testInsertReadDb() {
- // Test data we're going to insert into the DB to see if it works.
- String testLocationSetting = "99705";
- String testCityName = "North Pole";
- double testLatitude = 64.7488;
- double testLongitude = -147.353;
-
// If there's an error in those massive SQL table creation Strings,
// errors will be thrown here when you try to get a writable database.
WeatherDbHelper dbHelper = new WeatherDbHelper(mContext);
null // sort order
);
- validateCursor(cursor, locationTestValues);
+ validateCursor(cursor, locationTestValues);
// Fantastic. Now that we have a location, add some weather!
--- /dev/null
+package uk.me.njae.sunshine;
+
+import android.annotation.TargetApi;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import uk.me.njae.sunshine.data.WeatherContract.LocationEntry;
+import uk.me.njae.sunshine.data.WeatherContract.WeatherEntry;
+import uk.me.njae.sunshine.data.WeatherDbHelper;
+
+public class TestProvider extends AndroidTestCase {
+
+ public static final String LOG_TAG = TestProvider.class.getSimpleName();
+
+ // brings our database to an empty state
+ public void deleteAllRecords() {
+ mContext.getContentResolver().delete(
+ WeatherEntry.CONTENT_URI,
+ null,
+ null
+ );
+ mContext.getContentResolver().delete(
+ LocationEntry.CONTENT_URI,
+ null,
+ null
+ );
+
+ Cursor cursor = mContext.getContentResolver().query(
+ WeatherEntry.CONTENT_URI,
+ null,
+ null,
+ null,
+ null
+ );
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+
+ cursor = mContext.getContentResolver().query(
+ LocationEntry.CONTENT_URI,
+ null,
+ null,
+ null,
+ null
+ );
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ // Since we want each test to start with a clean slate, run deleteAllRecords
+ // in setUp (called by the test runner before each test).
+ public void setUp() {
+ deleteAllRecords();
+ }
+
+ public void testInsertReadProvider() {
+
+ // If there's an error in those massive SQL table creation Strings,
+ // errors will be thrown here when you try to get a writable database.
+ WeatherDbHelper dbHelper = new WeatherDbHelper(mContext);
+ // SQLiteDatabase db = dbHelper.getWritableDatabase();
+
+ // Create a new map of values, where column names are the keys
+ ContentValues locationTestValues = TestDb.createNorthPoleLocationValues();
+
+ Uri locationInsertUri = mContext.getContentResolver().insert(LocationEntry.CONTENT_URI, locationTestValues);
+ assertTrue(locationInsertUri != null);
+ long locationRowId = ContentUris.parseId(locationInsertUri);
+
+ Log.d(LOG_TAG, "New row id: " + locationRowId);
+
+ // Data's inserted. IN THEORY. Now pull some out to stare at it and verify it made
+ // the round trip.
+
+ // A cursor is your primary interface to the query results.
+ Cursor cursor = mContext.getContentResolver().query(
+ LocationEntry.CONTENT_URI, // Table to Query
+ null, // All columns
+ null, // Columns for the "where" clause
+ null, // Values for the "where" clause
+ null // columns to group by
+ );
+
+ TestDb.validateCursor(cursor, locationTestValues);
+
+ // Now see if we can successfully query if we include the row id
+ cursor = mContext.getContentResolver().query(
+ LocationEntry.buildLocationUri(locationRowId),
+ null, // leaving "columns" null just returns all the columns.
+ null, // cols for "where" clause
+ null, // values for "where" clause
+ null // sort order
+ );
+
+ TestDb.validateCursor(cursor, locationTestValues);
+
+
+ // Fantastic. Now that we have a location, add some weather!
+
+ ContentValues weatherTestValues = TestDb.createWeatherValues(locationRowId);
+
+ Uri weatherInsertUri = mContext.getContentResolver().insert(WeatherEntry.CONTENT_URI, weatherTestValues);
+ assertTrue(weatherInsertUri != null);
+
+ // A cursor is your primary interface to the query results.
+ Cursor weatherCursor = mContext.getContentResolver().query(
+ WeatherEntry.CONTENT_URI, // Table to Query
+ null, // leaving "columns" null just returns all the columns.
+ null, // cols for "where" clause
+ null, // values for "where" clause
+ null // columns to group by
+ );
+
+ TestDb.validateCursor(weatherCursor, weatherTestValues);
+
+
+ // Add the location values in with the weather data so that we can make
+ // sure that the join worked and we actually get all the values back
+ addAllContentValues(weatherTestValues, locationTestValues);
+
+ // Get the joined Weather and Location data
+ weatherCursor = mContext.getContentResolver().query(
+ WeatherEntry.buildWeatherLocation(TestDb.TEST_LOCATION),
+ null, // leaving "columns" null just returns all the columns.
+ null, // cols for "where" clause
+ null, // values for "where" clause
+ null // sort order
+ );
+ TestDb.validateCursor(weatherCursor, weatherTestValues);
+
+ // Get the joined Weather and Location data with a start date
+ weatherCursor = mContext.getContentResolver().query(
+ WeatherEntry.buildWeatherLocationWithStartDate(
+ TestDb.TEST_LOCATION, TestDb.TEST_DATE),
+ null, // leaving "columns" null just returns all the columns.
+ null, // cols for "where" clause
+ null, // values for "where" clause
+ null // sort order
+ );
+ TestDb.validateCursor(weatherCursor, weatherTestValues);
+
+ // Get the joined Weather data for a specific date
+ weatherCursor = mContext.getContentResolver().query(
+ WeatherEntry.buildWeatherLocationWithDate(TestDb.TEST_LOCATION, TestDb.TEST_DATE),
+ null,
+ null,
+ null,
+ null
+ );
+ TestDb.validateCursor(weatherCursor, weatherTestValues);
+
+ dbHelper.close();
+ }
+
+ public void testGetType() {
+ // content://uk.me.njae.sunshine/weather/
+ String type = mContext.getContentResolver().getType(WeatherEntry.CONTENT_URI);
+ // vnd.android.cursor.dir/uk.me.njae.sunshine/weather
+ assertEquals(WeatherEntry.CONTENT_TYPE, type);
+
+ String testLocation = "94074";
+ // content://uk.me.njae.sunshine/weather/94074
+ type = mContext.getContentResolver().getType(
+ WeatherEntry.buildWeatherLocation(testLocation));
+ // vnd.android.cursor.dir/uk.me.njae.sunshine/weather
+ assertEquals(WeatherEntry.CONTENT_TYPE, type);
+
+ String testDate = "20140612";
+ // content://uk.me.njae.sunshine/weather/94074/20140612
+ type = mContext.getContentResolver().getType(
+ WeatherEntry.buildWeatherLocationWithDate(testLocation, testDate));
+ // vnd.android.cursor.item/uk.me.njae.sunshine/weather
+ assertEquals(WeatherEntry.CONTENT_ITEM_TYPE, type);
+
+ // content://uk.me.njae.sunshine/location/
+ type = mContext.getContentResolver().getType(LocationEntry.CONTENT_URI);
+ // vnd.android.cursor.dir/uk.me.njae.sunshine/location
+ assertEquals(LocationEntry.CONTENT_TYPE, type);
+
+ // content://uk.me.njae.sunshine/location/1
+ type = mContext.getContentResolver().getType(LocationEntry.buildLocationUri(1L));
+ // vnd.android.cursor.item/uk.me.njae.sunshine/location
+ assertEquals(LocationEntry.CONTENT_ITEM_TYPE, type);
+ }
+
+ public void testUpdateLocation() {
+ // Create a new map of values, where column names are the keys
+ ContentValues values = TestDb.createNorthPoleLocationValues();
+
+ Uri locationUri = mContext.getContentResolver().
+ insert(LocationEntry.CONTENT_URI, values);
+ long locationRowId = ContentUris.parseId(locationUri);
+
+ // Verify we got a row back.
+ assertTrue(locationRowId != -1);
+ Log.d(LOG_TAG, "New row id: " + locationRowId);
+
+ ContentValues updatedValues = new ContentValues(values);
+ updatedValues.put(LocationEntry._ID, locationRowId);
+ updatedValues.put(LocationEntry.COLUMN_CITY_NAME, "Santa's Village");
+
+ int count = mContext.getContentResolver().update(
+ LocationEntry.CONTENT_URI, updatedValues, LocationEntry._ID + "= ?",
+ new String[] { Long.toString(locationRowId)});
+
+ assertEquals(count, 1);
+
+ // A cursor is your primary interface to the query results.
+ Cursor cursor = mContext.getContentResolver().query(
+ LocationEntry.buildLocationUri(locationRowId),
+ null,
+ null, // Columns for the "where" clause
+ null, // Values for the "where" clause
+ null // sort order
+ );
+
+ TestDb.validateCursor(cursor, updatedValues);
+ }
+
+ // Make sure we can still delete after adding/updating stuff
+ public void testDeleteRecordsAtEnd() {
+ deleteAllRecords();
+ }
+
+
+
+ // The target api annotation is needed for the call to keySet -- we wouldn't want
+ // to use this in our app, but in a test it's fine to assume a higher target.
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ void addAllContentValues(ContentValues destination, ContentValues source) {
+ for (String key : source.keySet()) {
+ destination.put(key, source.getAsString(key));
+ }
+ }
+}
android:name="android:support.PARENT_ACTIVITY"
android:value="MainActivity" />
</activity>
+ <provider
+ android:authorities="uk.me.njae.sunshine"
+ android:name=".data.WeatherProvider" />
</application>
</manifest>
--- /dev/null
+package uk.me.njae.sunshine;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.widget.ArrayAdapter;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Vector;
+
+import uk.me.njae.sunshine.data.WeatherContract;
+import uk.me.njae.sunshine.data.WeatherContract.LocationEntry;
+import uk.me.njae.sunshine.data.WeatherContract.WeatherEntry;
+
+/**
+ * Created by neil on 09/11/14.
+ */
+
+
+public class FetchWeatherTask extends AsyncTask<String, Void, String[]> {
+
+ private final String LOG_TAG = FetchWeatherTask.class.getSimpleName();
+
+ private ArrayAdapter<String> mForecastAdapter;
+ private final Context mContext;
+
+ public FetchWeatherTask(Context context, ArrayAdapter<String> forecastAdapter) {
+ mContext = context;
+ mForecastAdapter = forecastAdapter;
+ }
+
+ private boolean DEBUG = true;
+
+ /* The date/time conversion code is going to be moved outside the asynctask later,
+ * so for convenience we're breaking it out into its own method now.
+ */
+ private String getReadableDateString(long time) {
+ // Because the API returns a unix timestamp (measured in seconds),
+ // it must be converted to milliseconds in order to be converted to valid date.
+ Date date = new Date(time * 1000);
+ SimpleDateFormat format = new SimpleDateFormat("E, MMM d");
+ return format.format(date).toString();
+ }
+
+ /**
+ * Prepare the weather high/lows for presentation.
+ */
+ private String formatHighLows(double high, double low) {
+ // Data is fetched in Celsius by default.
+ // If user prefers to see in Fahrenheit, convert the values here.
+ // We do this rather than fetching in Fahrenheit so that the user can
+ // change this option without us having to re-fetch the data once
+ // we start storing the values in a database.
+ SharedPreferences sharedPrefs =
+ PreferenceManager.getDefaultSharedPreferences(mContext);
+ String unitType = sharedPrefs.getString(
+ mContext.getString(R.string.pref_units_key),
+ mContext.getString(R.string.pref_units_metric));
+
+ if (unitType.equals(mContext.getString(R.string.pref_units_imperial))) {
+ high = (high * 1.8) + 32;
+ low = (low * 1.8) + 32;
+ } else if (!unitType.equals(mContext.getString(R.string.pref_units_metric))) {
+ Log.d(LOG_TAG, "Unit type not found: " + unitType);
+ }
+
+ // For presentation, assume the user doesn't care about tenths of a degree.
+ long roundedHigh = Math.round(high);
+ long roundedLow = Math.round(low);
+
+ String highLowStr = roundedHigh + "/" + roundedLow;
+ return highLowStr;
+ }
+
+ /**
+ * Helper method to handle insertion of a new location in the weather database.
+ *
+ * @param locationSetting The location string used to request updates from the server.
+ * @param cityName A human-readable city name, e.g "Mountain View"
+ * @param lat the latitude of the city
+ * @param lon the longitude of the city
+ * @return the row ID of the added location.
+ */
+ private long addLocation(String locationSetting, String cityName, double lat, double lon) {
+
+ Log.v(LOG_TAG, "inserting " + cityName + ", with coord: " + lat + ", " + lon);
+
+ // First, check if the location with this city name exists in the db
+ Cursor cursor = mContext.getContentResolver().query(
+ LocationEntry.CONTENT_URI,
+ new String[]{LocationEntry._ID},
+ LocationEntry.COLUMN_LOCATION_SETTING + " = ?",
+ new String[]{locationSetting},
+ null);
+
+ if (cursor.moveToFirst()) {
+ Log.v(LOG_TAG, "Found it in the database!");
+ int locationIdIndex = cursor.getColumnIndex(LocationEntry._ID);
+ return cursor.getLong(locationIdIndex);
+ } else {
+ Log.v(LOG_TAG, "Didn't find it in the database, inserting now!");
+ ContentValues locationValues = new ContentValues();
+ locationValues.put(LocationEntry.COLUMN_LOCATION_SETTING, locationSetting);
+ locationValues.put(LocationEntry.COLUMN_CITY_NAME, cityName);
+ locationValues.put(LocationEntry.COLUMN_COORD_LAT, lat);
+ locationValues.put(LocationEntry.COLUMN_COORD_LONG, lon);
+
+ Uri locationInsertUri = mContext.getContentResolver()
+ .insert(LocationEntry.CONTENT_URI, locationValues);
+
+ return ContentUris.parseId(locationInsertUri);
+ }
+ }
+
+ /**
+ * Take the String representing the complete forecast in JSON Format and
+ * pull out the data we need to construct the Strings needed for the wireframes.
+ * <p/>
+ * Fortunately parsing is easy: constructor takes the JSON string and converts it
+ * into an Object hierarchy for us.
+ */
+ private String[] getWeatherDataFromJson(String forecastJsonStr, int numDays,
+ String locationSetting)
+ throws JSONException {
+
+ // These are the names of the JSON objects that need to be extracted.
+
+ // Location information
+ final String OWM_CITY = "city";
+ final String OWM_CITY_NAME = "name";
+ final String OWM_COORD = "coord";
+ final String OWM_COORD_LAT = "lat";
+ final String OWM_COORD_LONG = "lon";
+
+ // Weather information. Each day's forecast info is an element of the "list" array.
+ final String OWM_LIST = "list";
+
+ final String OWM_DATETIME = "dt";
+ final String OWM_PRESSURE = "pressure";
+ final String OWM_HUMIDITY = "humidity";
+ final String OWM_WINDSPEED = "speed";
+ final String OWM_WIND_DIRECTION = "deg";
+
+ // All temperatures are children of the "temp" object.
+ final String OWM_TEMPERATURE = "temp";
+ final String OWM_MAX = "max";
+ final String OWM_MIN = "min";
+
+ final String OWM_WEATHER = "weather";
+ final String OWM_DESCRIPTION = "main";
+ final String OWM_WEATHER_ID = "id";
+
+ JSONObject forecastJson = new JSONObject(forecastJsonStr);
+ JSONArray weatherArray = forecastJson.getJSONArray(OWM_LIST);
+
+ JSONObject cityJson = forecastJson.getJSONObject(OWM_CITY);
+ String cityName = cityJson.getString(OWM_CITY_NAME);
+ JSONObject coordJSON = cityJson.getJSONObject(OWM_COORD);
+ double cityLatitude = coordJSON.getLong(OWM_COORD_LAT);
+ double cityLongitude = coordJSON.getLong(OWM_COORD_LONG);
+
+ Log.v(LOG_TAG, cityName + ", with coord: " + cityLatitude + " " + cityLongitude);
+
+ // Insert the location into the database.
+ long locationID = addLocation(locationSetting, cityName, cityLatitude, cityLongitude);
+
+ // Get and insert the new weather information into the database
+ Vector<ContentValues> cVVector = new Vector<ContentValues>(weatherArray.length());
+
+ String[] resultStrs = new String[numDays];
+ for (int i = 0; i < weatherArray.length(); i++) {
+ // These are the values that will be collected.
+
+ long dateTime;
+ double pressure;
+ int humidity;
+ double windSpeed;
+ double windDirection;
+
+ double high;
+ double low;
+
+ String description;
+ int weatherId;
+
+ // Get the JSON object representing the day
+ JSONObject dayForecast = weatherArray.getJSONObject(i);
+
+ // The date/time is returned as a long. We need to convert that
+ // into something human-readable, since most people won't read "1400356800" as
+ // "this saturday".
+ dateTime = dayForecast.getLong(OWM_DATETIME);
+
+ pressure = dayForecast.getDouble(OWM_PRESSURE);
+ humidity = dayForecast.getInt(OWM_HUMIDITY);
+ windSpeed = dayForecast.getDouble(OWM_WINDSPEED);
+ windDirection = dayForecast.getDouble(OWM_WIND_DIRECTION);
+
+ // Description is in a child array called "weather", which is 1 element long.
+ // That element also contains a weather code.
+ JSONObject weatherObject =
+ dayForecast.getJSONArray(OWM_WEATHER).getJSONObject(0);
+ description = weatherObject.getString(OWM_DESCRIPTION);
+ weatherId = weatherObject.getInt(OWM_WEATHER_ID);
+
+ // Temperatures are in a child object called "temp". Try not to name variables
+ // "temp" when working with temperature. It confuses everybody.
+ JSONObject temperatureObject = dayForecast.getJSONObject(OWM_TEMPERATURE);
+ high = temperatureObject.getDouble(OWM_MAX);
+ low = temperatureObject.getDouble(OWM_MIN);
+
+ ContentValues weatherValues = new ContentValues();
+
+ weatherValues.put(WeatherEntry.COLUMN_LOC_KEY, locationID);
+ weatherValues.put(WeatherEntry.COLUMN_DATETEXT,
+ WeatherContract.getDbDateString(new Date(dateTime * 1000L)));
+ weatherValues.put(WeatherEntry.COLUMN_HUMIDITY, humidity);
+ weatherValues.put(WeatherEntry.COLUMN_PRESSURE, pressure);
+ weatherValues.put(WeatherEntry.COLUMN_WIND_SPEED, windSpeed);
+ weatherValues.put(WeatherEntry.COLUMN_DEGREES, windDirection);
+ weatherValues.put(WeatherEntry.COLUMN_MAX_TEMP, high);
+ weatherValues.put(WeatherEntry.COLUMN_MIN_TEMP, low);
+ weatherValues.put(WeatherEntry.COLUMN_SHORT_DESC, description);
+ weatherValues.put(WeatherEntry.COLUMN_WEATHER_ID, weatherId);
+
+ cVVector.add(weatherValues);
+
+ String highAndLow = formatHighLows(high, low);
+ String day = getReadableDateString(dateTime);
+ resultStrs[i] = day + " - " + description + " - " + highAndLow;
+ }
+ if (cVVector.size() > 0) {
+ ContentValues[] cvArray = new ContentValues[cVVector.size()];
+ cVVector.toArray(cvArray);
+ int rowsInserted = mContext.getContentResolver()
+ .bulkInsert(WeatherEntry.CONTENT_URI, cvArray);
+ Log.v(LOG_TAG, "inserted " + rowsInserted + " rows of weather data");
+ // Use a DEBUG variable to gate whether or not you do this, so you can easily
+ // turn it on and off, and so that it's easy to see what you can rip out if
+ // you ever want to remove it.
+ if (DEBUG) {
+ Cursor weatherCursor = mContext.getContentResolver().query(
+ WeatherEntry.CONTENT_URI,
+ null,
+ null,
+ null,
+ null
+ );
+
+ if (weatherCursor.moveToFirst()) {
+ ContentValues resultValues = new ContentValues();
+ DatabaseUtils.cursorRowToContentValues(weatherCursor, resultValues);
+ Log.v(LOG_TAG, "Query succeeded! **********");
+ for (String key : resultValues.keySet()) {
+ Log.v(LOG_TAG, key + ": " + resultValues.getAsString(key));
+ }
+ } else {
+ Log.v(LOG_TAG, "Query failed! :( **********");
+ }
+ }
+ }
+ return resultStrs;
+ }
+
+ @Override
+ protected String[] doInBackground(String... params) {
+
+ // If there's no zip code, there's nothing to look up. Verify size of params.
+ if (params.length == 0) {
+ return null;
+ }
+ String locationQuery = params[0];
+
+ // These two need to be declared outside the try/catch
+ // so that they can be closed in the finally block.
+ HttpURLConnection urlConnection = null;
+ BufferedReader reader = null;
+
+ // Will contain the raw JSON response as a string.
+ String forecastJsonStr = null;
+
+ String format = "json";
+ String units = "metric";
+ int numDays = 14;
+
+ try {
+ // Construct the URL for the OpenWeatherMap query
+ // Possible parameters are avaiable at OWM's forecast API page, at
+ // http://openweathermap.org/API#forecast
+ final String FORECAST_BASE_URL =
+ "http://api.openweathermap.org/data/2.5/forecast/daily?";
+ final String QUERY_PARAM = "q";
+ final String FORMAT_PARAM = "mode";
+ final String UNITS_PARAM = "units";
+ final String DAYS_PARAM = "cnt";
+
+ Uri builtUri = Uri.parse(FORECAST_BASE_URL).buildUpon()
+ .appendQueryParameter(QUERY_PARAM, params[0])
+ .appendQueryParameter(FORMAT_PARAM, format)
+ .appendQueryParameter(UNITS_PARAM, units)
+ .appendQueryParameter(DAYS_PARAM, Integer.toString(numDays))
+ .build();
+
+ URL url = new URL(builtUri.toString());
+
+ // Create the request to OpenWeatherMap, and open the connection
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setRequestMethod("GET");
+ urlConnection.connect();
+
+ // Read the input stream into a String
+ InputStream inputStream = urlConnection.getInputStream();
+ StringBuffer buffer = new StringBuffer();
+ if (inputStream == null) {
+ // Nothing to do.
+ return null;
+ }
+ reader = new BufferedReader(new InputStreamReader(inputStream));
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ // Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
+ // But it does make debugging a *lot* easier if you print out the completed
+ // buffer for debugging.
+ buffer.append(line + "\n");
+ }
+
+ if (buffer.length() == 0) {
+ // Stream was empty. No point in parsing.
+ return null;
+ }
+ forecastJsonStr = buffer.toString();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Error ", e);
+ // If the code didn't successfully get the weather data, there's no point in attemping
+ // to parse it.
+ return null;
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (final IOException e) {
+ Log.e(LOG_TAG, "Error closing stream", e);
+ }
+ }
+ }
+
+ try {
+ return getWeatherDataFromJson(forecastJsonStr, numDays, locationQuery);
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ e.printStackTrace();
+ }
+ // This will only happen if there was an error getting or parsing the forecast.
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(String[] result) {
+ if (result != null) {
+ mForecastAdapter.clear();
+ for (String dayForecastStr : result) {
+ mForecastAdapter.add(dayForecastStr);
+ }
+ // New data is back from the server. Hooray!
+ }
+ }
+
+}
\ No newline at end of file
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
-import android.widget.Toast;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.URL;
import java.net.URLEncoder;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
/**
* A placeholder fragment containing a simple view.
}
private void updateWeather() {
- FetchWeatherTask weatherTask = new FetchWeatherTask();
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
- String location = preferences.getString(getString(R.string.pref_location_key),
- getString(R.string.pref_location_default));
- // weatherTask.execute("2642465");
- weatherTask.execute(location);
+ String location = Utility.getPreferredLocation(getActivity());
+ new FetchWeatherTask(getActivity(), mForecastAdapter).execute(location);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
-// String[] forecastArray = {
-// "Today - Sunny - 10/10",
-// "Tomorrow - Cloudy - 11/11",
-// "Tuesday - Snow - 12/12",
-// "Wednesday - Rain - 13/13",
-// "Thursday - Hail - 14/14",
-// "Friday - Scorchio - 15/15",
-// "Saturday - Fog - 16/16"
-// };
-//
-// List<String> weekForecast = new ArrayList<String>(
-// Arrays.asList(forecastArray));
-
mForecastAdapter = new ArrayAdapter<String>(
getActivity(),
R.layout.list_item_forecast,
ListView listView = (ListView) rootView.findViewById(R.id.listview_forecast);
listView.setAdapter(mForecastAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
String forecast = mForecastAdapter.getItem(position);
updateWeather();
}
- public class FetchWeatherTask extends AsyncTask<String, Void, String[]> {
-
- private final String LOG_TAG = FetchWeatherTask.class.getSimpleName();
-
- @Override
- protected void onPostExecute(String[] result) {
- if (result != null) {
- mForecastAdapter.clear();
-// for (String dayForecastStr: result) {
-// mForecastAdapter.add(dayForecastStr);
-// }
- mForecastAdapter.addAll(result);
- }
- }
-
- @Override
- protected String[] doInBackground(String... params) {
- if (params.length == 0) {
- return null;
- }
-
- // These two need to be declared outside the try/catch
- // so that they can be closed in the finally block.
- HttpURLConnection urlConnection = null;
- BufferedReader reader = null;
-
- // Will contain the raw JSON response as a string.
- String forecastJsonStr;
-
- String format = "json";
- String units = "metric";
- int numDays = 7;
-
- try {
- // Construct the URL for the OpenWeatherMap query
- // Possible parameters are avaiable at OWM's forecast API page, at
- // http://openweathermap.org/API#forecast
- final String FORECAST_BASE_URL = "http://api.openweathermap.org/data/2.5/forecast/daily?";
- // final String QUERY_PARAM = "id"; // use this if using a location ID
- final String QUERY_PARAM = "q";
- final String FORMAT_PARAM = "mode";
- final String UNITS_PARAM = "units";
- final String DAYS_PARAM = "cnt";
-
- Uri builtUri = Uri.parse(FORECAST_BASE_URL).buildUpon()
- .appendQueryParameter(QUERY_PARAM, params[0])
- .appendQueryParameter(FORMAT_PARAM, format)
- .appendQueryParameter(UNITS_PARAM, units)
- .appendQueryParameter(DAYS_PARAM, Integer.toString(numDays))
- .build();
-
- URL url = new URL(builtUri.toString());
-/**/ Log.v(LOG_TAG, "Built URI " + builtUri.toString());
-
- // Create the request to OpenWeatherMap, and open the connection
- urlConnection = (HttpURLConnection) url.openConnection();
- urlConnection.setRequestMethod("GET");
- urlConnection.connect();
-
- // Read the input stream into a String
- InputStream inputStream = urlConnection.getInputStream();
- StringBuffer buffer = new StringBuffer();
- if (inputStream == null) {
- // Nothing to do.
- return null;
- }
- reader = new BufferedReader(new InputStreamReader(inputStream));
-
- String line;
- while ((line = reader.readLine()) != null) {
- // Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
- // But it does make debugging a *lot* easier if you print out the completed
- // buffer for debugging.
- buffer.append(line + "\n");
- }
-
- if (buffer.length() == 0) {
- // Stream was empty. No point in parsing.
- return null;
- }
- forecastJsonStr = buffer.toString();
-// Log.v(LOG_TAG, "Forecast JSON string: " + forecastJsonStr);
- try {
- return getWeatherDataFromJson(forecastJsonStr, numDays);
- } catch (JSONException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- e.printStackTrace();
- }
-
- // This will only happen if we fail to read the forecast
- return null;
-
- } catch (IOException e) {
- Log.e(LOG_TAG, "Error ", e);
- // If the code didn't successfully get the weather data, there's no point in attempting
- // to parse it.
- forecastJsonStr = null;
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- if (reader != null) {
- try {
- reader.close();
- } catch (final IOException e) {
- Log.e(LOG_TAG, "Error closing stream", e);
- }
- }
- }
- return null;
- }
-
- /* The date/time conversion code is going to be moved outside the asynctask later,
- * so for convenience we're breaking it out into its own method now.
- */
- private String getReadableDateString(long time) {
- // Because the API returns a unix timestamp (measured in seconds),
- // it must be converted to milliseconds in order to be converted to valid date.
- Date date = new Date(time * 1000);
- SimpleDateFormat format = new SimpleDateFormat("E, MMM d");
- return format.format(date).toString();
- }
-
- /**
- * Prepare the weather high/lows for presentation.
- */
- private String formatHighLows(double high, double low) {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
- String units = preferences.getString(getString(R.string.pref_units_key),
- getString(R.string.pref_units_default));
- // For presentation, assume the user doesn't care about tenths of a degree.
- long roundedHigh = Math.round(high);
- long roundedLow = Math.round(low);
-
- if (units.equals(getString(R.string.pref_units_imperial))) {
- roundedHigh = Math.round(high * 9 / 5 + 32);
- roundedLow = Math.round(low * 9 / 5 + 32);
- }
-
- String highLowStr = roundedHigh + "/" + roundedLow;
- return highLowStr;
- }
-
- /**
- * Take the String representing the complete forecast in JSON Format and
- * pull out the data we need to construct the Strings needed for the wireframes.
- * <p/>
- * Fortunately parsing is easy: constructor takes the JSON string and converts it
- * into an Object hierarchy for us.
- */
- private String[] getWeatherDataFromJson(String forecastJsonStr, int numDays)
- throws JSONException {
- // These are the names of the JSON objects that need to be extracted.
- final String OWM_LIST = "list";
- final String OWM_WEATHER = "weather";
- final String OWM_TEMPERATURE = "temp";
- final String OWM_MAX = "max";
- final String OWM_MIN = "min";
- final String OWM_DATETIME = "dt";
- final String OWM_DESCRIPTION = "main";
-
- JSONObject forecastJson = new JSONObject(forecastJsonStr);
- JSONArray weatherArray = forecastJson.getJSONArray(OWM_LIST);
-
- String[] resultStrs = new String[numDays];
- for (int i = 0; i < weatherArray.length(); i++) {
- // For now, using the format "Day, description, hi/low"
- String day;
- String description;
- String highAndLow;
-
- // Get the JSON object representing the day
- JSONObject dayForecast = weatherArray.getJSONObject(i);
-
- // The date/time is returned as a long. We need to convert that
- // into something human-readable, since most people won't read "1400356800" as
- // "this saturday".
- long dateTime = dayForecast.getLong(OWM_DATETIME);
- day = getReadableDateString(dateTime);
-
- // description is in a child array called "weather", which is 1 element long.
- JSONObject weatherObject = dayForecast.getJSONArray(OWM_WEATHER).getJSONObject(0);
- description = weatherObject.getString(OWM_DESCRIPTION);
-
- // Temperatures are in a child object called "temp". Try not to name variables
- // "temp" when working with temperature. It confuses everybody.
- JSONObject temperatureObject = dayForecast.getJSONObject(OWM_TEMPERATURE);
- double high = temperatureObject.getDouble(OWM_MAX);
- double low = temperatureObject.getDouble(OWM_MIN);
-
- highAndLow = formatHighLows(high, low);
- resultStrs[i] = day + " - " + description + " - " + highAndLow;
- }
-
-// for (String s: resultStrs) {
-// Log.v(LOG_TAG, "Forecast entry: " + s);
-// }
- return resultStrs;
- }
- }
}
--- /dev/null
+/*
+ * Copyright (C) 2014 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 uk.me.njae.sunshine;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+public class Utility {
+ public static String getPreferredLocation(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ return prefs.getString(context.getString(R.string.pref_location_key),
+ context.getString(R.string.pref_location_default));
+ }
+}
package uk.me.njae.sunshine.data;
+import android.content.ContentUris;
+import android.net.Uri;
import android.provider.BaseColumns;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
/**
* Created by neil on 09/11/14.
*/
public class WeatherContract {
+ // The "Content authority" is a name for the entire content provider, similar to the
+ // relationship between a domain name and its website. A convenient string to use for the
+ // content authority is the package name for the app, which is guaranteed to be unique on the
+ // device.
+ public static final String CONTENT_AUTHORITY = "uk.me.njae.sunshine";
+
+ // Use CONTENT_AUTHORITY to create the base of all URI's which apps will use to contact
+ // the content provider.
+ public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
+
+
+ // Format used for storing dates in the database. ALso used for converting those strings
+ // back into date objects for comparison/processing.
+ public static final String DATE_FORMAT = "yyyyMMdd";
+
+ /**
+ * Converts Date class to a string representation, used for easy comparison and database lookup.
+ * @param date The input date
+ * @return a DB-friendly representation of the date, using the format defined in DATE_FORMAT.
+ */
+ public static String getDbDateString(Date date){
+ // Because the API returns a unix timestamp (measured in seconds),
+ // it must be converted to milliseconds in order to be converted to valid date.
+ SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
+ return sdf.format(date);
+ }
+
+ // Possible paths (appended to base content URI for possible URI's)
+ // For instance, content://com.example.android.sunshine.app/weather/ is a valid path for
+ // looking at weather data. content://com.example.android.sunshine.app/givemeroot/ will fail,
+ // as the ContentProvider hasn't been given any information on what to do with "givemeroot".
+ // At least, let's hope not. Don't be that dev, reader. Don't be that dev.
+ public static final String PATH_WEATHER = "weather";
+ public static final String PATH_LOCATION = "location";
+
/* Inner class that defines the table contents of the location table */
public static final class LocationEntry implements BaseColumns {
+ public static final Uri CONTENT_URI =
+ BASE_CONTENT_URI.buildUpon().appendPath(PATH_LOCATION).build();
+
+ public static final String CONTENT_TYPE =
+ "vnd.android.cursor.dir/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;
+
// Table name
public static final String TABLE_NAME = "location";
// map intent, we store the latitude and longitude as returned by openweathermap.
public static final String COLUMN_COORD_LAT = "coord_lat";
public static final String COLUMN_COORD_LONG = "coord_long";
+
+ public static Uri buildLocationUri(long id) {
+ return ContentUris.withAppendedId(CONTENT_URI, id);
+ }
}
/* Inner class that defines the table contents of the weather table */
public static final class WeatherEntry implements BaseColumns {
+ public static final Uri CONTENT_URI =
+ BASE_CONTENT_URI.buildUpon().appendPath(PATH_WEATHER).build();
+
+ public static final String CONTENT_TYPE =
+ "vnd.android.cursor.dir/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;
+
+
public static final String TABLE_NAME = "weather";
// Column with the foreign key into the location table.
// Degrees are meteorological degrees (e.g, 0 is north, 180 is south). Stored as floats.
public static final String COLUMN_DEGREES = "degrees";
+
+ public static Uri buildWeatherUri(long id) {
+ return ContentUris.withAppendedId(CONTENT_URI, id);
+ }
+
+ public static Uri buildWeatherLocation(String locationSetting) {
+ return CONTENT_URI.buildUpon().appendPath(locationSetting).build();
+ }
+
+ public static Uri buildWeatherLocationWithStartDate(
+ String locationSetting, String startDate) {
+ return CONTENT_URI.buildUpon().appendPath(locationSetting)
+ .appendQueryParameter(COLUMN_DATETEXT, startDate).build();
+ }
+
+ public static Uri buildWeatherLocationWithDate(String locationSetting, String date) {
+ return CONTENT_URI.buildUpon().appendPath(locationSetting).appendPath(date).build();
+ }
+
+ public static String getLocationSettingFromUri(Uri uri) {
+ return uri.getPathSegments().get(1);
+ }
+
+ public static String getDateFromUri(Uri uri) {
+ return uri.getPathSegments().get(2);
+ }
+
+ public static String getStartDateFromUri(Uri uri) {
+ return uri.getQueryParameter(COLUMN_DATETEXT);
+ }
}
}
--- /dev/null
+package uk.me.njae.sunshine.data;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+
+/**
+ * Created by neil on 09/11/14.
+ */
+public class WeatherProvider extends ContentProvider {
+
+ // The URI Matcher used by this content provider.
+ private static final UriMatcher sUriMatcher = buildUriMatcher();
+
+ private static final int WEATHER = 100;
+ private static final int WEATHER_WITH_LOCATION = 101;
+ private static final int WEATHER_WITH_LOCATION_AND_DATE = 102;
+ private static final int LOCATION = 300;
+ private static final int LOCATION_ID = 301;
+
+ private WeatherDbHelper mOpenHelper;
+
+ private static UriMatcher buildUriMatcher() {
+ // I know what you're thinking. Why create a UriMatcher when you can use regular
+ // expressions instead? Because you're not crazy, that's why.
+
+ // All paths added to the UriMatcher have a corresponding code to return when a match is
+ // found. The code passed into the constructor represents the code to return for the root
+ // URI. It's common to use NO_MATCH as the code for this case.
+ final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+ final String authority = WeatherContract.CONTENT_AUTHORITY;
+
+ // For each type of URI you want to add, create a corresponding code.
+ matcher.addURI(authority, WeatherContract.PATH_WEATHER, WEATHER);
+ matcher.addURI(authority, WeatherContract.PATH_WEATHER + "/*", WEATHER_WITH_LOCATION);
+ matcher.addURI(authority, WeatherContract.PATH_WEATHER + "/*/*", WEATHER_WITH_LOCATION_AND_DATE);
+
+ matcher.addURI(authority, WeatherContract.PATH_LOCATION, LOCATION);
+ matcher.addURI(authority, WeatherContract.PATH_LOCATION + "/#", LOCATION_ID);
+
+ return matcher;
+ }
+
+
+ private static final SQLiteQueryBuilder sWeatherByLocationSettingQueryBuilder;
+
+ static {
+ sWeatherByLocationSettingQueryBuilder = new SQLiteQueryBuilder();
+ sWeatherByLocationSettingQueryBuilder.setTables(
+ WeatherContract.WeatherEntry.TABLE_NAME + " INNER JOIN " +
+ WeatherContract.LocationEntry.TABLE_NAME +
+ " ON " + WeatherContract.WeatherEntry.TABLE_NAME +
+ "." + WeatherContract.WeatherEntry.COLUMN_LOC_KEY +
+ " = " + WeatherContract.LocationEntry.TABLE_NAME +
+ "." + WeatherContract.LocationEntry._ID);
+ }
+
+ private static final String sLocationSettingSelection =
+ WeatherContract.LocationEntry.TABLE_NAME+
+ "." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? ";
+ private static final String sLocationSettingWithStartDateSelection =
+ WeatherContract.LocationEntry.TABLE_NAME+
+ "." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? AND " +
+ WeatherContract.WeatherEntry.COLUMN_DATETEXT + " >= ? ";
+ private static final String sLocationSettingAndDaySelection =
+ WeatherContract.LocationEntry.TABLE_NAME +
+ "." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? AND " +
+ WeatherContract.WeatherEntry.COLUMN_DATETEXT + " = ? ";
+
+ private Cursor getWeatherByLocationSetting(Uri uri, String[] projection, String sortOrder) {
+ String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
+ String startDate = WeatherContract.WeatherEntry.getStartDateFromUri(uri);
+
+ String[] selectionArgs;
+ String selection;
+
+ if (startDate == null) {
+ selection = sLocationSettingSelection;
+ selectionArgs = new String[]{locationSetting};
+ } else {
+ selectionArgs = new String[]{locationSetting, startDate};
+ selection = sLocationSettingWithStartDateSelection;
+ }
+
+ return sWeatherByLocationSettingQueryBuilder.query(mOpenHelper.getReadableDatabase(),
+ projection,
+ selection,
+ selectionArgs,
+ null,
+ null,
+ sortOrder
+ );
+ }
+
+ private Cursor getWeatherByLocationSettingAndDate(
+ Uri uri, String[] projection, String sortOrder) {
+ String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
+ String date = WeatherContract.WeatherEntry.getDateFromUri(uri);
+
+ return sWeatherByLocationSettingQueryBuilder.query(mOpenHelper.getReadableDatabase(),
+ projection,
+ sLocationSettingAndDaySelection,
+ new String[]{locationSetting, date},
+ null,
+ null,
+ sortOrder
+ );
+ }
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new WeatherDbHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ // Here's the switch statement that, given a URI, will determine what kind of request it is,
+ // and query the database accordingly.
+ Cursor retCursor;
+ switch (sUriMatcher.match(uri)) {
+ // "weather/*/*"
+ case WEATHER_WITH_LOCATION_AND_DATE:
+ {
+ retCursor = getWeatherByLocationSettingAndDate(uri, projection, sortOrder);
+ break;
+ }
+ // "weather/*"
+ case WEATHER_WITH_LOCATION: {
+ retCursor = getWeatherByLocationSetting(uri, projection, sortOrder);
+ break;
+ }
+ // "weather"
+ case WEATHER: {
+ retCursor = mOpenHelper.getReadableDatabase().query(
+ WeatherContract.WeatherEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArgs,
+ null,
+ null,
+ sortOrder
+ );
+ break;
+ }
+ // "location/*"
+ case LOCATION_ID: {
+ retCursor = mOpenHelper.getReadableDatabase().query(
+ WeatherContract.LocationEntry.TABLE_NAME,
+ projection,
+ WeatherContract.LocationEntry._ID + " = '" + ContentUris.parseId(uri) + "'",
+ null,
+ null,
+ null,
+ sortOrder
+ );
+ break;
+ }
+ // "location"
+ case LOCATION: {
+ retCursor = mOpenHelper.getReadableDatabase().query(
+ WeatherContract.LocationEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArgs,
+ null,
+ null,
+ sortOrder);
+ break;
+ }
+
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ retCursor.setNotificationUri(getContext().getContentResolver(), uri);
+ return retCursor;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ // Use the Uri Matcher to determine what kind of URI this is.
+ final int match = sUriMatcher.match(uri);
+
+ switch (match) {
+ case WEATHER_WITH_LOCATION_AND_DATE:
+ return WeatherContract.WeatherEntry.CONTENT_ITEM_TYPE;
+ case WEATHER_WITH_LOCATION:
+ return WeatherContract.WeatherEntry.CONTENT_TYPE;
+ case WEATHER:
+ return WeatherContract.WeatherEntry.CONTENT_TYPE;
+ case LOCATION:
+ return WeatherContract.LocationEntry.CONTENT_TYPE;
+ case LOCATION_ID:
+ return WeatherContract.LocationEntry.CONTENT_ITEM_TYPE;
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues contentValues) {
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final int match = sUriMatcher.match(uri);
+ Uri returnUri;
+
+ switch (match) {
+ case WEATHER: {
+ long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, contentValues);
+ if ( _id > 0 )
+ returnUri = WeatherContract.WeatherEntry.buildWeatherUri(_id);
+ else
+ throw new android.database.SQLException("Failed to insert row into " + uri);
+ break;
+ }
+ case LOCATION: {
+ long _id = db.insert(WeatherContract.LocationEntry.TABLE_NAME, null, contentValues);
+ if ( _id > 0 )
+ returnUri = WeatherContract.LocationEntry.buildLocationUri(_id);
+ else
+ throw new android.database.SQLException("Failed to insert row into " + uri);
+ break;
+ }
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ getContext().getContentResolver().notifyChange(returnUri, null);
+ return returnUri;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final int match = sUriMatcher.match(uri);
+ int rowsDeleted = 0;
+
+ switch (match) {
+ case WEATHER: {
+ rowsDeleted = db.delete(WeatherContract.WeatherEntry.TABLE_NAME, selection, selectionArgs);
+ break;
+ }
+ case LOCATION: {
+ rowsDeleted = db.delete(WeatherContract.LocationEntry.TABLE_NAME, selection, selectionArgs);
+ break;
+ }
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ if (selection == null || rowsDeleted != 0) {
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ return rowsDeleted;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final int match = sUriMatcher.match(uri);
+ int rowsUpdated;
+
+ switch (match) {
+ case WEATHER:
+ rowsUpdated = db.update(WeatherContract.WeatherEntry.TABLE_NAME, contentValues, selection,
+ selectionArgs);
+ break;
+ case LOCATION:
+ rowsUpdated = db.update(WeatherContract.LocationEntry.TABLE_NAME, contentValues, selection,
+ selectionArgs);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ if (rowsUpdated != 0) {
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ return rowsUpdated;
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final int match = sUriMatcher.match(uri);
+ switch (match) {
+ case WEATHER:
+ db.beginTransaction();
+ int returnCount = 0;
+ try {
+ for (ContentValues value : values) {
+ long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, value);
+ if (_id != -1) {
+ returnCount++;
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+ return returnCount;
+ default:
+ return super.bulkInsert(uri, values);
+ }
+ }
+}