From 31bb51070a4598daf3f40175dda9ebea803bc52b Mon Sep 17 00:00:00 2001 From: Protino Date: Tue, 8 Nov 2016 20:23:20 +0530 Subject: [PATCH] Display error messages when no data --- app/build.gradle | 1 + .../sunshine_v2/Activity/MainActivity.java | 1 + .../Activity/SettingsActivity.java | 35 +- .../sunshine_v2/Data/FetchWeatherTask.java | 343 -------------- .../Fragment/DetailActivityFragment.java | 6 +- .../sunshine_v2/Fragment/DetailFragment.java | 7 +- .../Fragment/ForecastFragment.java | 55 ++- .../calgen/prodek/sunshine_v2/Utility.java | 11 +- .../sunshine_v2/adapter/ForecastAdapter.java | 6 +- .../sunshine_v2/sync/SunshineSyncAdapter.java | 417 ++++++++++-------- app/src/main/res/values/strings.xml | 2 + 11 files changed, 307 insertions(+), 577 deletions(-) delete mode 100644 app/src/main/java/com/calgen/prodek/sunshine_v2/Data/FetchWeatherTask.java diff --git a/app/build.gradle b/app/build.gradle index a83a614..e97c478 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,4 +29,5 @@ dependencies { compile 'com.android.support:appcompat-v7:23.2.0' compile 'com.android.support:design:23.2.0' compile 'com.android.support:support-v4:23.2.0' + compile 'com.android.support:support-annotations:23.2.0' } diff --git a/app/src/main/java/com/calgen/prodek/sunshine_v2/Activity/MainActivity.java b/app/src/main/java/com/calgen/prodek/sunshine_v2/Activity/MainActivity.java index 1844146..0213635 100644 --- a/app/src/main/java/com/calgen/prodek/sunshine_v2/Activity/MainActivity.java +++ b/app/src/main/java/com/calgen/prodek/sunshine_v2/Activity/MainActivity.java @@ -99,6 +99,7 @@ private void openPreferredLocationInMap() { } } + @Override protected void onResume() { super.onResume(); diff --git a/app/src/main/java/com/calgen/prodek/sunshine_v2/Activity/SettingsActivity.java b/app/src/main/java/com/calgen/prodek/sunshine_v2/Activity/SettingsActivity.java index 19ef3f6..3edcceb 100644 --- a/app/src/main/java/com/calgen/prodek/sunshine_v2/Activity/SettingsActivity.java +++ b/app/src/main/java/com/calgen/prodek/sunshine_v2/Activity/SettingsActivity.java @@ -1,5 +1,6 @@ package com.calgen.prodek.sunshine_v2.activity; +import android.content.SharedPreferences; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.ListPreference; @@ -9,11 +10,14 @@ import android.view.MenuItem; import com.calgen.prodek.sunshine_v2.R; +import com.calgen.prodek.sunshine_v2.Utility; +import com.calgen.prodek.sunshine_v2.data.WeatherContract; +import com.calgen.prodek.sunshine_v2.sync.SunshineSyncAdapter; /** * Created by Gurupad on 15-Jun-16. */ -public class SettingsActivity extends AppCompactPreferenceActivity implements Preference.OnPreferenceChangeListener { +public class SettingsActivity extends AppCompactPreferenceActivity implements Preference.OnPreferenceChangeListener, SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = SettingsActivity.class.getSimpleName(); @@ -61,6 +65,7 @@ private void bindPreferenceSummaryToValue(Preference preference) { .getString(preference.getKey(), "")); } + @Override public boolean onPreferenceChange(Preference preference, Object value) { String stringValue = value.toString(); @@ -75,9 +80,33 @@ public boolean onPreferenceChange(Preference preference, Object value) { } } else { // For other preferences, set the summary to the value's simple string representation. - if (!(preference instanceof CheckBoxPreference)) - preference.setSummary(stringValue); + if (!(preference instanceof CheckBoxPreference)) preference.setSummary(stringValue); + } return true; } + + @Override + protected void onResume() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + sp.registerOnSharedPreferenceChangeListener(this); + super.onResume(); + } + + @Override + protected void onPause() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + sp.unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(getString(R.string.pref_location_key))) { + Utility.resetLocationStatus(this); + SunshineSyncAdapter.syncImmediately(this); + } else if (key.equals(getString(R.string.pref_temperature_key))) { + getContentResolver().notifyChange(WeatherContract.WeatherEntry.CONTENT_URI, null); + } + } } diff --git a/app/src/main/java/com/calgen/prodek/sunshine_v2/Data/FetchWeatherTask.java b/app/src/main/java/com/calgen/prodek/sunshine_v2/Data/FetchWeatherTask.java deleted file mode 100644 index 52e2b9a..0000000 --- a/app/src/main/java/com/calgen/prodek/sunshine_v2/Data/FetchWeatherTask.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * 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 com.calgen.prodek.sunshine_v2.data; - -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.net.Uri; -import android.os.AsyncTask; -import android.text.format.Time; -import android.util.Log; - -import com.calgen.prodek.sunshine_v2.BuildConfig; -import com.calgen.prodek.sunshine_v2.data.WeatherContract.WeatherEntry; - -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.util.Vector; - -public class FetchWeatherTask extends AsyncTask { - - private final String LOG_TAG = FetchWeatherTask.class.getSimpleName(); - private final Context mContext; - - public FetchWeatherTask(Context context) { - mContext = context; - } - - /** - * 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. - */ - public long addLocation(String locationSetting, String cityName, double lat, double lon) { - long locationId; - Cursor cursor = mContext.getContentResolver().query( - WeatherContract.LocationEntry.CONTENT_URI, - new String[]{WeatherContract.LocationEntry._ID}, - WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ?", - new String[]{locationSetting}, - null); - if (cursor.moveToFirst()) - locationId = cursor.getLong(cursor.getColumnIndex(WeatherContract.LocationEntry._ID)); - else { - ContentValues values = new ContentValues(); - values.put(WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING, locationSetting); - values.put(WeatherContract.LocationEntry.COLUMN_CITY_NAME, cityName); - values.put(WeatherContract.LocationEntry.COLUMN_COORD_LAT, lat); - values.put(WeatherContract.LocationEntry.COLUMN_COORD_LONG, lon); - Uri insertedUri = mContext.getContentResolver().insert(WeatherContract.LocationEntry.CONTENT_URI, values); - locationId = ContentUris.parseId(insertedUri); - } - cursor.close(); - return locationId; - } - - - /** - * 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. - *

- * Fortunately parsing is easy: constructor takes the JSON string and converts it - * into an Object hierarchy for us. - */ - public void getWeatherDataFromJson(String forecastJsonStr, - String locationSetting) - throws JSONException { - - // Now we have a String representing the complete forecast in JSON Format. - // Fortunately parsing is easy: constructor takes the JSON string and converts it - // into an Object hierarchy for us. - - // 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"; - - // Location coordinate - final String OWM_LATITUDE = "lat"; - final String OWM_LONGITUDE = "lon"; - - // Weather information. Each day's forecast info is an element of the "list" array. - final String OWM_LIST = "list"; - - 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"; - - try { - 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 cityCoord = cityJson.getJSONObject(OWM_COORD); - double cityLatitude = cityCoord.getDouble(OWM_LATITUDE); - double cityLongitude = cityCoord.getDouble(OWM_LONGITUDE); - - long locationId = addLocation(locationSetting, cityName, cityLatitude, cityLongitude); - - // Insert the new weather information into the database - Vector cVVector = new Vector(weatherArray.length()); - - // OWM returns daily forecasts based upon the local time of the city that is being - // asked for, which means that we need to know the GMT offset to translate this data - // properly. - - // Since this data is also sent in-order and the first day is always the - // current day, we're going to take advantage of that to get a nice - // normalized UTC date for all of our weather. - - Time dayTime = new Time(); - dayTime.setToNow(); - - // we start at the day returned by local time. Otherwise this is a mess. - int julianStartDay = Time.getJulianDay(System.currentTimeMillis(), dayTime.gmtoff); - - // now we work exclusively in UTC - dayTime = new Time(); - - 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); - - // Cheating to convert this to UTC time, which is what we want anyhow - dateTime = dayTime.setJulianDay(julianStartDay + i); - - 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_DATE, dateTime); - 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); - } - - // add to database - if (cVVector.size() > 0) { - // Student: call bulkInsert to add the weatherEntries to the database here - ContentValues[] values = new ContentValues[cVVector.size()]; - cVVector.toArray(values); - mContext.getContentResolver().bulkInsert(WeatherEntry.CONTENT_URI, values); - } - - // Sort order: Ascending, by date. - String sortOrder = WeatherEntry.COLUMN_DATE + " ASC"; - Uri weatherForLocationUri = WeatherEntry.buildWeatherLocationWithStartDate( - locationSetting, System.currentTimeMillis()); - - - Cursor cur = mContext.getContentResolver().query(weatherForLocationUri, - null, null, null, sortOrder); - - cVVector = new Vector(cur.getCount()); - if ( cur.moveToFirst() ) { - do { - ContentValues cv = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(cur, cv); - cVVector.add(cv); - } while (cur.moveToNext()); - } - - Log.d(LOG_TAG, "FetchWeatherTask Complete. " + cVVector.size() + " Inserted"); - - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - e.printStackTrace(); - } - } - - @Override - protected Void 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 = 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 = "q"; - final String FORMAT_PARAM = "mode"; - final String UNITS_PARAM = "units"; - final String DAYS_PARAM = "cnt"; - final String API_KEY = "appid"; - - 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)) - .appendQueryParameter(API_KEY, BuildConfig.OPEN_WEATHER_MAP_API_KEY) - .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(); - getWeatherDataFromJson(forecastJsonStr, locationQuery); - } 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; - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - e.printStackTrace(); - } 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; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/DetailActivityFragment.java b/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/DetailActivityFragment.java index eee1d37..ab76e59 100644 --- a/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/DetailActivityFragment.java +++ b/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/DetailActivityFragment.java @@ -165,15 +165,13 @@ public void onLoadFinished(Loader loader, Cursor data) { mDescriptionView.setText(description); // Read high temperature from cursor and update view - boolean isMetric = Utility.isMetric(getActivity()); - double high = data.getDouble(COL_WEATHER_MAX_TEMP); - String highString = Utility.formatTemperature(getActivity(), high, isMetric); + String highString = Utility.formatTemperature(getActivity(), high); mHighTempView.setText(highString); // Read low temperature from cursor and update view double low = data.getDouble(COL_WEATHER_MIN_TEMP); - String lowString = Utility.formatTemperature(getActivity(), low, isMetric); + String lowString = Utility.formatTemperature(getActivity(), low); mLowTempView.setText(lowString); // Read humidity from cursor and update view diff --git a/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/DetailFragment.java b/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/DetailFragment.java index b68e25c..ade0fb2 100644 --- a/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/DetailFragment.java +++ b/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/DetailFragment.java @@ -172,16 +172,13 @@ public void onLoadFinished(Loader loader, Cursor data) { String description = data.getString(COL_WEATHER_DESC); mDescriptionView.setText(description); - // Read high temperature from cursor and update view - boolean isMetric = Utility.isMetric(getActivity()); - double high = data.getDouble(COL_WEATHER_MAX_TEMP); - String highString = Utility.formatTemperature(getActivity(), high, isMetric); + String highString = Utility.formatTemperature(getActivity(), high); mHighTempView.setText(highString); // Read low temperature from cursor and update view double low = data.getDouble(COL_WEATHER_MIN_TEMP); - String lowString = Utility.formatTemperature(getActivity(), low, isMetric); + String lowString = Utility.formatTemperature(getActivity(), low); mLowTempView.setText(lowString); // Read humidity from cursor and update view diff --git a/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/ForecastFragment.java b/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/ForecastFragment.java index a598070..e744e73 100644 --- a/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/ForecastFragment.java +++ b/app/src/main/java/com/calgen/prodek/sunshine_v2/Fragment/ForecastFragment.java @@ -4,7 +4,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; @@ -23,6 +22,7 @@ import com.calgen.prodek.sunshine_v2.data.WeatherContract; import com.calgen.prodek.sunshine_v2.sync.SunshineSyncAdapter.LocationStatus; +import static com.calgen.prodek.sunshine_v2.sync.SunshineSyncAdapter.LOCATION_STATUS_INVALID; import static com.calgen.prodek.sunshine_v2.sync.SunshineSyncAdapter.LOCATION_STATUS_SERVER_DOWN; import static com.calgen.prodek.sunshine_v2.sync.SunshineSyncAdapter.LOCATION_STATUS_SERVER_INVALID; @@ -91,7 +91,6 @@ public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } - public void setUseTodayLayout(boolean useTodayLayout) { mUseTodayLayout = useTodayLayout; if (mForecastAdapter != null) { @@ -105,12 +104,15 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, mForecastAdapter = new ForecastAdapter(getActivity(), null, 0); mForecastAdapter.setmUseTodayLayout(mUseTodayLayout); View rootView = inflater.inflate(R.layout.fragment_main, container, false); + View emptyView = rootView.findViewById(R.id.empty_view); mListView = (ListView) rootView.findViewById(R.id.listview_forecast); mListView.setAdapter(mForecastAdapter); + mListView.setEmptyView(emptyView); //set click listeners on the list items mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override public void onItemClick(AdapterView adapterView, View view, int position, long l) { // CursorAdapter returns a cursor at the correct position for getItem(), or null @@ -138,7 +140,6 @@ public void onItemClick(AdapterView adapterView, View view, int position, long l return rootView; } - @Override public Loader onCreateLoader(int id, Bundle args) { String locationSetting = Utility.getPreferredLocation(getActivity()); @@ -167,6 +168,16 @@ public void onLoadFinished(Loader loader, Cursor data) { updateEmptyView(); } + @Override + public void onLoaderReset(Loader loader) { + mForecastAdapter.swapCursor(null); + } + + // since we read the location when we create the loader, all we need to do is restart things + public void onLocationChanged() { + getLoaderManager().restartLoader(MY_LOADER_ID, null, this); + } + private void updateEmptyView() { if (mForecastAdapter.getCount() == 0) { mEmptyView = (TextView) getView().findViewById(R.id.empty_view); @@ -180,6 +191,9 @@ private void updateEmptyView() { case LOCATION_STATUS_SERVER_INVALID: message = R.string.empty_forecast_list_server_error; break; + case LOCATION_STATUS_INVALID: + message = R.string.empty_forecast_list_invalid_location; + break; default: if (!Utility.isNetworkAvailable(getContext())) { message = R.string.empty_forecast_list_no_network; @@ -190,40 +204,23 @@ private void updateEmptyView() { } } - @Override - public void onLoaderReset(Loader loader) { - mForecastAdapter.swapCursor(null); - } - - public void onLocationChanged() { - getLoaderManager().restartLoader(MY_LOADER_ID, null, this); - } - - @Override - public void onResume() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); - sp.registerOnSharedPreferenceChangeListener(this); - super.onResume(); - } - - @Override - public void onPause() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); - sp.unregisterOnSharedPreferenceChangeListener(this); - super.onPause(); - } - @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(getString(R.string.pref_location_status_key))) updateEmptyView(); + if (key.equals(getString(R.string.pref_location_status_key))) { + updateEmptyView(); + } } - + /** + * A callback interface that all activities containing this fragment must + * implement. This mechanism allows activities to be notified of item + * selections. + */ public interface Callback { /** * DetailFragmentCallback for when an item has been selected. */ - void onItemSelected(Uri dateUri); + public void onItemSelected(Uri dateUri); } } diff --git a/app/src/main/java/com/calgen/prodek/sunshine_v2/Utility.java b/app/src/main/java/com/calgen/prodek/sunshine_v2/Utility.java index 1e9c948..25f6e19 100644 --- a/app/src/main/java/com/calgen/prodek/sunshine_v2/Utility.java +++ b/app/src/main/java/com/calgen/prodek/sunshine_v2/Utility.java @@ -37,9 +37,9 @@ public static boolean isNotificationOn(Context context) { Boolean.parseBoolean(context.getString(R.string.pref_notification_default))); } - public static String formatTemperature(Context context, double temperature, boolean isMetric) { + public static String formatTemperature(Context context, double temperature) { double temp; - if (!isMetric) { + if (!isMetric(context)) { temp = 9 * temperature / 5 + 32; } else { temp = temperature; @@ -306,4 +306,11 @@ int getLocationStatus(Context context) { return sp.getInt(context.getString(R.string.pref_location_status_key), SunshineSyncAdapter.LOCATION_STATUS_UNKNOWN); } + + public static void resetLocationStatus(Context context) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor spe = sp.edit(); + spe.putInt(context.getString(R.string.pref_location_status_key), SunshineSyncAdapter.LOCATION_STATUS_UNKNOWN); + spe.apply(); + } } diff --git a/app/src/main/java/com/calgen/prodek/sunshine_v2/adapter/ForecastAdapter.java b/app/src/main/java/com/calgen/prodek/sunshine_v2/adapter/ForecastAdapter.java index 981aefd..239a5d8 100644 --- a/app/src/main/java/com/calgen/prodek/sunshine_v2/adapter/ForecastAdapter.java +++ b/app/src/main/java/com/calgen/prodek/sunshine_v2/adapter/ForecastAdapter.java @@ -33,7 +33,7 @@ public ForecastAdapter(Context context, Cursor c, int flags) { */ private String formatHighLows(double high, double low) { boolean isMetric = Utility.isMetric(mContext); - String highLowStr = Utility.formatTemperature(mContext, high, isMetric) + "/" + Utility.formatTemperature(mContext, low, isMetric); + String highLowStr = Utility.formatTemperature(mContext, high) + "/" + Utility.formatTemperature(mContext, low); return highLowStr; } @@ -93,11 +93,11 @@ public void bindView(View view, Context context, Cursor cursor) { // Read high temperature from cursor double high = cursor.getDouble(ForecastFragment.COL_WEATHER_MAX_TEMP); - viewHolder.highTempView.setText(Utility.formatTemperature(mContext, high, isMetric)); + viewHolder.highTempView.setText(Utility.formatTemperature(mContext, high)); // Read low temperature from cursor double low = cursor.getDouble(ForecastFragment.COL_WEATHER_MIN_TEMP); - viewHolder.lowTempView.setText(Utility.formatTemperature(mContext, low, isMetric)); + viewHolder.lowTempView.setText(Utility.formatTemperature(mContext, low)); } diff --git a/app/src/main/java/com/calgen/prodek/sunshine_v2/sync/SunshineSyncAdapter.java b/app/src/main/java/com/calgen/prodek/sunshine_v2/sync/SunshineSyncAdapter.java index e3be831..805456a 100644 --- a/app/src/main/java/com/calgen/prodek/sunshine_v2/sync/SunshineSyncAdapter.java +++ b/app/src/main/java/com/calgen/prodek/sunshine_v2/sync/SunshineSyncAdapter.java @@ -14,8 +14,10 @@ import android.content.SharedPreferences; import android.content.SyncRequest; import android.content.SyncResult; +import android.content.res.Resources; import android.database.Cursor; -import android.database.DatabaseUtils; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -29,7 +31,7 @@ import com.calgen.prodek.sunshine_v2.BuildConfig; import com.calgen.prodek.sunshine_v2.R; import com.calgen.prodek.sunshine_v2.Utility; -import com.calgen.prodek.sunshine_v2.activity.DetailActivity; +import com.calgen.prodek.sunshine_v2.activity.MainActivity; import com.calgen.prodek.sunshine_v2.data.WeatherContract; import org.json.JSONArray; @@ -37,6 +39,7 @@ import org.json.JSONObject; import java.io.BufferedReader; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -44,47 +47,57 @@ import java.lang.annotation.RetentionPolicy; import java.net.HttpURLConnection; import java.net.URL; -import java.util.Calendar; import java.util.Vector; -public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { - public static final int SYNC_INTERVAL = 10800; +public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter { + // Interval at which to sync with the weather, in seconds. + // 60 seconds (1 minute) * 180 = 3 hours + public static final int SYNC_INTERVAL = 60 * 180; public static final int SYNC_FLEXTIME = SYNC_INTERVAL / 3; - - - @Retention(RetentionPolicy.SOURCE) - @IntDef({LOCATION_STATUS_OK, LOCATION_STATUS_SERVER_DOWN, LOCATION_STATUS_SERVER_INVALID, LOCATION_STATUS_UNKNOWN}) - public @interface LocationStatus { - } - - //Constants for location status public static final int LOCATION_STATUS_OK = 0; public static final int LOCATION_STATUS_SERVER_DOWN = 1; public static final int LOCATION_STATUS_SERVER_INVALID = 2; public static final int LOCATION_STATUS_UNKNOWN = 3; - + public static final int LOCATION_STATUS_INVALID = 4; + private static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24; + private static final int WEATHER_NOTIFICATION_ID = 3004; private static final String[] NOTIFY_WEATHER_PROJECTION = new String[]{ WeatherContract.WeatherEntry.COLUMN_WEATHER_ID, - WeatherContract.WeatherEntry.COLUMN_DATE, WeatherContract.WeatherEntry.COLUMN_MAX_TEMP, WeatherContract.WeatherEntry.COLUMN_MIN_TEMP, WeatherContract.WeatherEntry.COLUMN_SHORT_DESC }; // these indices must match the projection private static final int INDEX_WEATHER_ID = 0; - private static final int INDEX_WEATHER_DATE = 1; - private static final int INDEX_MAX_TEMP = 2; - private static final int INDEX_MIN_TEMP = 3; - private static final int INDEX_SHORT_DESC = 4; - private static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24; - private static final int WEATHER_NOTIFICATION_ID = 3004; - public final String TAG = SunshineSyncAdapter.class.getSimpleName(); + private static final int INDEX_MAX_TEMP = 1; + private static final int INDEX_MIN_TEMP = 2; + private static final int INDEX_SHORT_DESC = 3; + public final String LOG_TAG = SunshineSyncAdapter.class.getSimpleName(); public SunshineSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); } + /** + * Helper method to schedule the sync adapter periodic execution + */ + public static void configurePeriodicSync(Context context, int syncInterval, int flexTime) { + Account account = getSyncAccount(context); + String authority = context.getString(R.string.content_authority); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // we can enable inexact timers in our periodic sync + SyncRequest request = new SyncRequest.Builder(). + syncPeriodic(syncInterval, flexTime). + setSyncAdapter(account, authority). + setExtras(new Bundle()).build(); + ContentResolver.requestSync(request); + } else { + ContentResolver.addPeriodicSync(account, + authority, new Bundle(), syncInterval); + } + } + /** * Helper method to have the sync adapter sync immediately * @@ -137,25 +150,6 @@ public static Account getSyncAccount(Context context) { return newAccount; } - /** - * Helper method to schedule the sync adapter periodic execution - */ - public static void configurePeriodicSync(Context context, int syncInterval, int flexTime) { - Account account = getSyncAccount(context); - String authority = context.getString(R.string.content_authority); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - // we can enable inexact timers in our periodic sync - SyncRequest request = new SyncRequest.Builder(). - syncPeriodic(syncInterval, flexTime). - setSyncAdapter(account, authority). - setExtras(new Bundle()).build(); - ContentResolver.requestSync(request); - } else { - ContentResolver.addPeriodicSync(account, - authority, new Bundle(), syncInterval); - } - } - private static void onAccountCreated(Account newAccount, Context context) { /* * Since we've created an account @@ -177,29 +171,40 @@ public static void initializeSyncAdapter(Context context) { getSyncAccount(context); } + /** + * Sets the location status into shared preference. This function should not be called from + * the UI thread because it uses commit to write to the shared preferences. + * + * @param c Context to get the PreferenceManager from. + * @param locationStatus The IntDef value to set + */ + static private void setLocationStatus(Context c, @LocationStatus int locationStatus) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(c); + SharedPreferences.Editor spe = sp.edit(); + spe.putInt(c.getString(R.string.pref_location_status_key), locationStatus); + spe.commit(); + } + @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { - + Log.d(LOG_TAG, "Starting sync"); String locationQuery = Utility.getPreferredLocation(getContext()); - // If there's no zip code, there's nothing to look up. Verify size of params. - if (locationQuery == null || locationQuery.length() == 0) { - return; - } + // 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 forecastJsonStr = null; String format = "json"; String units = "metric"; - int numDays = 7; + int numDays = 14; try { // Construct the URL for the OpenWeatherMap query - // Possible parameters are avaiable at OWM's forecast API page, at + // Possible parameters are available 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?"; @@ -207,7 +212,7 @@ public void onPerformSync(Account account, Bundle extras, String authority, Cont final String FORMAT_PARAM = "mode"; final String UNITS_PARAM = "units"; final String DAYS_PARAM = "cnt"; - final String API_KEY = "appid"; + final String API_KEY = "APPID"; Uri builtUri = Uri.parse(FORECAST_BASE_URL).buildUpon() .appendQueryParameter(QUERY_PARAM, locationQuery) @@ -224,10 +229,9 @@ public void onPerformSync(Account account, Bundle extras, String authority, Cont urlConnection.setRequestMethod("GET"); urlConnection.connect(); - // Read the input stream into a String InputStream inputStream = urlConnection.getInputStream(); - StringBuilder buffer = new StringBuilder(); + StringBuffer buffer = new StringBuffer(); if (inputStream == null) { // Nothing to do. return; @@ -239,7 +243,7 @@ public void onPerformSync(Account account, Bundle extras, String authority, Cont // 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).append("\n"); + buffer.append(line + "\n"); } if (buffer.length() == 0) { @@ -249,14 +253,18 @@ public void onPerformSync(Account account, Bundle extras, String authority, Cont } forecastJsonStr = buffer.toString(); getWeatherDataFromJson(forecastJsonStr, locationQuery); - setLocationStatus(getContext(), LOCATION_STATUS_OK); + } catch (FileNotFoundException e) { + Log.e(LOG_TAG, "Error", e); + setLocationStatus(getContext(), LOCATION_STATUS_INVALID); } catch (IOException e) { - Log.e(TAG, "Error ", 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. setLocationStatus(getContext(), LOCATION_STATUS_SERVER_DOWN); } catch (JSONException e) { - Log.e(TAG, e.getMessage(), e); - setLocationStatus(getContext(), LOCATION_STATUS_SERVER_INVALID); + Log.e(LOG_TAG, e.getMessage(), e); e.printStackTrace(); + setLocationStatus(getContext(), LOCATION_STATUS_SERVER_INVALID); } finally { if (urlConnection != null) { urlConnection.disconnect(); @@ -265,53 +273,21 @@ public void onPerformSync(Account account, Bundle extras, String authority, Cont try { reader.close(); } catch (final IOException e) { - Log.e(TAG, "Error closing stream", e); + Log.e(LOG_TAG, "Error closing stream", e); } } } } - /** - * 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. - */ - public long addLocation(String locationSetting, String cityName, double lat, double lon) { - long locationId; - Cursor cursor = getContext().getContentResolver().query( - WeatherContract.LocationEntry.CONTENT_URI, - new String[]{WeatherContract.LocationEntry._ID}, - WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ?", - new String[]{locationSetting}, - null); - if (cursor.moveToFirst()) - locationId = cursor.getLong(cursor.getColumnIndex(WeatherContract.LocationEntry._ID)); - else { - ContentValues values = new ContentValues(); - values.put(WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING, locationSetting); - values.put(WeatherContract.LocationEntry.COLUMN_CITY_NAME, cityName); - values.put(WeatherContract.LocationEntry.COLUMN_COORD_LAT, lat); - values.put(WeatherContract.LocationEntry.COLUMN_COORD_LONG, lon); - Uri insertedUri = getContext().getContentResolver().insert(WeatherContract.LocationEntry.CONTENT_URI, values); - locationId = ContentUris.parseId(insertedUri); - } - cursor.close(); - return locationId; - } - /** * 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. - *

+ *

* Fortunately parsing is easy: constructor takes the JSON string and converts it * into an Object hierarchy for us. */ - public void getWeatherDataFromJson(String forecastJsonStr, - String locationSetting) + private void getWeatherDataFromJson(String forecastJsonStr, + String locationSetting) throws JSONException { // Now we have a String representing the complete forecast in JSON Format. @@ -346,8 +322,31 @@ public void getWeatherDataFromJson(String forecastJsonStr, final String OWM_DESCRIPTION = "main"; final String OWM_WEATHER_ID = "id"; + final String OWM_MESSAGE_CODE = "cod"; + try { JSONObject forecastJson = new JSONObject(forecastJsonStr); + + // Check for error + if (forecastJson.has(OWM_MESSAGE_CODE)) { + int errorCode = forecastJson.getInt(OWM_MESSAGE_CODE); + + switch (errorCode) { + case HttpURLConnection.HTTP_OK: + break; + case HttpURLConnection.HTTP_NOT_FOUND: + setLocationStatus(getContext(), LOCATION_STATUS_INVALID); + return; + case HttpURLConnection.HTTP_FORBIDDEN: + Log.e(LOG_TAG, "getWeatherDataFromJson: INVALID API_KEY"); + setLocationStatus(getContext(), LOCATION_STATUS_SERVER_INVALID); + return; + default: + setLocationStatus(getContext(), LOCATION_STATUS_SERVER_DOWN); + return; + } + } + JSONArray weatherArray = forecastJson.getJSONArray(OWM_LIST); JSONObject cityJson = forecastJson.getJSONObject(OWM_CITY); @@ -433,122 +432,164 @@ public void getWeatherDataFromJson(String forecastJsonStr, cVVector.add(weatherValues); } + int inserted = 0; // add to database if (cVVector.size() > 0) { - // Student: call bulkInsert to add the weatherEntries to the database here - ContentValues[] values = new ContentValues[cVVector.size()]; - cVVector.toArray(values); - getContext().getContentResolver().bulkInsert(WeatherContract.WeatherEntry.CONTENT_URI, values); - } + ContentValues[] cvArray = new ContentValues[cVVector.size()]; + cVVector.toArray(cvArray); + getContext().getContentResolver().bulkInsert(WeatherContract.WeatherEntry.CONTENT_URI, cvArray); - //delete old data - long currentTime = Calendar.getInstance().getTimeInMillis(); - long yesterday = currentTime - DAY_IN_MILLIS; - getContext().getContentResolver().delete( - WeatherContract.WeatherEntry.CONTENT_URI, - WeatherContract.WeatherEntry.COLUMN_DATE + "<=?", - new String[]{Long.toString(yesterday)}); + // delete old data so we don't build up an endless history + getContext().getContentResolver().delete(WeatherContract.WeatherEntry.CONTENT_URI, + WeatherContract.WeatherEntry.COLUMN_DATE + " <= ?", + new String[]{Long.toString(dayTime.setJulianDay(julianStartDay - 1))}); - if (Utility.isNotificationOn(getContext())) notifyWeather(); - - // Sort order: Ascending, by date. - String sortOrder = WeatherContract.WeatherEntry.COLUMN_DATE + " ASC"; - Uri weatherForLocationUri = WeatherContract.WeatherEntry.buildWeatherLocationWithStartDate( - locationSetting, System.currentTimeMillis()); - - - Cursor cur = getContext().getContentResolver().query(weatherForLocationUri, - null, null, null, sortOrder); - - if (cur != null) { - cVVector = new Vector(cur.getCount()); - } - if (cur != null && cur.moveToFirst()) { - do { - ContentValues cv = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(cur, cv); - cVVector.add(cv); - } while (cur.moveToNext()); } + Log.d(LOG_TAG, "Sync Complete. " + cVVector.size() + " Inserted"); + setLocationStatus(getContext(), LOCATION_STATUS_OK); } catch (JSONException e) { - Log.e(TAG, e.getMessage(), e); + Log.e(LOG_TAG, e.getMessage(), e); e.printStackTrace(); + setLocationStatus(getContext(), LOCATION_STATUS_SERVER_INVALID); } } private void notifyWeather() { Context context = getContext(); - //checking the last update and notify if it's the first of the day + //checking the last update and notify if it' the first of the day SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - String lastNotificationKey = context.getString(R.string.pref_last_notification); - long lastSync = prefs.getLong(lastNotificationKey, 0); - - if (System.currentTimeMillis() - lastSync >= DAY_IN_MILLIS) { - // Last sync was more than 1 day ago, let's send a notification with the weather. - String locationQuery = Utility.getPreferredLocation(context); - - Uri weatherUri = WeatherContract.WeatherEntry.buildWeatherLocationWithDate(locationQuery, System.currentTimeMillis()); - - // we'll query our contentProvider, as always - Cursor cursor = context.getContentResolver().query(weatherUri, NOTIFY_WEATHER_PROJECTION, null, null, null); - - if (cursor.moveToFirst()) { - int weatherId = cursor.getInt(INDEX_WEATHER_ID); - double high = cursor.getDouble(INDEX_MAX_TEMP); - double low = cursor.getDouble(INDEX_MIN_TEMP); - String desc = cursor.getString(INDEX_SHORT_DESC); - long date = cursor.getLong(INDEX_WEATHER_DATE); - - int iconId = Utility.getIconResourceForWeatherCondition(weatherId); - String title = context.getString(R.string.app_name); - - // Define the text of the forecast. - String contentText = String.format(context.getString(R.string.format_notification), - desc, - Utility.formatTemperature(context, high, Utility.isMetric(context)), - Utility.formatTemperature(context, low, Utility.isMetric(context))); - - //add content to the notification UI - NotificationCompat.Builder mBuilder = - new NotificationCompat.Builder(getContext()) - .setAutoCancel(true) - .setSmallIcon(iconId) - .setContentTitle(title) - .setContentText(contentText); - - //create pending intent and handle back presses by creating back stack - Intent detailActivityIntent = new Intent(getContext(), DetailActivity.class); - detailActivityIntent.setData(WeatherContract.WeatherEntry.buildWeatherLocationWithDate(locationQuery, date)); - TaskStackBuilder stackBuilder = TaskStackBuilder.create(getContext()); - stackBuilder.addParentStack(DetailActivity.class); - stackBuilder.addNextIntent(detailActivityIntent); - - PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); - - mBuilder.setContentIntent(pendingIntent); - NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.notify(WEATHER_NOTIFICATION_ID, mBuilder.build()); - - //refreshing last sync - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(lastNotificationKey, System.currentTimeMillis()); - editor.apply(); + String displayNotificationsKey = context.getString(R.string.pref_notification_key); + boolean displayNotifications = prefs.getBoolean(displayNotificationsKey, + Boolean.parseBoolean(context.getString(R.string.pref_notification_default))); + + if (displayNotifications) { + + String lastNotificationKey = context.getString(R.string.pref_last_notification); + long lastSync = prefs.getLong(lastNotificationKey, 0); + + if (System.currentTimeMillis() - lastSync >= DAY_IN_MILLIS) { + // Last sync was more than 1 day ago, let's send a notification with the weather. + String locationQuery = Utility.getPreferredLocation(context); + + Uri weatherUri = WeatherContract.WeatherEntry.buildWeatherLocationWithDate(locationQuery, System.currentTimeMillis()); + + // we'll query our contentProvider, as always + Cursor cursor = context.getContentResolver().query(weatherUri, NOTIFY_WEATHER_PROJECTION, null, null, null); + + if (cursor.moveToFirst()) { + int weatherId = cursor.getInt(INDEX_WEATHER_ID); + double high = cursor.getDouble(INDEX_MAX_TEMP); + double low = cursor.getDouble(INDEX_MIN_TEMP); + String desc = cursor.getString(INDEX_SHORT_DESC); + + int iconId = Utility.getIconResourceForWeatherCondition(weatherId); + Resources resources = context.getResources(); + Bitmap largeIcon = BitmapFactory.decodeResource(resources, + Utility.getArtResourceForWeatherCondition(weatherId)); + String title = context.getString(R.string.app_name); + + // Define the text of the forecast. + String contentText = String.format(context.getString(R.string.format_notification), + desc, + Utility.formatTemperature(context, high), + Utility.formatTemperature(context, low)); + + // NotificationCompatBuilder is a very convenient way to build backward-compatible + // notifications. Just throw in some data. + NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(getContext()) + .setColor(getContext().getResources().getColor(R.color.sunshine_light_blue)) + .setSmallIcon(iconId) + .setLargeIcon(largeIcon) + .setContentTitle(title) + .setContentText(contentText); + + // Make something interesting happen when the user clicks on the notification. + // In this case, opening the app is sufficient. + Intent resultIntent = new Intent(context, MainActivity.class); + + // The stack builder object will contain an artificial back stack for the + // started Activity. + // This ensures that navigating backward from the Activity leads out of + // your application to the Home screen. + TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); + stackBuilder.addNextIntent(resultIntent); + PendingIntent resultPendingIntent = + stackBuilder.getPendingIntent( + 0, + PendingIntent.FLAG_UPDATE_CURRENT + ); + mBuilder.setContentIntent(resultPendingIntent); + + NotificationManager mNotificationManager = + (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); + // WEATHER_NOTIFICATION_ID allows you to update the notification later on. + mNotificationManager.notify(WEATHER_NOTIFICATION_ID, mBuilder.build()); + + //refreshing last sync + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong(lastNotificationKey, System.currentTimeMillis()); + editor.commit(); + } cursor.close(); } } } - @SunshineSyncAdapter.LocationStatus - public int getLocationStatus() { - return LOCATION_STATUS_OK; + /** + * 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. + */ + long addLocation(String locationSetting, String cityName, double lat, double lon) { + long locationId; + + // First, check if the location with this city name exists in the db + Cursor locationCursor = getContext().getContentResolver().query( + WeatherContract.LocationEntry.CONTENT_URI, + new String[]{WeatherContract.LocationEntry._ID}, + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ?", + new String[]{locationSetting}, + null); + + if (locationCursor.moveToFirst()) { + int locationIdIndex = locationCursor.getColumnIndex(WeatherContract.LocationEntry._ID); + locationId = locationCursor.getLong(locationIdIndex); + } else { + // Now that the content provider is set up, inserting rows of data is pretty simple. + // First create a ContentValues object to hold the data you want to insert. + ContentValues locationValues = new ContentValues(); + + // Then add the data, along with the corresponding name of the data type, + // so the content provider knows what kind of value is being inserted. + locationValues.put(WeatherContract.LocationEntry.COLUMN_CITY_NAME, cityName); + locationValues.put(WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING, locationSetting); + locationValues.put(WeatherContract.LocationEntry.COLUMN_COORD_LAT, lat); + locationValues.put(WeatherContract.LocationEntry.COLUMN_COORD_LONG, lon); + + // Finally, insert location data into the database. + Uri insertedUri = getContext().getContentResolver().insert( + WeatherContract.LocationEntry.CONTENT_URI, + locationValues + ); + + // The resulting URI contains the ID for the row. Extract the locationId from the Uri. + locationId = ContentUris.parseId(insertedUri); + } + + locationCursor.close(); + // Wait, that worked? Yes! + return locationId; } - static private void setLocationStatus(Context c,@LocationStatus int status) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(c); - SharedPreferences.Editor spe = sp.edit(); - spe.putInt(c.getString(R.string.pref_location_status_key),status); - spe.commit(); + @Retention(RetentionPolicy.SOURCE) + @IntDef({LOCATION_STATUS_OK, LOCATION_STATUS_SERVER_DOWN, LOCATION_STATUS_SERVER_INVALID, LOCATION_STATUS_INVALID, LOCATION_STATUS_UNKNOWN}) + public @interface LocationStatus { } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be8f955..2694f13 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -71,5 +71,7 @@ No weather information available. The network is not available to fetch weather data. No weather information available. The server is not returning data. No weather information available. The server is not returning valid data. Please check for an updated version of Sunshine. + No weather information available. The location in settings is not recognized by the weather server. +