move connectors
[andGMXsms.git] / src / de / ub0r / android / andGMXsms / Connector.java
bloba677a6877b7b2bc277819f923e7ec56c5ad96b53
1 /*
2 * Copyright (C) 2010 Felix Bechstein
3 *
4 * This file is part of WebSMS.
5 *
6 * This program is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License as published by the Free Software
8 * Foundation; either version 3 of the License, or (at your option) any later
9 * version.
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14 * details.
16 * You should have received a copy of the GNU General Public License along with
17 * this program; If not, see <http://www.gnu.org/licenses/>.
20 package de.ub0r.android.andGMXsms;
22 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.net.URI;
27 import java.net.URISyntaxException;
28 import java.util.ArrayList;
30 import org.apache.http.Header;
31 import org.apache.http.HttpResponse;
32 import org.apache.http.client.HttpClient;
33 import org.apache.http.client.entity.UrlEncodedFormEntity;
34 import org.apache.http.client.methods.HttpGet;
35 import org.apache.http.client.methods.HttpPost;
36 import org.apache.http.client.methods.HttpRequestBase;
37 import org.apache.http.cookie.Cookie;
38 import org.apache.http.cookie.CookieOrigin;
39 import org.apache.http.cookie.MalformedCookieException;
40 import org.apache.http.impl.client.DefaultHttpClient;
41 import org.apache.http.impl.cookie.BrowserCompatSpec;
42 import org.apache.http.impl.cookie.CookieSpecBase;
43 import org.apache.http.message.BasicNameValuePair;
45 import android.app.Notification;
46 import android.app.PendingIntent;
47 import android.app.ProgressDialog;
48 import android.content.ContentValues;
49 import android.content.Context;
50 import android.content.Intent;
51 import android.net.Uri;
52 import android.os.AsyncTask;
53 import android.util.Log;
54 import de.ub0r.android.websms.connector.common.WebSMSException;
56 /**
57 * Connector is the basic Connector. Implement other real Connectors as extend.
58 * Connector will act as entry to them.
60 * @author Felix Bechstein
62 public abstract class Connector extends AsyncTask<String, Boolean, Boolean> {
63 /** Tag for output. */
64 private static final String TAG = "WebSMS.con";
66 /** Intent's scheme to send sms. */
67 public static final String INTENT_SCHEME_SMSTO = "smsto";
69 /** HTTP Response 200. */
70 static final int HTTP_SERVICE_OK = 200;
71 /** HTTP Response 401. */
72 static final int HTTP_SERVICE_UNAUTHORIZED = 401;
73 /** HTTP Response 503. */
74 static final int HTTP_SERVICE_UNAVAILABLE = 503;
76 /** Connector type: SMS. */
77 static final short SMS = 0;
78 /** Connector type: GMX. */
79 static final short GMX = 1;
80 /** Connector type: O2. */
81 static final short O2 = 2;
82 /** Connector type: Sipgate. */
83 static final short SIPGATE = 3;
84 /** Connector type: Innosend. */
85 static final short INNOSEND_FREE = 4;
86 /** Connector type: Innosend w/o sender. */
87 static final short INNOSEND_WO_SENDER = 5;
88 /** Connector type: Innosend w/ sender. */
89 static final short INNOSEND_W_SENDER = 6;
90 /** Connector type: Innosend. */
91 static final short INNOSEND = INNOSEND_WO_SENDER;
92 /** Connector type: CherrySMS w/o sender. */
93 static final short CHERRY_WO_SENDER = 7;
94 /** Connector type: CherrySMS w/ sender. */
95 static final short CHERRY_W_SENDER = 8;
96 /** Connector type: CherrySMS. */
97 static final short CHERRY = CHERRY_WO_SENDER;
98 /** Connector type: Sloono discount. */
99 static final short SLOONO_DISCOUNT = 9;
100 /** Connector type: Sloono basic. */
101 static final short SLOONO_BASIC = 10;
102 /** Connector type: Sloono pro. */
103 static final short SLOONO_PRO = 11;
104 /** Connector type: Sloono. */
105 static final short SLOONO = SLOONO_PRO;
106 /** Connector type: Arcor. */
107 static final short ARCOR = 12;
109 /** Number of connectors. */
110 static final short CONNECTORS = ARCOR + 1;
112 /** ID of Param-ID. This is to distinguish between different calls. */
113 static final int ID_ID = 0;
114 /** ID of text in array. */
115 static final int ID_TEXT = 1;
116 /** ID of recipient in array. */
117 static final int ID_TO = 2;
118 /** ID of falshsms. */
119 static final int ID_FLASHSMS = 3;
120 /** ID of custom sender. */
121 static final int ID_CUSTOMSENDER = 4;
122 /** ID of send later. */
123 static final int ID_SENDLATER = 5;
124 /** ID of default sender. */
125 static final int ID_DEFSENDER = 6;
126 /** ID of default prefix. */
127 static final int ID_DEFPREFIX = 7;
129 /** ID of mail in array. */
130 static final int ID_MAIL = 1;
131 /** ID of password in array. */
132 static final int ID_PW = 2;
134 /** Number of IDs in array for sms send. */
135 static final int IDS_SEND = 8;
137 /** ID_ID for sending a message. */
138 static final String ID_SEND = "0";
139 /** ID_ID for updating message count. */
140 static final String ID_UPDATE = "1";
141 /** ID_ID for bootstrapping. */
142 public static final String ID_BOOSTR = "2";
144 /** Parameters for updating message count. */
145 static final String[] PARAMS_UPDATE = { ID_UPDATE };
147 /** Standard buffer size. */
148 public static final int BUFSIZE = 32768;
150 /** SMS DB: address. */
151 static final String ADDRESS = "address";
152 /** SMS DB: person. */
153 // private static final String PERSON = "person";
154 /** SMS DB: date. */
155 private static final String DATE = "date";
156 /** SMS DB: read. */
157 static final String READ = "read";
158 /** SMS DB: status. */
159 // private static final String STATUS = "status";
160 /** SMS DB: type. */
161 static final String TYPE = "type";
162 /** SMS DB: body. */
163 static final String BODY = "body";
164 /** SMS DB: type - sent. */
165 static final int MESSAGE_TYPE_SENT = 2;
167 /** recipient, numbers only. */
168 protected String[] to;
169 /** recipient, names only. */
170 protected String[] toNames;
171 /** recipient, splitted but not stripped. */
172 protected String[] toFull;
173 /** recipients list. as it comes from the user. */
174 protected String tos = "";
176 /** Text. */
177 protected String text;
178 /** Send as flashSMS? */
179 protected boolean flashSMS;
180 /** Custom sender. */
181 protected String customSender;
182 /** Timestamp when to send sms. */
183 protected long sendLater;
185 /** Default prefix. */
186 private String defPrefix;
187 /** Sender. */
188 protected String defSender;
190 /** Connector is bootstrapping. */
191 static boolean inBootstrap = false;
193 /** Connector is in update. */
194 private static final boolean[] IN_UPDATE = new boolean[CONNECTORS];
196 static {
197 for (int i = 0; i < CONNECTORS; i++) {
198 IN_UPDATE[i] = false;
202 /** Type of IO Op. */
203 protected String type;
205 /** Message to log to the user. */
206 protected String failedMessage = null;
208 /** Context IO is running from. */
209 protected Context context;
211 /** Concurrent updates running. */
212 private static int countUpdates = 0;
214 /** Notification showed in case of failure. */
215 private Notification notification = null;
217 /** Type of connector. */
218 private short connector = 0;
221 * Default Constructor.
223 * @param c
224 * context
226 protected Connector(final Context c) {
227 this.context = c;
231 * Extract receivers from a parameters list.
233 * @param params
234 * parameters
235 * @return receivers of a message
237 protected static final String[] getReceivers(final String[] params) {
238 String[] ret = new String[params.length - 2];
239 for (int i = 2; i < params.length; i++) {
240 ret[i - 2] = params[i];
242 return ret;
246 * Read in data from Stream into String.
248 * @param is
249 * stream
250 * @return String
251 * @throws IOException
252 * IOException
254 protected static final String stream2str(final InputStream is)
255 throws IOException {
256 BufferedReader bufferedReader = new BufferedReader(
257 new InputStreamReader(is), BUFSIZE);
258 StringBuilder data = new StringBuilder();
259 String line = null;
260 while ((line = bufferedReader.readLine()) != null) {
261 data.append(line + "\n");
263 bufferedReader.close();
264 return data.toString();
268 * Get a fresh HTTP-Connection.
270 * @param url
271 * URL to open
272 * @param cookies
273 * cookies to transmit
274 * @param postData
275 * post data
276 * @param userAgent
277 * user agent
278 * @param referer
279 * referer
280 * @return the connection
281 * @throws IOException
283 protected static HttpResponse getHttpClient(final String url,
284 final ArrayList<Cookie> cookies,
285 final ArrayList<BasicNameValuePair> postData,
286 final String userAgent, final String referer) throws IOException {
287 // TODO flx, why ArrayList and not just List?
288 // TODO flx, this method does not return an HttpClientInstance. It
289 // should be executeRequest IMHO
290 HttpClient client = new DefaultHttpClient();
291 HttpRequestBase request;
292 if (postData == null) {
293 request = new HttpGet(url);
294 } else {
295 request = new HttpPost(url);
296 ((HttpPost) request).setEntity(new UrlEncodedFormEntity(postData,
297 "ISO-8859-15"));
299 if (referer != null) {
300 request.setHeader("Referer", referer);
302 if (userAgent != null) {
303 request.setHeader("User-Agent", userAgent);
306 if (cookies != null && cookies.size() > 0) {
307 CookieSpecBase cookieSpecBase = new BrowserCompatSpec();
308 for (Header cookieHeader : cookieSpecBase.formatCookies(cookies)) {
309 // Setting the cookie
310 request.setHeader(cookieHeader);
313 return client.execute(request);
317 * Update cookies from response.
319 * @param cookies
320 * old cookie list
321 * @param headers
322 * headers from response
323 * @param url
324 * requested url
325 * @throws URISyntaxException
326 * malformed uri
327 * @throws MalformedCookieException
328 * malformed cookie
330 protected static void updateCookies(final ArrayList<Cookie> cookies,
331 final Header[] headers, final String url)
332 throws URISyntaxException, MalformedCookieException {
333 final URI uri = new URI(url);
334 int port = uri.getPort();
335 if (port < 0) {
336 if (url.startsWith("https")) {
337 port = 443;
338 } else {
339 port = 80;
342 CookieOrigin origin = new CookieOrigin(uri.getHost(), port, uri
343 .getPath(), false);
344 CookieSpecBase cookieSpecBase = new BrowserCompatSpec();
345 for (Header header : headers) {
346 for (Cookie cookie : cookieSpecBase.parse(header, origin)) {
347 // THE cookie
348 String name = cookie.getName();
349 String value = cookie.getValue();
350 if (value == null || value.equals("")) {
351 continue;
353 for (Cookie c : cookies) {
354 if (name.equals(c.getName())) {
355 cookies.remove(c);
356 cookies.add(cookie);
357 name = null;
358 break;
361 if (name != null) {
362 cookies.add(cookie);
369 * Save Message to internal database.
371 * @param reciepients
372 * reciepients. first entry is skipped!
373 * @param msgText
374 * text of message.
376 protected final void saveMessage(final String[] reciepients,
377 final String msgText) {
378 if (reciepients == null || msgText == null) {
379 return;
381 for (int i = 0; i < reciepients.length; i++) {
382 if (reciepients[i] == null || reciepients[i].length() == 0) {
383 continue; // skip empty recipients
385 // save sms to content://sms/sent
386 ContentValues values = new ContentValues();
387 values.put(ADDRESS, reciepients[i]);
388 values.put(READ, 1);
389 values.put(TYPE, MESSAGE_TYPE_SENT);
390 values.put(BODY, msgText);
391 if (this.sendLater > 0) {
392 values.put(DATE, this.sendLater);
394 this.context.getContentResolver().insert(
395 Uri.parse("content://sms/sent"), values);
400 * Get free sms count.
402 * @return ok?
403 * @throws WebSMSException
404 * WebSMSException
406 protected abstract boolean updateMessages() throws WebSMSException;
409 * Bootstrap: Get preferences. This default implementation odes nothing!
411 * @param params
412 * Parameters
413 * @return ok?
414 * @throws WebSMSException
415 * WebSMSException
417 protected boolean doBootstrap(final String[] params) throws WebSMSException {
418 return false;
422 * Send sms.
424 * @return ok?
425 * @throws WebSMSException
426 * WebSMSException
428 protected abstract boolean sendMessage() throws WebSMSException;
431 * Check if update is possible.
433 * @param startUpdate
434 * true for starting, false for ending update
435 * @return true if update is possible
437 private synchronized boolean checkUpdate(final boolean startUpdate) {
438 if (startUpdate) {
439 if (IN_UPDATE[this.connector]) {
440 return false;
441 } else {
442 IN_UPDATE[this.connector] = true;
443 return true;
445 } else {
446 IN_UPDATE[this.connector] = false;
447 return true;
452 * Prepare reciepients before sending.
454 private void prepareSend() {
455 // fetch text/recipient
456 final String[] numbers = this.parseReciepients(this.tos);
457 final String prefix = this.defPrefix;
458 // fix number prefix
459 for (int i = 0; i < numbers.length; i++) {
460 String t = numbers[i];
461 if (t != null) {
462 if (!t.startsWith("+")) {
463 if (t.startsWith("00")) {
464 t = "+" + t.substring(2);
465 } else if (t.startsWith("0")) {
466 t = prefix + t.substring(1);
470 numbers[i] = Connector.cleanRecipient(t);
472 this.to = numbers;
476 * Run IO in background.
478 * @param params
479 * (text,recipient)
480 * @return ok?
482 @Override
483 protected final Boolean doInBackground(final String... params) {
484 Log.d(TAG, "doInBackground()");
485 boolean ret = false;
486 try {
487 String t;
488 if (params == null || params[ID_ID] == null) {
489 t = ID_UPDATE;
490 } else {
491 t = params[ID_ID];
493 this.type = t;
494 if (t.equals(ID_UPDATE)) {
495 if (!this.checkUpdate(true)) {
496 return false;
498 this.publishProgress(false);
499 ret = this.updateMessages();
500 this.checkUpdate(false);
501 } else if (t.equals(ID_BOOSTR)) {
502 this.publishProgress(false);
503 ret = this.doBootstrap(params);
504 } else if (t.equals(ID_SEND)) {
505 this.text = params[ID_TEXT];
506 this.tos = params[ID_TO];
507 this.flashSMS = params[ID_FLASHSMS] != null;
508 this.customSender = params[ID_CUSTOMSENDER];
509 final String s = params[ID_SENDLATER];
510 if (s == null) {
511 this.sendLater = -1;
512 } else {
513 this.sendLater = Long.parseLong(s);
515 this.defSender = params[ID_DEFSENDER];
516 this.defPrefix = params[ID_DEFPREFIX];
517 this.prepareSend();
518 this.notification = this.updateNotification(null);
519 IOService.register(this.notification);
520 this.publishProgress(false);
521 ret = this.sendMessage();
523 } catch (WebSMSException e) {
524 this.pushMessage(WebSMS.MESSAGE_LOG, e.getMessage());
525 ret = false;
527 Log.d(TAG, "doInBackground() return " + ret);
528 return ret;
532 * Update progress. Only ran once on startup to display progress dialog.
534 * @param progress
535 * finished?
537 @Override
538 protected final void onProgressUpdate(final Boolean... progress) {
539 final Context c = this.context;
540 final String t = this.type;
541 if (t.equals(ID_UPDATE)) {
542 ((WebSMS) c).setProgressBarIndeterminateVisibility(true);
543 } else if (t.equals(ID_BOOSTR)) {
544 if (WebSMS.dialog != null) {
545 try {
546 WebSMS.dialog.dismiss();
547 } catch (Exception e) {
548 // do nothing
551 WebSMS.dialogString = c.getString(R.string.bootstrap_);
552 WebSMS.dialog = ProgressDialog.show(c, null, WebSMS.dialogString,
553 true);
558 * Update or Create a Notification.
560 * @param n
561 * Notification for update
562 * @return created/updated Notification
564 private Notification updateNotification(final Notification n) {
565 Notification noti = n;
566 final Context c = this.context;
567 String rcvs = this.tos.trim();
568 if (rcvs.endsWith(",")) {
569 rcvs = rcvs.substring(0, rcvs.length() - 1);
571 if (noti == null) {
572 noti = new Notification(R.drawable.stat_notify_sms_failed, c
573 .getString(R.string.notify_failed_), System
574 .currentTimeMillis());
575 } else {
576 noti.contentIntent.cancel();
578 final Intent i = new Intent(Intent.ACTION_SENDTO, Uri
579 .parse(INTENT_SCHEME_SMSTO + ":" + Uri.encode(this.tos)), c,
580 WebSMS.class);
581 i.putExtra(Intent.EXTRA_TEXT, this.text);
582 if (this.failedMessage == null) {
583 this.failedMessage = c.getString(R.string.notify_failed_);
585 i.putExtra(WebSMS.EXTRA_ERRORMESSAGE, this.failedMessage);
586 i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
587 final PendingIntent cIntent = PendingIntent.getActivity(c, 0, i, 0);
588 noti.setLatestEventInfo(c, c.getString(R.string.notify_failed) + " "
589 + this.failedMessage, rcvs + ": " + this.text, cIntent);
590 noti.flags |= Notification.FLAG_AUTO_CANCEL;
591 Log.d(TAG, "update notification " + this.failedMessage);
592 return noti;
596 * Push data back to GUI. Close progress dialog.
598 * @param result
599 * result
601 @Override
602 protected final void onPostExecute(final Boolean result) {
603 Log.d(TAG, "onPostExecute(" + result + ")");
604 final String t = this.type;
605 if (t.equals(ID_UPDATE)) {
606 --countUpdates;
607 if (countUpdates == 0) {
608 ((WebSMS) this.context)
609 .setProgressBarIndeterminateVisibility(false);
611 } else {
612 WebSMS.dialogString = null;
613 if (WebSMS.dialog != null) {
614 try {
615 WebSMS.dialog.dismiss();
616 WebSMS.dialog = null;
617 } catch (Exception e) {
618 System.gc();
622 if (t.equals(ID_SEND)) {
623 if (result) {
624 this.saveMessage(this.to, this.text);
625 } else {
626 this.updateNotification(this.notification);
629 if (this.context instanceof IOService) {
630 IOService.unregister(this.notification, !result);
632 System.gc();
633 Log.d(TAG, "onPostExecute(" + result + ") return");
637 * Parse a String of "name (number), name (number), number, ..." to an array
638 * of numbers. It will fill this.toFull and this.toNames too.
640 * @param reciepients
641 * reciepients
642 * @return array of reciepients
644 private String[] parseReciepients(final String reciepients) {
645 String s = reciepients.trim();
646 if (s.endsWith(",")) {
647 s = s.substring(0, s.length() - 1);
649 String[] ret0 = s.split(",");
650 final int e = ret0.length;
651 final String[] ret = new String[e];
652 final String[] ret1 = new String[e];
653 for (int i = 0; i < e; i++) {
654 // get number only
655 s = ret0[i];
656 int j = s.lastIndexOf('<');
657 if (j >= 0) {
658 int h = s.indexOf('>', j);
659 if (h > 0) {
660 s = s.substring(j + 1, h);
663 ret[i] = s;
664 // get name only
665 s = ret0[i];
666 j = s.lastIndexOf('<');
667 if (j >= 0) {
668 ret1[i] = s.substring(0, j).trim();
671 this.toFull = ret0;
672 this.toNames = ret1;
673 return ret;
677 * Clean recipient's phone number from [ -.()].
679 * @param recipient
680 * recipient's mobile number
681 * @return clean number
683 public static final String cleanRecipient(final String recipient) {
684 if (recipient == null) {
685 return "";
687 return recipient.replace(" ", "").replace("-", "").replace(".", "")
688 .replace("(", "").replace(")", "").replace("<", "").replace(
689 ">", "").trim();
693 * Convert international number to national.
695 * @param number
696 * international number
697 * @return national number
699 final String international2national(final String number) {
700 if (number.startsWith(this.defPrefix)) {
701 return '0' + number.substring(this.defPrefix.length());
703 return number;
707 * Convert international number to old format. Eg. +49123 to 0049123
709 * @param number
710 * international number starting with +
711 * @return international number in old format starting with 00
713 static final String international2oldformat(final String number) {
714 if (number.startsWith("+")) {
715 return "00" + number.substring(1);
717 return number;
721 * Add sub-connectors to item list, return numer of connectors added.
723 * @param connector
724 * connector to add
725 * @param items
726 * add names to this list
727 * @param allItems
728 * list of all connctors
729 * @return number of connectors added
731 static final short getSubConnectors(final short connector,
732 final ArrayList<String> items, final String[] allItems) {
733 switch (connector) {
734 case INNOSEND:
735 if (items != null && allItems != null) {
736 items.add(allItems[Connector.INNOSEND_FREE]);
737 items.add(allItems[Connector.INNOSEND_WO_SENDER]);
738 items.add(allItems[Connector.INNOSEND_W_SENDER]);
740 return 3;
741 case CHERRY:
742 if (items != null && allItems != null) {
743 items.add(allItems[Connector.CHERRY_WO_SENDER]);
744 items.add(allItems[Connector.CHERRY_W_SENDER]);
746 return 2;
747 case SLOONO: // do not change order here!
748 if (items != null && allItems != null) {
749 items.add(allItems[Connector.SLOONO_DISCOUNT]);
750 items.add(allItems[Connector.SLOONO_BASIC]);
751 items.add(allItems[Connector.SLOONO_PRO]);
753 return 3;
754 default:
755 if (items != null && allItems != null) {
756 items.add(allItems[connector]);
758 return 1;
763 * Send a Message to Activity or Log.
765 * @param msgType
766 * message type
767 * @param msg
768 * message
770 protected final void pushMessage(final int msgType, final String msg) {
771 final Context c = this.context;
772 if (c instanceof WebSMS) {
773 WebSMS.pushMessage(msgType, msg);
774 } else if (c instanceof IOService) {
775 if (msgType == WebSMS.MESSAGE_FREECOUNT) {
776 WebSMS.pushMessage(msgType, msg);
778 if (msg == null) {
779 Log.d(TAG, "null");
780 } else {
781 Log.d(TAG, msg);
782 if (msgType == WebSMS.MESSAGE_LOG) {
783 this.failedMessage = msg;
790 * Send a Message to Activity or Log.
792 * @param msgType
793 * message type
794 * @param msg
795 * message
797 protected final void pushMessage(final int msgType, final int msg) {
798 this.pushMessage(msgType, this.context.getString(msg));
802 * Send a Message to Activity or Log.
804 * @param msgType
805 * message type
806 * @param msgFront
807 * message's first part as resID
808 * @param msgTail
809 * message's last part
811 protected final void pushMessage(final int msgType, final int msgFront,
812 final String msgTail) {
813 this.pushMessage(msgType, this.context.getString(msgFront) + msgTail);