Java tutorial
/* Radiobeacon - Openbmap wifi and cell logger Copyright (C) 2013 wish7 This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.openbmap.soapclient; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.State; import android.os.AsyncTask; import android.preference.PreferenceManager; import android.util.Base64; import android.util.Log; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.openbmap.Preferences; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; /** * Checks whether this client version is outdated. * Allowed client version is retrieved from openbmap server */ public final class CheckServerTask extends AsyncTask<String, Object, Object[]> { private static final String TAG = CheckServerTask.class.getSimpleName(); public interface ServerCheckerListener { void onServerAllowsUpload(); void onServerDeclinesUpload(ServerAnswer code, String description); void onServerCheckFailed(); } public enum ServerAnswer { OK, BAD_PASSWORD, OUTDATED, NO_REPLY, UNKNOWN_ERROR } /** * */ private static final int CONNECTION_TIMEOUT = 10000; /** * Especially after wifi sleep / wifi repair, it takes a couple of seconds before * device goes into connecting state * How many seconds should we wait for state connecting? */ private static final int WAIT_FOR_CONNECTING = 10; /** * After device is in connecting state, it takes a couple of seconds to final connect * How many seconds should we wait for state connected? */ private static final int WAIT_FOR_CONNECTED = 5; private String serverVersion = ""; private final Context mContext; private final ServerCheckerListener mListener; public CheckServerTask(final Context context, final ServerCheckerListener listener) { mListener = listener; mContext = context; } /* (non-Javadoc) * @see android.os.AsyncTask#doInBackground(Params[]) */ @Override protected Object[] doInBackground(final String... params) { try { final Object[] result = new Object[2]; result[0] = ServerAnswer.UNKNOWN_ERROR; result[1] = "Uninitialized"; //check whether we have a connection to openbmap.org if (!isOnline()) { // if not, check whether connecting, if so wait Log.i(TAG, "No reply from server! Device might just been switched on, so wait a bit"); waitForConnect(); if (!isOnline()) { Log.i(TAG, "Waiting didn't help. Still no connection"); result[0] = ServerAnswer.NO_REPLY; result[1] = "No online connection!"; return result; } } final SAXParserFactory factory = SAXParserFactory.newInstance(); final DefaultHandler handler = new DefaultHandler() { private boolean versionElement = false; public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException { if (qName.equalsIgnoreCase("ALLOWED")) { versionElement = true; } } public void endElement(final String uri, final String localName, final String qName) throws SAXException { // Log.d(TAG, "End Element :" + qName); } public void characters(final char[] ch, final int start, final int length) throws SAXException { if (versionElement) { serverVersion = new String(ch, start, length); versionElement = false; } } }; Log.i(TAG, "Verifying client version at" + Preferences.VERSION_CHECK_URL); // version file is opened as stream, thus preventing immediate timeout issues final URL url = new URL(Preferences.VERSION_CHECK_URL); final InputStream stream = url.openStream(); final SAXParser saxParser = factory.newSAXParser(); saxParser.parse(stream, handler); stream.close(); if (serverVersion.equals(params[0])) { Log.i(TAG, "Client version is up-to-date: " + params[0]); final boolean anonymousUpload = PreferenceManager.getDefaultSharedPreferences(mContext) .getBoolean(Preferences.KEY_ANONYMOUS_UPLOAD, false); if (!anonymousUpload && credentialsAccepted(params[1], params[2])) { result[0] = ServerAnswer.OK; result[1] = "Everything fine! You're using the most up-to-date version!"; } else if (!anonymousUpload && !credentialsAccepted(params[1], params[2])) { result[0] = ServerAnswer.BAD_PASSWORD; result[1] = "Server reports bad user or password!"; } else { result[0] = ServerAnswer.OK; result[1] = "Password validation skipped, anonymous upload!"; } return result; } else { Log.i(TAG, "Client version is outdated: server " + serverVersion + " client " + params[0]); result[0] = ServerAnswer.OUTDATED; result[1] = "New version available:" + serverVersion; return result; } } catch (final IOException e) { Log.e(TAG, "Error while checking version. Are you online?"); return new Object[] { ServerAnswer.NO_REPLY, "Couldn't contact server" }; } catch (final Exception e) { Log.e(TAG, "Error while checking version: " + e.toString(), e); return new Object[] { ServerAnswer.UNKNOWN_ERROR, "Error: " + e.toString() }; } } /** * Sends a https request to website to check if server accepts user name and password * @return true if server confirms credentials */ private boolean credentialsAccepted(String user, String password) { if (user == null || password == null) { return false; } final DefaultHttpClient httpclient = new DefaultHttpClient(); final HttpPost httppost = new HttpPost(Preferences.PASSWORD_VALIDATION_URL); try { final String authorizationString = "Basic " + Base64.encodeToString((user + ":" + password).getBytes(), Base64.NO_WRAP); httppost.setHeader("Authorization", authorizationString); final HttpResponse response = httpclient.execute(httppost); final int reply = response.getStatusLine().getStatusCode(); if (reply == 200) { Log.v(TAG, "Server accepted credentials"); return true; } else if (reply == 401) { Log.e(TAG, "Server authentication failed"); return false; } else { Log.w(TAG, "Generic error: server reply " + reply); return false; } // TODO: redirects (301, 302) are NOT handled here // thus if something changes on the server side we're dead here } catch (final ClientProtocolException e) { Log.e(TAG, e.getMessage(), e); } catch (final IOException e) { Log.e(TAG, "I/O exception while checking credentials " + e.getMessage(), e); } return false; } @Override protected void onPostExecute(final Object[] result) { if (result.length == 2) { final ServerAnswer code = (ServerAnswer) result[0]; final String description = (String) result[1]; if (code == ServerAnswer.OK) { if (mListener != null) { mListener.onServerAllowsUpload(); } } else if (code == ServerAnswer.OUTDATED || code == ServerAnswer.BAD_PASSWORD) { // cancel, if client version to old or bad credentials if (mListener != null) { mListener.onServerDeclinesUpload(code, description); } } else if (code == ServerAnswer.NO_REPLY || code == ServerAnswer.UNKNOWN_ERROR) { Log.e(TAG, "Couldn't verify server version. Are you offline?"); // cancel, if client version couldn't be verified if (mListener != null) { mListener.onServerCheckFailed(); } } } } /** * Gives system some time to initialize network adapter and connections */ private void waitForConnect() { final ConnectivityManager cm = (ConnectivityManager) mContext .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = null; // wait for state change for (int i = 0; i < WAIT_FOR_CONNECTING; i++) { try { networkInfo = cm.getActiveNetworkInfo(); if ((networkInfo != null) && (networkInfo.getState() == State.CONNECTING || networkInfo.getState() == State.CONNECTED)) { break; } Log.i(TAG, "Network neither connected nor connecting. Wait 1 sec.."); Thread.sleep(1000); } catch (final InterruptedException e) { // ignore } } networkInfo = cm.getActiveNetworkInfo(); if (networkInfo != null) { Log.i(TAG, "Connection status" + networkInfo.toString()); } // once we're in CONNECTING state, wait another couple of seconds if (networkInfo != null && networkInfo.getState().equals(State.CONNECTING)) { // if in connecting state, wait 1 second for connection // this process is repeated multiple times according to retries Log.i(TAG, "Hoorray: after all connecting.. Wait for connection ready"); for (int i = 0; i < WAIT_FOR_CONNECTED; i++) { if (isOnline()) { return; } Log.i(TAG, "Connection not yet ready. Waiting 1 sec.."); try { Thread.sleep(1000); } catch (final InterruptedException e) { // ignore } } } networkInfo = cm.getActiveNetworkInfo(); if (networkInfo != null) { Log.i(TAG, "Waited enough: now " + networkInfo.toString()); } } /** * Checks connection to openbmap.org * @return true on successful http connection */ private static boolean isOnline() { try { Log.v(TAG, "Ping " + Preferences.VERSION_CHECK_URL); final URL url = new URL(Preferences.VERSION_CHECK_URL); final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("Connection", "close"); connection.setConnectTimeout(CONNECTION_TIMEOUT); connection.connect(); if (connection.getResponseCode() == 200) { Log.i(TAG, String.format("Good: Server reply %s - device & server online", connection.getResponseCode())); return true; } else { Log.w(TAG, String.format("Bad: Http ping failed (server reply %s).", connection.getResponseCode())); } } catch (final IOException e) { Log.w(TAG, "Bad: Http ping failed (no response).."); } return false; } }