2 * Copyright (C) 2010 Felix Bechstein
4 * This file is part of WebSMS.
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
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
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
;
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
;
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";
155 private static final String DATE
= "date";
157 static final String READ
= "read";
158 /** SMS DB: status. */
159 // private static final String STATUS = "status";
161 static final String TYPE
= "type";
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
= "";
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
;
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
];
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.
226 protected Connector(final Context c
) {
231 * Extract receivers from a parameters list.
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
];
246 * Read in data from Stream into String.
251 * @throws IOException
254 protected static final String
stream2str(final InputStream is
)
256 BufferedReader bufferedReader
= new BufferedReader(
257 new InputStreamReader(is
), BUFSIZE
);
258 StringBuilder data
= new StringBuilder();
260 while ((line
= bufferedReader
.readLine()) != null) {
261 data
.append(line
+ "\n");
263 bufferedReader
.close();
264 return data
.toString();
268 * Get a fresh HTTP-Connection.
273 * cookies to transmit
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
);
295 request
= new HttpPost(url
);
296 ((HttpPost
) request
).setEntity(new UrlEncodedFormEntity(postData
,
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.
322 * headers from response
325 * @throws URISyntaxException
327 * @throws MalformedCookieException
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();
336 if (url
.startsWith("https")) {
342 CookieOrigin origin
= new CookieOrigin(uri
.getHost(), port
, uri
344 CookieSpecBase cookieSpecBase
= new BrowserCompatSpec();
345 for (Header header
: headers
) {
346 for (Cookie cookie
: cookieSpecBase
.parse(header
, origin
)) {
348 String name
= cookie
.getName();
349 String value
= cookie
.getValue();
350 if (value
== null || value
.equals("")) {
353 for (Cookie c
: cookies
) {
354 if (name
.equals(c
.getName())) {
369 * Save Message to internal database.
372 * reciepients. first entry is skipped!
376 protected final void saveMessage(final String
[] reciepients
,
377 final String msgText
) {
378 if (reciepients
== null || msgText
== null) {
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
]);
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.
403 * @throws WebSMSException
406 protected abstract boolean updateMessages() throws WebSMSException
;
409 * Bootstrap: Get preferences. This default implementation odes nothing!
414 * @throws WebSMSException
417 protected boolean doBootstrap(final String
[] params
) throws WebSMSException
{
425 * @throws WebSMSException
428 protected abstract boolean sendMessage() throws WebSMSException
;
431 * Check if update is possible.
434 * true for starting, false for ending update
435 * @return true if update is possible
437 private synchronized boolean checkUpdate(final boolean startUpdate
) {
439 if (IN_UPDATE
[this.connector
]) {
442 IN_UPDATE
[this.connector
] = true;
446 IN_UPDATE
[this.connector
] = false;
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
;
459 for (int i
= 0; i
< numbers
.length
; i
++) {
460 String t
= numbers
[i
];
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
);
476 * Run IO in background.
483 protected final Boolean
doInBackground(final String
... params
) {
484 Log
.d(TAG
, "doInBackground()");
488 if (params
== null || params
[ID_ID
] == null) {
494 if (t
.equals(ID_UPDATE
)) {
495 if (!this.checkUpdate(true)) {
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
];
513 this.sendLater
= Long
.parseLong(s
);
515 this.defSender
= params
[ID_DEFSENDER
];
516 this.defPrefix
= params
[ID_DEFPREFIX
];
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());
527 Log
.d(TAG
, "doInBackground() return " + ret
);
532 * Update progress. Only ran once on startup to display progress dialog.
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) {
546 WebSMS
.dialog
.dismiss();
547 } catch (Exception e
) {
551 WebSMS
.dialogString
= c
.getString(R
.string
.bootstrap_
);
552 WebSMS
.dialog
= ProgressDialog
.show(c
, null, WebSMS
.dialogString
,
558 * Update or Create a Notification.
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);
572 noti
= new Notification(R
.drawable
.stat_notify_sms_failed
, c
573 .getString(R
.string
.notify_failed_
), System
574 .currentTimeMillis());
576 noti
.contentIntent
.cancel();
578 final Intent i
= new Intent(Intent
.ACTION_SENDTO
, Uri
579 .parse(INTENT_SCHEME_SMSTO
+ ":" + Uri
.encode(this.tos
)), c
,
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
);
596 * Push data back to GUI. Close progress dialog.
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
)) {
607 if (countUpdates
== 0) {
608 ((WebSMS
) this.context
)
609 .setProgressBarIndeterminateVisibility(false);
612 WebSMS
.dialogString
= null;
613 if (WebSMS
.dialog
!= null) {
615 WebSMS
.dialog
.dismiss();
616 WebSMS
.dialog
= null;
617 } catch (Exception e
) {
622 if (t
.equals(ID_SEND
)) {
624 this.saveMessage(this.to
, this.text
);
626 this.updateNotification(this.notification
);
629 if (this.context
instanceof IOService
) {
630 IOService
.unregister(this.notification
, !result
);
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.
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
++) {
656 int j
= s
.lastIndexOf('<');
658 int h
= s
.indexOf('>', j
);
660 s
= s
.substring(j
+ 1, h
);
666 j
= s
.lastIndexOf('<');
668 ret1
[i
] = s
.substring(0, j
).trim();
677 * Clean recipient's phone number from [ -.()].
680 * recipient's mobile number
681 * @return clean number
683 public static final String
cleanRecipient(final String recipient
) {
684 if (recipient
== null) {
687 return recipient
.replace(" ", "").replace("-", "").replace(".", "")
688 .replace("(", "").replace(")", "").replace("<", "").replace(
693 * Convert international number to national.
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());
707 * Convert international number to old format. Eg. +49123 to 0049123
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);
721 * Add sub-connectors to item list, return numer of connectors added.
726 * add names to this list
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
) {
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
]);
742 if (items
!= null && allItems
!= null) {
743 items
.add(allItems
[Connector
.CHERRY_WO_SENDER
]);
744 items
.add(allItems
[Connector
.CHERRY_W_SENDER
]);
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
]);
755 if (items
!= null && allItems
!= null) {
756 items
.add(allItems
[connector
]);
763 * Send a Message to Activity or Log.
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
);
782 if (msgType
== WebSMS
.MESSAGE_LOG
) {
783 this.failedMessage
= msg
;
790 * Send a Message to Activity or Log.
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.
807 * message's first part as resID
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
);