From 2e5049c9c08b1989b4bfbbfbf479d83dbe2b75a9 Mon Sep 17 00:00:00 2001 From: Marius Gavrilescu Date: Tue, 12 Mar 2013 18:27:54 +0200 Subject: [PATCH] Merge SendHttpMessageAsyncTask and PollServerAsyncTask The resulting class is named HttpCallExxecutableRunnable. It uses the new ExecutableRunnable class and lets the users make a HTTP call to a server URL and pass in a callback to use the response. --- src/ro/ieval/fonbot/DynamicEventReceiver.java | 2 +- src/ro/ieval/fonbot/FonBotApplication.java | 2 +- src/ro/ieval/fonbot/FonBotMainActivity.java | 49 +++++- src/ro/ieval/fonbot/GCMIntentService.java | 13 +- src/ro/ieval/fonbot/Heavy.java | 2 +- ...k.java => HttpCallExecutableRunnable.java} | 136 ++++++--------- .../ieval/fonbot/LocalBroadcastReceiver.java | 2 +- src/ro/ieval/fonbot/PollResultCallback.java | 104 +++++++++++ src/ro/ieval/fonbot/PollServerAsyncTask.java | 162 ------------------ .../ieval/fonbot/RemoteCrashdumpHandler.java | 4 +- src/ro/ieval/fonbot/Utils.java | 13 +- 11 files changed, 221 insertions(+), 268 deletions(-) rename src/ro/ieval/fonbot/{SendHttpMessageAsyncTask.java => HttpCallExecutableRunnable.java} (51%) create mode 100644 src/ro/ieval/fonbot/PollResultCallback.java delete mode 100644 src/ro/ieval/fonbot/PollServerAsyncTask.java diff --git a/src/ro/ieval/fonbot/DynamicEventReceiver.java b/src/ro/ieval/fonbot/DynamicEventReceiver.java index d7acf54..0912e88 100644 --- a/src/ro/ieval/fonbot/DynamicEventReceiver.java +++ b/src/ro/ieval/fonbot/DynamicEventReceiver.java @@ -47,7 +47,7 @@ public final class DynamicEventReceiver extends BroadcastReceiver { if(action.equals(Intent.ACTION_SCREEN_ON)){ if(PreferenceManager.getDefaultSharedPreferences(context).getBoolean("poll_on_screen_on", false)) - new PollServerAsyncTask(context).execute(); + Utils.pollServer(context); } else if(action.equals(Intent.ACTION_BATTERY_CHANGED)) Heavy.describeBatteryLevel(context, null, toNonNull(MessageType.BATTERY_CHANGED)); else if(action.equals(Intent.ACTION_HEADSET_PLUG)){ diff --git a/src/ro/ieval/fonbot/FonBotApplication.java b/src/ro/ieval/fonbot/FonBotApplication.java index be774ce..af5a70c 100644 --- a/src/ro/ieval/fonbot/FonBotApplication.java +++ b/src/ro/ieval/fonbot/FonBotApplication.java @@ -48,7 +48,7 @@ public final class FonBotApplication extends Application { final TelephonyManager tman=(TelephonyManager) getSystemService(TELEPHONY_SERVICE); tman.listen(new FonBotPhoneStateListener(this), PhoneStateListener.LISTEN_CALL_STATE); - new PollServerAsyncTask(this).execute(); + Utils.pollServer(this); startService(new Intent(this, FonBotMainService.class)); diff --git a/src/ro/ieval/fonbot/FonBotMainActivity.java b/src/ro/ieval/fonbot/FonBotMainActivity.java index 7ef45b8..2ec56e2 100644 --- a/src/ro/ieval/fonbot/FonBotMainActivity.java +++ b/src/ro/ieval/fonbot/FonBotMainActivity.java @@ -3,11 +3,11 @@ package ro.ieval.fonbot; import static ro.ieval.fonbot.R.string.logging_in; import static ro.ieval.fonbot.Utils.toNonNull; -import java.util.Collections; - +import java.io.InputStream; import org.eclipse.jdt.annotation.Nullable; import ro.ieval.fonbot.FonBotMainService.Binder; +import ro.ieval.fonbot.HttpCallExecutableRunnable.ResultCallback; import ro.ieval.fonbot.Utils.OngoingEvent; import android.app.ListActivity; import android.content.BroadcastReceiver; @@ -150,8 +150,8 @@ public final class FonBotMainActivity extends ListActivity { public void onReceive(@Nullable final Context context, @Nullable final Intent intent) { if(intent==null) return; - resultTextView.setText(intent.getStringExtra(SendHttpMessageAsyncTask.EXTRA_RESPONSE_MESSAGE)); - final int responseCode=intent.getIntExtra(SendHttpMessageAsyncTask.EXTRA_RESPONSE_CODE, 0); + resultTextView.setText(intent.getStringExtra(BroadcastResultCallback.EXTRA_RESPONSE_MESSAGE)); + final int responseCode=intent.getIntExtra(BroadcastResultCallback.EXTRA_RESPONSE_CODE, 0); if(responseCode>=200&&responseCode<300) resultTextView.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.tick, 0, 0); else @@ -159,6 +159,37 @@ public final class FonBotMainActivity extends ListActivity { } } + /** + * Implementation of ResultCallback that broadcasts a {@link #LOGIN_BROADCAST} + * + * @author Marius Gavrilescu + */ + public final class BroadcastResultCallback implements ResultCallback{ + /** + * Extra: the response message + */ + public static final String EXTRA_RESPONSE_MESSAGE="response_message"; + /** + * Extra: the response code + */ + public static final String EXTRA_RESPONSE_CODE="response_code"; + + @Override + public void onResult(final int responseCode, final String responseMessage, final InputStream inputStream) { + final Intent intent=new Intent(LOGIN_BROADCAST); + intent.putExtra(EXTRA_RESPONSE_MESSAGE, responseMessage); + intent.putExtra(EXTRA_RESPONSE_CODE, responseCode); + sendBroadcast(intent); + } + + @Override + public void onError(final String error) { + final Intent intent=new Intent(LOGIN_BROADCAST); + intent.putExtra(EXTRA_RESPONSE_MESSAGE, error); + sendBroadcast(intent); + } + } + /** * The one instance of {@link OngoingUpdateReceiver} */ @@ -172,13 +203,17 @@ public final class FonBotMainActivity extends ListActivity { */ private final BroadcastReceiver loginReceiver=new LoginReceiver(); /** - * The broadcast sent by {@link SendHttpMessageAsyncTask} when logging in. + * The broadcast sent by {@link HttpCallExecutableRunnable} when logging in. */ private static final String LOGIN_BROADCAST="ro.ieval.fonbot.LOGIN_RESULT"; /** * IntentFilter for {@link #loginReceiver} */ private static final IntentFilter LOGIN_FILTER=new IntentFilter(LOGIN_BROADCAST); + /** + * The one instance of {@link BroadcastResultCallback} + */ + private final BroadcastResultCallback broadcastResultCallback=new BroadcastResultCallback(); /** * The one instance of {@link ServiceConnection} */ @@ -202,8 +237,8 @@ public final class FonBotMainActivity extends ListActivity { protected void onStart() { super.onStart(); resultTextView.setText(logging_in); - new SendHttpMessageAsyncTask("/ok", toNonNull(Collections.
emptySet()), - LOGIN_BROADCAST,this).execute(); + new HttpCallExecutableRunnable("/ok", null, + toNonNull(getApplicationContext()), toNonNull(broadcastResultCallback)).execute(); connection.refreshAdapter(); } diff --git a/src/ro/ieval/fonbot/GCMIntentService.java b/src/ro/ieval/fonbot/GCMIntentService.java index bf17e5f..84a3c79 100644 --- a/src/ro/ieval/fonbot/GCMIntentService.java +++ b/src/ro/ieval/fonbot/GCMIntentService.java @@ -4,9 +4,6 @@ import org.eclipse.jdt.annotation.Nullable; import android.content.Context; import android.content.Intent; -import android.os.Handler; -import android.os.Looper; - import com.google.android.gcm.GCMBaseIntentService; /* @@ -34,9 +31,6 @@ import com.google.android.gcm.GCMBaseIntentService; * @author Marius Gavrilescu */ public class GCMIntentService extends GCMBaseIntentService { - /** Handler instance */ - private final Handler handler=new Handler(Looper.getMainLooper()); - /** * Constructs a GCMIntentService with the iEval sender id */ @@ -54,12 +48,7 @@ public class GCMIntentService extends GCMBaseIntentService { if(context==null) return; - handler.post(new Runnable(){ - @Override - public void run() { - new PollServerAsyncTask(context).execute(); - } - }); + Utils.pollServer(context); } @Override diff --git a/src/ro/ieval/fonbot/Heavy.java b/src/ro/ieval/fonbot/Heavy.java index 905b577..6e91f2f 100644 --- a/src/ro/ieval/fonbot/Heavy.java +++ b/src/ro/ieval/fonbot/Heavy.java @@ -1507,7 +1507,7 @@ final class Heavy { */ public static void poll(final Context context, final Address replyTo) { Utils.sendMessage(context, replyTo, polling_server); - new PollServerAsyncTask(context).execute(); + Utils.pollServer(context); } /** diff --git a/src/ro/ieval/fonbot/SendHttpMessageAsyncTask.java b/src/ro/ieval/fonbot/HttpCallExecutableRunnable.java similarity index 51% rename from src/ro/ieval/fonbot/SendHttpMessageAsyncTask.java rename to src/ro/ieval/fonbot/HttpCallExecutableRunnable.java index 68f7319..621f0bc 100644 --- a/src/ro/ieval/fonbot/SendHttpMessageAsyncTask.java +++ b/src/ro/ieval/fonbot/HttpCallExecutableRunnable.java @@ -3,6 +3,7 @@ package ro.ieval.fonbot; import static ro.ieval.fonbot.R.string.*; import static ro.ieval.fonbot.Utils.toNonNull; +import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; @@ -11,8 +12,6 @@ import java.util.Collection; import org.eclipse.jdt.annotation.Nullable; import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; import android.preference.PreferenceManager; import android.util.Base64; import android.util.Log; @@ -39,28 +38,37 @@ import com.google.android.gcm.GCMRegistrar; */ /** - * AsyncTask that sends a HTTP request to the server and broadcasts back the response message & response code + * ExecutableRunnable that makes a HTTP call to the server and hands the response to a callback * * @author Marius Gavrilescu */ -public final class SendHttpMessageAsyncTask extends AsyncTask { +public final class HttpCallExecutableRunnable extends ExecutableRunnable{ /** - * Extra: the response message - */ - public static final String EXTRA_RESPONSE_MESSAGE="response_message"; - /** - * Extra: the response code + * Callback which is run after a HTTP call. + * + * @author Marius Gavrilescu */ - public static final String EXTRA_RESPONSE_CODE="response_code"; + public static interface ResultCallback{ + /** + * Callback invoked if the HTTP call is successful. + * + * @param responseCode HTTP response code + * @param responseMessage HTTP response message + * @param inputStream HTTP content InputStream + */ + public void onResult(final int responseCode, final String responseMessage, final InputStream inputStream); + /** + * Callback invoked if the HTTP call is unsuccessful. + * + * @param error localized error message + */ + public void onError(final String error); + } /** * List of extra request headers. */ private final Collection
headers; - /** - * Action to send back in the response broadcast - */ - private final String broadcast; /** * Context instance used by this class */ @@ -74,117 +82,87 @@ public final class SendHttpMessageAsyncTask extends AsyncTask headers, final Context context){ - super(); - this.path=path; - this.headers=headers; - this.broadcast=null;//NOPMD final field - this.context=context; - this.data=null;//NOPMD final field - } + private final ResultCallback callback; /** - * Constructs a SendHttpMessageAsyncTask which sends a binary message and does not broadcast the response. + * Constructs a SendHttpMessageAsyncTask which sends a binary message. * * @param path URL path * @param headers the extra headers * @param context the context instance + * @param resultCallback {@link ResultCallback} instance * @param data the message to send */ - public SendHttpMessageAsyncTask(final String path, final Collection
headers, final Context context, final byte[] data){//NOPMD array is supposed to be immutable. - super(); + public HttpCallExecutableRunnable(final String path, final @Nullable Collection
headers, final Context context, final @Nullable ResultCallback resultCallback, final byte[] data){//NOPMD array is supposed to be immutable. this.path=path; this.headers=headers; - this.broadcast=null;//NOPMD final field this.context=context; + this.callback=resultCallback; this.data=data; } /** - * Constructs a SendHttpMessageAsyncTask which sends a string-based message and broadcasts the response. + * Constructs a SendHttpMessageAsyncTask which sends a text message. * * @param path URL path * @param headers the extra headers - * @param broadcast the broadcast to send * @param context the context instance + * @param resultCallback {@link ResultCallback} instance + * @param message message to send */ - public SendHttpMessageAsyncTask(final String path, final Collection
headers, final String broadcast, final Context context){ - super(); + public HttpCallExecutableRunnable(final String path, final @Nullable Collection
headers, final Context context, final @Nullable ResultCallback resultCallback, final String... message){ this.path=path; this.headers=headers; - this.broadcast=broadcast; this.context=context; - this.data=null;//NOPMD final field + this.callback=resultCallback; + if(message.length == 0) + this.data=null;//NOPMD final field + else + this.data=Utils.join(" ", message).getBytes(); } @Override - protected String doInBackground(final @Nullable String... args) { - if(args==null) - return ""; - - final byte[] msg; - if(data==null && args.length > 0) - msg=Utils.join(" ", args).getBytes(); - else if(data!=null) - msg=data; - else - msg=null; - + public void run() { try { final URL url=Utils.getServerURL(toNonNull(context),toNonNull(path)); final HttpURLConnection conn=(HttpURLConnection) url.openConnection(); - if(msg!=null){ + if(data!=null){ conn.setDoOutput(true); - conn.setFixedLengthStreamingMode(msg.length); + conn.setFixedLengthStreamingMode(data.length); } conn.setRequestProperty("X-ID", GCMRegistrar.getRegistrationId(context)); final String user=PreferenceManager.getDefaultSharedPreferences(context).getString("username", null); final String password=PreferenceManager.getDefaultSharedPreferences(context).getString("password", null); - if(user == null || password == null) - return toNonNull(context.getString(user_or_password_not_set)); + if(user == null || password == null){ + if(callback!=null) + callback.onError(toNonNull(context.getString(user_or_password_not_set))); + return; + } conn.setRequestProperty("Authorization", "Basic "+Base64.encodeToString( (user+':'+password).getBytes(), Base64.NO_WRAP)); - for (Header header : headers) - conn.setRequestProperty(header.name, header.value); + if(headers != null) + for (final Header header : headers) + conn.setRequestProperty(header.name, header.value); conn.connect(); - if(msg!=null){ + if(data!=null){ final OutputStream stream=conn.getOutputStream(); - stream.write(msg); + stream.write(data); stream.close(); } Log.d(getClass().getName(),"HTTP Response: "+conn.getResponseCode()+" "+conn.getResponseMessage()); - conn.disconnect(); final String message=conn.getResponseMessage(); - responseCode=conn.getResponseCode(); - if(message==null) - return toNonNull(context.getString(no_response_returned_from_server)); - return Utils.parseHttpMessage(message); + if(message==null && callback != null) + callback.onError(toNonNull(context.getString(no_response_returned_from_server))); + else if(message != null && callback != null) + callback.onResult(conn.getResponseCode(), Utils.parseHttpMessage(message), + toNonNull(conn.getInputStream())); + conn.disconnect(); } catch (Exception e) { e.printStackTrace(); - return toNonNull(context.getString(connection_error)); - } - } - - @Override - protected void onPostExecute(final @Nullable String result) { - super.onPostExecute(result); - if(broadcast!=null){ - final Intent intent=new Intent(broadcast); - intent.putExtra(EXTRA_RESPONSE_MESSAGE, result); - intent.putExtra(EXTRA_RESPONSE_CODE, responseCode); - context.sendBroadcast(intent); + callback.onError(toNonNull(context.getString(connection_error))); } } } diff --git a/src/ro/ieval/fonbot/LocalBroadcastReceiver.java b/src/ro/ieval/fonbot/LocalBroadcastReceiver.java index 88cf462..2aade7f 100644 --- a/src/ro/ieval/fonbot/LocalBroadcastReceiver.java +++ b/src/ro/ieval/fonbot/LocalBroadcastReceiver.java @@ -43,7 +43,7 @@ public final class LocalBroadcastReceiver extends BroadcastReceiver { final String action=intent.getAction(); if(action.equals(LocalBroadcastReceiver.ACTION_POLL_ALARM)) - new PollServerAsyncTask(context).execute(); + Utils.pollServer(context); } } diff --git a/src/ro/ieval/fonbot/PollResultCallback.java b/src/ro/ieval/fonbot/PollResultCallback.java new file mode 100644 index 0000000..2517fce --- /dev/null +++ b/src/ro/ieval/fonbot/PollResultCallback.java @@ -0,0 +1,104 @@ +package ro.ieval.fonbot; + +import static ro.ieval.fonbot.Utils.toNonNull; + +import java.io.InputStream; + +import org.json.JSONArray; +import org.json.JSONObject; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import ro.ieval.fonbot.Address.Protocol; +import ro.ieval.fonbot.HttpCallExecutableRunnable.ResultCallback; + +/* +* Copyright © 2013 Marius Gavrilescu +* +* This file is part of FonBot. +* +* FonBot is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* FonBot 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with FonBot. If not, see . +*/ + +/** + * ResultCallback implementation that polls the server for pending commands. + * + * @author Marius Gavrilescu + */ +final class PollResultCallback implements ResultCallback { + /** Context instance */ + private final Context context; + + /** + * Construct a PollResultCallback with a Context. + * + * @param context Context instance + */ + public PollResultCallback(final Context context) { + this.context=context; + } + + @Override + public void onResult(int responseCode, String responseMessage, + InputStream inputStream) { + if(responseCode!=200) + return; + + final Handler handler=new Handler(Looper.getMainLooper()); + + try{ + final JSONArray array; + { + final byte[] buf=new byte[4096*1024]; + inputStream.read(buf); + array=new JSONArray(new String(buf)); + } + + for(int i=0;i. - */ - -/** - * AsyncTask that polls the server for pending commands and executes any commands found. - * - * @author Marius Gavrilescu - */ -public final class PollServerAsyncTask extends AsyncTask> { - /** - * POJO that represents a command in a basic form: a command name, a list of arguments and a reply address, all of them being strings - * - * @author Marius Gavrilescu - */ - final class Command{ - /** - * The command name - */ - final String command;//NOPMD no confusion here - /** - * The list of command arguments - */ - final String[] args; - /** - * The reply address, excluding the protocol - */ - final String replyto; - - /** - * Construct a Command from its three parts - * - * @param command the command name - * @param args the command arguments - * @param replyto the reply address - */ - Command(final String command, final String[] args, final String replyto) {//NOPMD array is immutable - this.command=command; - this.args=args; - this.replyto=replyto; - } - } - - /** Context instance */ - private final Context context; - - /** - * Constructs a PollServerAsyncTask. - * - * @param context Context instance - */ - public PollServerAsyncTask(final Context context) { - this.context=context; - } - - @Override - protected List doInBackground(final @Nullable Void... params) { - Log.d(getClass().getName(), "Polling server"); - final List commands=new ArrayList(10); - try { - final URL url=Utils.getServerURL(toNonNull(context),"/get"); - final HttpURLConnection conn=(HttpURLConnection) url.openConnection(); - conn.setRequestProperty("X-ID", GCMRegistrar.getRegistrationId(context)); - final String user=PreferenceManager.getDefaultSharedPreferences(context).getString("username", null); - final String password=PreferenceManager.getDefaultSharedPreferences(context).getString("password", null); - if(user == null || password == null) - return commands; - - conn.setRequestProperty("Authorization", "Basic "+Base64.encodeToString( - (user+':'+password).getBytes(), Base64.NO_WRAP)); - conn.connect(); - Log.d(getClass().getName(), "Server poll got response code "+conn.getResponseCode()+" and message "+conn.getResponseMessage()); - if(conn.getResponseCode()!=200) - return commands; - - final JSONArray array; - { - final byte[] buf=new byte[4096*1024]; - conn.getInputStream().read(buf); - array=new JSONArray(new String(buf)); - } - - for(int i=0;i commands) { - if(commands==null) - return; - for (Command command : commands) { - Log.d(getClass().getName(), "Poll got command "+command.command+" with "+((command.args.length==0)?"no args":"args "+Utils.join( - " ",toNonNull(command.args)))); - Utils.processCommand( - toNonNull(context), - toNonNull(command.command), - toNonNull(command.args), - new Address(toNonNull(Protocol.HTTP), command.replyto));//NOPMD address depends on command - } - } -} diff --git a/src/ro/ieval/fonbot/RemoteCrashdumpHandler.java b/src/ro/ieval/fonbot/RemoteCrashdumpHandler.java index e228869..e09e31e 100644 --- a/src/ro/ieval/fonbot/RemoteCrashdumpHandler.java +++ b/src/ro/ieval/fonbot/RemoteCrashdumpHandler.java @@ -59,8 +59,8 @@ final class RemoteCrashdumpHandler implements UncaughtExceptionHandler { ex.printStackTrace(pw); pw.close(); - new SendHttpMessageAsyncTask("/crashdump", toNonNull(Collections.
emptyList()), - toNonNull(context), toNonNull(baos.toByteArray())).execute(); + new HttpCallExecutableRunnable("/crashdump", toNonNull(Collections.
emptyList()), + toNonNull(context), null, toNonNull(baos.toByteArray())).execute(); try { baos.close(); diff --git a/src/ro/ieval/fonbot/Utils.java b/src/ro/ieval/fonbot/Utils.java index d9060eb..e14095e 100644 --- a/src/ro/ieval/fonbot/Utils.java +++ b/src/ro/ieval/fonbot/Utils.java @@ -316,8 +316,8 @@ public final class Utils { public static void sendMessage(final Context context, final Address address, final String message){ switch(address.protocol){ case HTTP: - new SendHttpMessageAsyncTask("/send", toNonNull(Arrays.asList( - new Header("X-Destination", toNonNull(address.data)))), context).execute(message); + new HttpCallExecutableRunnable("/send", toNonNull(Arrays.asList( + new Header("X-Destination", toNonNull(address.data)))), context, null, message).execute(); break; case SMS: @@ -425,6 +425,15 @@ public final class Utils { return url; } + /** + * Poll the server for pending commands. + * + * @param context Context instance + */ + public static void pollServer(final Context context){ + new HttpCallExecutableRunnable("/get", null, context, new PollResultCallback(context)).execute(); + } + /** * Executes a given command * -- 2.39.2