--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="de.example.exampletdd"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="19"
+ android:targetSdkVersion="19" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name="de.example.exampletdd.WeatherInformationActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
--- /dev/null
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
--- /dev/null
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
--- /dev/null
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="de.example.exampletdd.MainActivity"
+ tools:ignore="MergeRootFrame" />
--- /dev/null
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context="de.example.exampletdd.MainActivity$PlaceholderFragment" >
+
+ <EditText
+ android:id="@+id/editTextCity"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="text"
+ android:labelFor="@id/editTextCity"
+ android:text="@string/text_default_city" />
+
+ <Button
+ android:id="@+id/buttonweather"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="24dp"
+ android:onClick="onClickGetWeather"
+ android:text="@string/button_weather" />
+
+ <EditText
+ android:id="@+id/editTextWeatherDescription"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/editTextCity"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="102dp"
+ android:ems="10"
+ android:inputType="none"
+ android:labelFor="@id/editTextWeatherDescription"
+ android:text="@string/text_field_description" />
+
+ <EditText
+ android:id="@+id/editTextTemperature"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/editTextWeatherDescription"
+ android:layout_centerHorizontal="true"
+ android:ems="10"
+ android:inputType="none"
+ android:labelFor="@id/editTextTemperature"
+ android:text="@string/text_field_tem" />
+
+ <EditText
+ android:id="@+id/editTextMaxTemperature"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/editTextTemperature"
+ android:layout_centerHorizontal="true"
+ android:ems="10"
+ android:inputType="none"
+ android:labelFor="@id/editTextMaxTemperature"
+ android:text="@string/text_field_tem_max" />
+
+ <EditText
+ android:id="@+id/editTextMinTemperature"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/editTextMaxTemperature"
+ android:layout_centerHorizontal="true"
+ android:ems="10"
+ android:inputType="none"
+ android:labelFor="@id/editTextMinTemperature"
+ android:text="@string/text_field_tem_min" />
+
+ <EditText
+ android:id="@+id/editTextSunRise"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/editTextMinTemperature"
+ android:layout_centerHorizontal="true"
+ android:ems="10"
+ android:inputType="none"
+ android:labelFor="@id/editTextSunRise"
+ android:text="@string/text_field_sun_rise" />
+
+ <EditText
+ android:id="@+id/editTextSunSet"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/editTextSunRise"
+ android:layout_centerHorizontal="true"
+ android:ems="10"
+ android:inputType="none"
+ android:labelFor="@id/editTextSunSet"
+ android:text="@string/text_field_sun_set" />
+
+ <ImageView
+ android:id="@+id/imageIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/editTextCity"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="30dp"
+ android:contentDescription="@string/icon_weather_description"
+ android:src="@drawable/ic_launcher" />
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="de.example.exampletdd.MainActivity" >
+
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="100"
+ android:showAsAction="never"
+ android:title="@string/action_settings"/>
+
+</menu>
--- /dev/null
+<resources>
+
+ <!--
+ Base application theme for API 11+. This theme completely replaces
+ AppBaseTheme from res/values/styles.xml on API 11+ devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+ <!-- API 11 theme customizations can go here. -->
+ </style>
+
+</resources>
--- /dev/null
+<resources>
+
+ <!--
+ Base application theme for API 14+. This theme completely replaces
+ AppBaseTheme from BOTH res/values/styles.xml and
+ res/values-v11/styles.xml on API 14+ devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ <!-- API 14 theme customizations can go here. -->
+ </style>
+
+</resources>
--- /dev/null
+<resources>
+
+ <!--
+ Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
+ -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+
+</resources>
--- /dev/null
+<resources>
+
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <string name="app_name">Weather Information</string>
+ <string name="action_settings">Settings</string>
+ <string name="button_weather">Get weather</string>
+ <string name="uri_api_coord">http://api.openweathermap.org/data/{0}/weather?lat={1}&lon={2}</string>
+ <string name="uri_api_city">http://api.openweathermap.org/data/{0}/weather?q={1}</string>
+ <string name="uri_api_icon">http://openweathermap.org/img/w/{0}</string>
+ <string name="api_version">2.5</string>
+ <string name="text_default_city">City,country</string>
+ <string name="text_field_city">City name</string>
+ <string name="text_field_latitude">Latitude</string>
+ <string name="text_field_longitude">Longitude</string>
+ <string name="text_field_sun_rise">Sun rise time</string>
+ <string name="text_field_sun_set">Sun set time</string>
+ <string name="text_field_description">Weather description</string>
+ <string name="text_field_tem">Temperature</string>
+ <string name="text_field_tem_min">Min temperature</string>
+ <string name="text_field_tem_max">Max temperature</string>
+ <string name="button_ok">OK</string>
+ <string name="error_dialog_connection_tiemout">Connection error timeout</string>
+ <string name="error_dialog_generic_error">Impossible to receive weather data.</string>
+ <string name="icon_weather_description">Icon weather</string>
+
+</resources>
--- /dev/null
+<resources>
+
+ <!--
+ Base application theme, dependent on API level. This theme is replaced
+ by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Light">
+ <!--
+ Theme customizations available in newer API levels can go in
+ res/values-vXX/styles.xml, while customizations related to
+ backward-compatibility can go here.
+ -->
+ </style>
+
+ <!-- Application theme. -->
+ <style name="AppTheme" parent="AppBaseTheme">
+ <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+ </style>
+
+</resources>
--- /dev/null
+package de.example.exampletdd;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.EditText;
+import android.widget.ImageView;
+import de.example.exampletdd.activityinterface.ErrorMessage;
+import de.example.exampletdd.activityinterface.UpdateWeatherData;
+import de.example.exampletdd.fragment.ErrorDialogFragment;
+import de.example.exampletdd.fragment.WeatherDataFragment;
+import de.example.exampletdd.model.WeatherData;
+
+public class WeatherInformationActivity extends Activity implements ErrorMessage, UpdateWeatherData {
+ EditText weatherDescription;
+ EditText temperature;
+ EditText maxTemperature;
+ EditText minTemperature;
+ EditText sunRise;
+ EditText sunSet;
+ ImageView imageIcon;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.setContentView(R.layout.activity_main);
+
+ if (savedInstanceState == null) {
+ this.getFragmentManager().beginTransaction()
+ .add(R.id.container, new WeatherDataFragment()).commit();
+ }
+
+ this.weatherDescription = (EditText) this.findViewById(R.id.editTextWeatherDescription);
+ this.temperature = (EditText) this.findViewById(R.id.editTextTemperature);
+ this.maxTemperature = (EditText) this.findViewById(R.id.editTextMaxTemperature);
+ this.minTemperature = (EditText) this.findViewById(R.id.editTextMinTemperature);
+ this.sunRise = (EditText) this.findViewById(R.id.editTextSunRise);
+ this.sunSet = (EditText) this.findViewById(R.id.editTextSunSet);
+ this.imageIcon = (ImageView) this.findViewById(R.id.imageIcon);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+
+ // Inflate the menu; this adds items to the action bar if it is present.
+ this.getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ final int id = item.getItemId();
+ if (id == R.id.action_settings) {
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void createErrorDialog(final int title) {
+ final DialogFragment newFragment = ErrorDialogFragment
+ .newInstance(title);
+ newFragment.show(this.getFragmentManager(), "errorDialog");
+ }
+
+
+ @Override
+ public void updateWeatherData(final WeatherData weatherData) {
+
+ if (weatherData.getWeather() != null) {
+ this.weatherDescription.setText(weatherData.getWeather().getDescription());
+ double conversion = weatherData.getMain().getTemp();
+ conversion = conversion - 273.15;
+ this.temperature.setText(String.valueOf(conversion));
+ conversion = weatherData.getMain().getMaxTemp();
+ conversion = conversion - 273.15;
+ this.maxTemperature.setText(String.valueOf(conversion));
+ conversion = weatherData.getMain().getMinTemp();
+ conversion = conversion - 273.15;
+ this.minTemperature.setText(String.valueOf(conversion));
+ }
+
+ if (weatherData.getSystem() != null) {
+ long unixTime = weatherData.getSystem().getSunRiseTime();
+ Date dateUnix = new Date(unixTime);
+ String dateFormatUnix = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss Z").format(dateUnix);
+ this.sunRise.setText(dateFormatUnix);
+
+ unixTime = weatherData.getSystem().getSunSetTime();
+ dateUnix = new Date(unixTime);
+ dateFormatUnix = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss Z").format(dateUnix);
+ this.sunSet.setText(dateFormatUnix);
+ }
+
+ if (weatherData.getIconData() != null) {
+ final Bitmap icon = BitmapFactory.decodeByteArray(
+ weatherData.getIconData(), 0,
+ weatherData.getIconData().length);
+ this.imageIcon.setImageBitmap(icon);
+ }
+ }
+}
--- /dev/null
+package de.example.exampletdd.activityinterface;
+
+public interface ErrorMessage {
+
+ public void createErrorDialog(final int title);
+}
--- /dev/null
+package de.example.exampletdd.activityinterface;
+
+import de.example.exampletdd.model.WeatherData;
+
+public interface UpdateWeatherData {
+
+ public void updateWeatherData(WeatherData weatherData);
+}
--- /dev/null
+package de.example.exampletdd.fragment;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import de.example.exampletdd.R;
+
+public class ErrorDialogFragment extends DialogFragment {
+
+ public static ErrorDialogFragment newInstance(final int title) {
+ final ErrorDialogFragment frag = new ErrorDialogFragment();
+ final Bundle args = new Bundle();
+
+ args.putInt("title", title);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final int title = this.getArguments().getInt("title");
+
+ return new AlertDialog.Builder(this.getActivity())
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(title)
+ .setPositiveButton(R.string.button_ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int whichButton) {
+
+ }
+ }).create();
+ }
+}
\ No newline at end of file
--- /dev/null
+package de.example.exampletdd.fragment;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import org.apache.http.client.ClientProtocolException;
+import org.json.JSONException;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.net.http.AndroidHttpClient;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import de.example.exampletdd.R;
+import de.example.exampletdd.activityinterface.ErrorMessage;
+import de.example.exampletdd.activityinterface.UpdateWeatherData;
+import de.example.exampletdd.httpclient.WeatherHTTPClient;
+import de.example.exampletdd.model.WeatherData;
+import de.example.exampletdd.parser.IJPOSWeatherParser;
+import de.example.exampletdd.parser.JPOSWeatherParser;
+import de.example.exampletdd.service.WeatherService;
+
+public class WeatherDataFragment extends Fragment {
+ private final Activity currentActivity;
+
+ public WeatherDataFragment() {
+ this.currentActivity = this.getActivity();
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater,
+ final ViewGroup container, final Bundle savedInstanceState) {
+ final View rootView = inflater.inflate(R.layout.fragment_main,
+ container, false);
+ return rootView;
+ }
+
+ public void onClickGetWeather(final View v) {
+
+ final IJPOSWeatherParser JPOSWeatherParser = new JPOSWeatherParser();
+ final WeatherService weatherService = new WeatherService(
+ JPOSWeatherParser);
+ final AndroidHttpClient httpClient = AndroidHttpClient
+ .newInstance("Android Weather Information Agent");
+ final WeatherHTTPClient HTTPweatherClient = new WeatherHTTPClient(
+ httpClient);
+
+ final WeatherTask weatherTask = new WeatherTask(HTTPweatherClient, weatherService);
+
+ final EditText cityCountry = (EditText) this.getActivity()
+ .findViewById(R.id.editTextCity);
+
+ weatherTask.execute(cityCountry.getText().toString());
+ }
+
+ public class WeatherTask extends AsyncTask<Object, Void, WeatherData> {
+ private static final String TAG = "JSONWeatherTask";
+ private final WeatherHTTPClient weatherHTTPClient;
+ private final WeatherService weatherService;
+
+ public WeatherTask(final WeatherHTTPClient weatherHTTPClient,
+ final WeatherService weatherService) {
+ this.weatherHTTPClient = weatherHTTPClient;
+ this.weatherService = weatherService;
+ }
+
+ @Override
+ protected WeatherData doInBackground(final Object... params) {
+ final String cityCountry = (String) params[0];
+
+ final String urlAPICity = WeatherDataFragment.this.getResources()
+ .getString(R.string.uri_api_city);
+ final String APIVersion = WeatherDataFragment.this.getResources()
+ .getString(R.string.api_version);
+
+ String url = this.weatherService.createURIAPICityCountry(
+ cityCountry, urlAPICity, APIVersion);
+
+ String jsonData = null;
+ try {
+ jsonData = this.weatherHTTPClient
+ .retrieveJSONDataFromAPI(new URL(url));
+ } catch (final ClientProtocolException e) {
+ Log.e(TAG, "WeatherHTTPClient exception: ", e);
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ } catch (final MalformedURLException e) {
+ Log.e(TAG, "Syntax URL exception: ", e);
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ } catch (final URISyntaxException e) {
+ Log.e(TAG, "WeatherHTTPClient exception: ", e);
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ } catch (final IOException e) {
+ Log.e(TAG, "WeatherHTTPClient exception: ", e);
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ } finally {
+ this.weatherHTTPClient.close();
+ }
+
+ WeatherData weatherData = null;
+ if (jsonData != null) {
+ try {
+ weatherData = this.weatherService.retrieveWeather(jsonData);
+ } catch (final JSONException e) {
+ Log.e(TAG, "WeatherService exception: ", e);
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ }
+
+ }
+
+ if (weatherData != null) {
+ final String icon = weatherData.getWeather().getIcon();
+ final String urlAPIicon = WeatherDataFragment.this
+ .getResources().getString(R.string.uri_api_icon);
+
+ url = this.weatherService.createURIAPIicon(icon, urlAPIicon);
+ try {
+ final byte[] iconData = this.weatherHTTPClient
+ .retrieveDataFromAPI(new URL(url)).toByteArray();
+ weatherData.setIconData(iconData);
+ } catch (final ClientProtocolException e) {
+ Log.e(TAG, "WeatherHTTPClient exception: ", e);
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ } catch (final MalformedURLException e) {
+ Log.e(TAG, "Syntax URL exception: ", e);
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ } catch (final URISyntaxException e) {
+ Log.e(TAG, "WeatherHTTPClient exception: ", e);
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ } catch (final IOException e) {
+ Log.e(TAG, "WeatherHTTPClient exception: ", e);
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ } finally {
+ this.weatherHTTPClient.close();
+ }
+ }
+
+ return weatherData;
+ }
+
+ @Override
+ protected void onPostExecute(final WeatherData weatherData) {
+ if (weatherData != null) {
+ ((UpdateWeatherData) WeatherDataFragment.this.currentActivity)
+ .updateWeatherData(weatherData);
+ } else {
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_generic_error);
+ }
+
+ this.weatherHTTPClient.close();
+ }
+
+ @Override
+ protected void onCancelled(final WeatherData weatherData) {
+ this.onCancelled();
+ ((ErrorMessage) WeatherDataFragment.this.currentActivity)
+ .createErrorDialog(R.string.error_dialog_connection_tiemout);
+
+ this.weatherHTTPClient.close();
+ }
+ }
+}
--- /dev/null
+package de.example.exampletdd.httpclient;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpGet;
+
+import android.net.http.AndroidHttpClient;
+
+public class WeatherHTTPClient {
+ private final AndroidHttpClient httpClient;
+
+ public WeatherHTTPClient(final AndroidHttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ public String retrieveJSONDataFromAPI(final URL url)
+ throws URISyntaxException, ClientProtocolException, IOException {
+
+ final ResponseHandler<String> handler = new ResponseHandler<String>() {
+ @Override
+ public String handleResponse(
+ final HttpResponse response)
+ throws UnsupportedEncodingException, IOException {
+
+ if (response != null) {
+ final HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ final String contentEncoding = entity
+ .getContentEncoding()
+ .getValue();
+ final ByteArrayOutputStream buffer = WeatherHTTPClient.this
+ .sortResponse(response);
+ return new String(buffer.toByteArray(), contentEncoding);
+ }
+
+ throw new IOException("There is no entity");
+ }
+
+ throw new IOException("There is no response");
+ }
+ };
+
+ final HttpGet httpGet = new HttpGet();
+ httpGet.setURI(url.toURI());
+
+ return this.httpClient.execute(httpGet, handler);
+ }
+
+ public ByteArrayOutputStream retrieveDataFromAPI(final URL url)
+ throws URISyntaxException, ClientProtocolException, IOException {
+ final ResponseHandler<ByteArrayOutputStream> handler = new ResponseHandler<ByteArrayOutputStream>() {
+
+ @Override
+ public ByteArrayOutputStream handleResponse(
+ final HttpResponse response)
+ throws UnsupportedEncodingException, IOException {
+
+ if (response != null) {
+ final HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ return WeatherHTTPClient.this.sortResponse(response);
+ }
+
+ throw new IOException("There is no entity");
+ }
+
+ throw new IOException("There is no response");
+ }
+ };
+
+ final HttpGet httpGet = new HttpGet();
+ httpGet.setURI(url.toURI());
+
+ return this.httpClient.execute(httpGet, handler);
+ }
+
+ public ByteArrayOutputStream sortResponse(final HttpResponse httpResponse)
+ throws IOException {
+
+ if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+ final HttpEntity entity = httpResponse.getEntity();
+ if (entity != null) {
+ try {
+ return this.readInputStream(entity.getContent());
+ } finally {
+ entity.consumeContent();
+ }
+ }
+ }
+
+ throw new IOException("Unexpected response code: "
+ + httpResponse.getStatusLine().getStatusCode());
+ }
+
+ public void close() {
+ this.httpClient.close();
+ }
+
+ private ByteArrayOutputStream readInputStream (final InputStream inputStream) throws IOException {
+ final ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
+ final int bufferSize = 1024;
+ final byte[] buffer = new byte[bufferSize];
+
+ try {
+ int len = 0;
+ while ((len = inputStream.read(buffer)) != -1) {
+ byteBuffer.write(buffer, 0, len);
+ }
+ }finally {
+ inputStream.close();
+ }
+
+ return byteBuffer;
+ }
+}
--- /dev/null
+package de.example.exampletdd.model;
+
+
+public class WeatherData {
+ private final Main main;
+ private final Wind wind;
+ private final Rain rain;
+ private final Coord coord;
+ private final DataReceivingTime dataReceivingTime;
+ private final StationName stationName;
+ private final System system;
+ private final Clouds clouds;
+ private final Weather weather;
+ private byte[] iconData;
+
+
+ public static class Builder {
+ //Optional parameters
+ private Main mMain;
+ private Wind mWind;
+ private Rain mRain;
+ private Coord mCoord;
+ private DataReceivingTime mDataReceivingTime;
+ private StationName mStationName;
+ private System mSystem;
+ private Clouds mClouds;
+ private Weather mWeather;
+
+
+ public Builder setMain(final Main main) {
+ this.mMain = main;
+ return this;
+ }
+
+ public Builder setWind(final Wind wind) {
+ this.mWind = wind;
+ return this;
+ }
+
+ public Builder setRain(final Rain rain) {
+ this.mRain = rain;
+ return this;
+ }
+
+ public Builder setCoord(final Coord coord) {
+ this.mCoord = coord;
+ return this;
+ }
+
+ public Builder setDataReceivingTime(
+ final DataReceivingTime dataReceivingTime) {
+ this.mDataReceivingTime = dataReceivingTime;
+ return this;
+ }
+
+ public Builder setStationName(final StationName stationName) {
+ this.mStationName = stationName;
+ return this;
+ }
+
+ public Builder setSystem(final System system) {
+ this.mSystem = system;
+ return this;
+ }
+
+ public Builder setClouds(final Clouds clouds) {
+ this.mClouds = clouds;
+ return this;
+ }
+
+ public Builder setWeather(final Weather weather) {
+ this.mWeather = weather;
+ return this;
+ }
+
+ public WeatherData build() {
+ return new WeatherData(this);
+ }
+ }
+
+ private WeatherData(final Builder builder) {
+ this.main = builder.mMain;
+ this.wind = builder.mWind;
+ this.rain = builder.mRain;
+ this.coord = builder.mCoord;
+ this.dataReceivingTime = builder.mDataReceivingTime;
+ this.stationName = builder.mStationName;
+ this.system = builder.mSystem;
+ this.clouds = builder.mClouds;
+ this.weather = builder.mWeather;
+ }
+
+
+ @Override
+ public String toString() {
+ final StringBuilder builder2 = new StringBuilder();
+ builder2.append("WeatherData [main=").append(this.main)
+ .append(", wind=").append(this.wind).append(", rain=")
+ .append(this.rain).append(", coord=").append(this.coord)
+ .append(", dataReceivingTime=").append(this.dataReceivingTime)
+ .append(", stationName=").append(this.stationName)
+ .append(", system=").append(this.system).append(", clouds=")
+ .append(this.clouds).append(", weather=").append(this.weather)
+ .append("]");
+ return builder2.toString();
+ }
+
+ public Main getMain() {
+ return this.main;
+ }
+
+ public Wind getWind() {
+ return this.wind;
+ }
+
+ public Rain getRain() {
+ return this.rain;
+ }
+
+ public Coord getCoord() {
+ return this.coord;
+ }
+
+ public DataReceivingTime getDataReceivingTime() {
+ return this.dataReceivingTime;
+ }
+
+ public StationName getStationName() {
+ return this.stationName;
+ }
+
+ public System getSystem() {
+ return this.system;
+ }
+
+ public Clouds getClouds() {
+ return this.clouds;
+ }
+
+ public Weather getWeather() {
+ return this.weather;
+ }
+
+ public void setIconData(final byte[] iconData) {
+ this.iconData = iconData;
+ }
+
+ public byte[] getIconData() {
+ return this.iconData;
+ }
+
+ public static class Main {
+ private final double temp;
+ // Minimum temperature
+ private final double minTemp;
+ // Maximum temperature
+ private final double maxTemp;
+ // Humidity in %
+ private final double humidity;
+ // Atmospheric pressure in kPa
+ private final double pressure;
+
+ public Main(final double temp, final double minTemp, final double maxTemp,
+ final double humidity, final double pressure) {
+ this.temp = temp;
+ this.minTemp = minTemp;
+ this.maxTemp = maxTemp;
+ this.humidity = humidity;
+ this.pressure = pressure;
+ }
+
+ public double getTemp() {
+ return this.temp;
+ }
+
+ public double getMinTemp() {
+ return this.minTemp;
+ }
+
+ public double getMaxTemp() {
+ return this.maxTemp;
+ }
+
+ public double getHumidity() {
+ return this.humidity;
+ }
+
+ public double getPressure() {
+ return this.pressure;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Main [temp=").append(this.temp)
+ .append(", minTemp=").append(this.minTemp)
+ .append(", maxTemp=").append(this.maxTemp)
+ .append(", humidity=").append(this.humidity)
+ .append(", pressure=").append(this.pressure).append("]");
+ return builder.toString();
+ }
+ }
+
+ public static class Wind {
+ // Wind speed in mps
+ private final double speed;
+ // Wind direction in degrees (meteorological)
+ private final double deg;
+ // speed of wind gust
+ private final double gust;
+ // Wind direction
+ private final double var_beg;
+ // Wind direction
+ private final double var_end;
+
+ public Wind(final double speed, final double deg, final double gust,
+ final double var_beg, final double var_end) {
+ this.speed = speed;
+ this.deg = deg;
+ this.gust = gust;
+ this.var_beg = var_beg;
+ this.var_end = var_end;
+ }
+
+ public double getSpeed() {
+ return this.speed;
+ }
+
+ public double getDeg() {
+ return this.deg;
+ }
+
+ public double getGust() {
+ return this.gust;
+ }
+
+ public double getVar_beg() {
+ return this.var_beg;
+ }
+
+ public double getVar_end() {
+ return this.var_end;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Wind [speed=").append(this.speed).append(", deg=")
+ .append(this.deg).append(", gust=").append(this.gust)
+ .append(", var_beg=").append(this.var_beg)
+ .append(", var_end=").append(this.var_end).append("]");
+ return builder.toString();
+ }
+ }
+
+ public static class Rain {
+ // Period
+ private final String time;
+ // Precipitation volume for period
+ private final double ammount;
+
+ public Rain(final String time, final double ammount) {
+ this.time = time;
+ this.ammount = ammount;
+ }
+
+ public String getTime() {
+ return this.time;
+ }
+
+ public double getAmmount() {
+ return this.ammount;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Rain [time=").append(this.time)
+ .append(", ammount=").append(this.ammount).append("]");
+ return builder.toString();
+ }
+
+ }
+
+ public static class Coord {
+ // City location
+ private final double longitude;
+ private final double latitude;
+
+ public Coord(final double longitude, final double latitude) {
+ this.longitude = longitude;
+ this.latitude = latitude;
+ }
+
+ public double getLongitude() {
+ return this.longitude;
+ }
+
+ public double getLatitude() {
+ return this.latitude;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Coord [longitude=").append(this.longitude)
+ .append(", latitude=").append(this.latitude).append("]");
+ return builder.toString();
+ }
+ }
+
+ public static class DataReceivingTime {
+ // Time of data receiving in unixtime GMT
+ private final double time;
+
+ public DataReceivingTime(final double time) {
+ this.time = time;
+ }
+
+ public double getTime() {
+ return this.time;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("DataReceivingTime [time=").append(this.time)
+ .append("]");
+ return builder.toString();
+ }
+ }
+
+ public static class StationName {
+ private final String name;
+
+ public StationName(final String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("StationName [name=").append(this.name).append("]");
+ return builder.toString();
+ }
+ }
+
+ public static class System {
+ private final String country;
+ // Unixtime GMT
+ private final long sunRiseTime;
+ // Unixtime GMT
+ private final long sunSetTime;
+ private final double message;
+
+ public System(final String country, final long sunRiseTime,
+ final long sunSetTime, final double message) {
+ this.country = country;
+ this.sunRiseTime = sunRiseTime;
+ this.sunSetTime = sunSetTime;
+ this.message = message;
+ }
+
+ public String getCountry() {
+ return this.country;
+ }
+
+ public long getSunRiseTime() {
+ return this.sunRiseTime;
+ }
+
+ public long getSunSetTime() {
+ return this.sunSetTime;
+ }
+
+ public double getMessage() {
+ return this.message;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("System [country=").append(this.country)
+ .append(", sunRiseTime=").append(this.sunRiseTime)
+ .append(", sunSetTime=").append(this.sunSetTime)
+ .append(", message=").append(this.message).append("]");
+ return builder.toString();
+ }
+ }
+
+ public static class Clouds {
+ // Cloudiness in %
+ private final double cloudiness;
+
+ public Clouds(final double cloudiness) {
+ this.cloudiness = cloudiness;
+ }
+
+ public double getCloudiness() {
+ return this.cloudiness;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Clouds [cloudiness=").append(this.cloudiness)
+ .append("]");
+ return builder.toString();
+ }
+ }
+
+ public static class Weather {
+ private final int weatherId;
+ private final String main;
+ private final String description;
+ private final String icon;
+
+ public Weather(final int weatherId, final String main,
+ final String description, final String icon) {
+ this.weatherId = weatherId;
+ this.main = main;
+ this.description = description;
+ this.icon = icon;
+ }
+
+ public int getWeatherId() {
+ return this.weatherId;
+ }
+
+ public String getMain() {
+ return this.main;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public String getIcon() {
+ return this.icon;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Weather [weatherId=").append(this.weatherId)
+ .append(", main=").append(this.main)
+ .append(", description=").append(this.description)
+ .append(", icon=").append(this.icon).append("]");
+ return builder.toString();
+ }
+ }
+}
--- /dev/null
+package de.example.exampletdd.parser;
+
+import org.json.JSONException;
+
+import de.example.exampletdd.model.WeatherData;
+
+public interface IJPOSWeatherParser {
+
+ WeatherData retrieveWeatherFromJPOS(final String jsonData) throws JSONException;
+}
--- /dev/null
+package de.example.exampletdd.parser;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import de.example.exampletdd.model.WeatherData;
+
+public class JPOSWeatherParser implements IJPOSWeatherParser {
+
+ @Override
+ public WeatherData retrieveWeatherFromJPOS(final String jsonData) throws JSONException {
+ final JSONObject jsonWeatherData = new JSONObject(jsonData);
+
+ JSONObject jsonObject = jsonWeatherData.getJSONObject("coord");
+ final double longitude = jsonObject.getDouble("lon");
+ final double latitude = jsonObject.getDouble("lat");
+ final WeatherData.Coord coord = new WeatherData.Coord(longitude,
+ latitude);
+
+ jsonObject = jsonWeatherData.getJSONObject("sys");
+ final long sunRiseTime = jsonObject.getLong("sunrise");
+ final long sunSetTime = jsonObject.getLong("sunset");
+ final double message = jsonObject.getDouble("message");
+ final String country = jsonObject.getString("country");
+ final WeatherData.System system = new WeatherData.System(country,
+ sunRiseTime, sunSetTime, message);
+
+ jsonObject = jsonWeatherData.getJSONObject("weather");
+ final int id = jsonObject.getInt("id");
+ final String mainWeather = jsonObject.getString("main");
+ final String description = jsonObject.getString("description");
+ final String icon = jsonObject.getString("icon");
+ final WeatherData.Weather weather = new WeatherData.Weather(id,
+ mainWeather, description, icon);
+
+ jsonObject = jsonWeatherData.getJSONObject("main");
+ final double temp = jsonObject.getDouble("temp");
+ final double minTemp = jsonObject.getDouble("minTemp");
+ final double maxTemp = jsonObject.getDouble("maxTemp");
+ final double humidity = jsonObject.getDouble("humidity");
+ final double pressure = jsonObject.getDouble("pressure");
+ final WeatherData.Main main = new WeatherData.Main(temp, minTemp,
+ maxTemp, humidity, pressure);
+
+ jsonObject = jsonWeatherData.getJSONObject("wind");
+ final double speed = jsonObject.getDouble("speed");
+ final double deg = jsonObject.getDouble("deg");
+ final double gust = jsonObject.getDouble("gust");
+ final double var_beg = jsonObject.getDouble("var_beg");
+ final double var_end = jsonObject.getDouble("var_end");
+ final WeatherData.Wind wind = new WeatherData.Wind(speed, deg, gust,
+ var_beg, var_end);
+
+ jsonObject = jsonWeatherData.getJSONObject("clouds");
+ final double cloudiness = jsonObject.getDouble("all");
+ final WeatherData.Clouds clouds = new WeatherData.Clouds(cloudiness);
+
+ final double time = jsonObject.getDouble("time");
+ final WeatherData.DataReceivingTime dataReceivingTime =
+ new WeatherData.DataReceivingTime(time);
+
+
+ return new WeatherData.Builder().setCoord(coord).setSystem(system)
+ .setWeather(weather).setMain(main).setWind(wind)
+ .setClouds(clouds).setDataReceivingTime(dataReceivingTime)
+ .build();
+ }
+}
--- /dev/null
+package de.example.exampletdd.service;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+
+import org.json.JSONException;
+
+import de.example.exampletdd.model.WeatherData;
+import de.example.exampletdd.parser.IJPOSWeatherParser;
+
+public class WeatherService {
+ private final IJPOSWeatherParser JPOSWeatherParser;
+
+ public WeatherService(final IJPOSWeatherParser JPOSWeatherParser) {
+ this.JPOSWeatherParser = JPOSWeatherParser;
+ }
+
+ public WeatherData retrieveWeather(final String jsonData) throws JSONException {
+ return this.JPOSWeatherParser.retrieveWeatherFromJPOS(jsonData);
+ }
+
+ public String createURIAPICoord(final int latitude, final int longitude,
+ final String urlAPI, final String APIVersion) {
+
+ final MessageFormat formatURIAPI = new MessageFormat(urlAPI,
+ Locale.ENGLISH);
+ final Object[] values = new Object[3];
+ values[0] = APIVersion;
+ values[1] = latitude;
+ values[2] = longitude;
+
+ return formatURIAPI.format(values);
+ }
+
+ public String createURIAPICityCountry(final String cityCountry,
+ final String urlAPI, final String APIVersion) {
+
+ final MessageFormat formatURIAPI = new MessageFormat(urlAPI, Locale.ENGLISH);
+ final Object[] values = new Object[2];
+ values[0] = APIVersion;
+ values[1] = cityCountry;
+
+ return formatURIAPI.format(values);
+ }
+
+ public String createURIAPIicon(final String icon, final String urlAPI) {
+
+ final MessageFormat formatURIAPI = new MessageFormat(urlAPI, Locale.ENGLISH);
+
+ return formatURIAPI.format(icon);
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="de.example.exampletdd.test"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="19" />
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="de.example.exampletdd" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+</manifest>
\ No newline at end of file
--- /dev/null
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
--- /dev/null
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">Weather Information Test</string>
+
+</resources>
--- /dev/null
+package de.example.exampletdd.test;
+
+import junit.framework.TestCase;
+
+import org.json.JSONException;
+
+import de.example.exampletdd.model.WeatherData;
+import de.example.exampletdd.parser.JPOSWeatherParser;
+
+public class JPOSWeatherParserTest extends TestCase {
+ private JPOSWeatherParser jposWeatherParser;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ this.jposWeatherParser = new JPOSWeatherParser();
+ }
+
+ public void testRetrieveWeatherFromJPOS() throws JSONException {
+ // Arrange
+ final String jsonData = "{\"coord\":{\"lon\":139,\"lat\":35}}";
+ final double longitude = 139;
+ final double latitude = 35;
+ final WeatherData.Coord coord = new WeatherData.Coord(longitude, latitude);
+ final WeatherData expectedWeather = new WeatherData.Builder().setCoord(coord).build();
+
+ // Act
+ final WeatherData finalWeather = this.jposWeatherParser.retrieveWeatherFromJPOS(jsonData);
+
+ // Assert
+ assertEquals(expectedWeather.toString(), finalWeather.toString());
+ }
+
+}
--- /dev/null
+package de.example.exampletdd.test;
+
+import android.content.Intent;
+import android.test.ActivityUnitTestCase;
+import android.widget.Button;
+import de.example.exampletdd.WeatherInformationActivity;
+
+public class WeatherInformationActivityUnitTest extends
+ ActivityUnitTestCase<WeatherInformationActivity> {
+
+ private WeatherInformationActivity activity;
+
+ public WeatherInformationActivityUnitTest() {
+ super(WeatherInformationActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Intent intent = new Intent(this.getInstrumentation().getTargetContext(),
+ WeatherInformationActivity.class);
+ this.startActivity(intent, null, null);
+ this.activity = this.getActivity();
+ }
+
+ public void testIntentTriggerViaOnClick() {
+ final int buttonweather = de.example.exampletdd.R.id.buttonweather;
+ final Button view = (Button) this.activity.findViewById(buttonweather);
+ assertNotNull("Button Weather not allowed to be null", view);
+
+ view.performClick();
+
+ // TouchUtils cannot be used, only allowed in
+ // InstrumentationTestCase or ActivityInstrumentationTestCase2
+
+ // Check the intent which was started
+ final Intent triggeredIntent = this.getStartedActivityIntent();
+ assertNotNull("Intent was null", triggeredIntent);
+ final String data = triggeredIntent.getDataString();
+
+ assertEquals("Incorrect data passed via the intent",
+ "http://gumartinm.name", data);
+ }
+}