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/>.
19 package de
.ub0r
.android
.websms
;
21 import java
.util
.ArrayList
;
22 import java
.util
.Calendar
;
24 import android
.app
.Activity
;
25 import android
.app
.AlertDialog
;
26 import android
.app
.DatePickerDialog
;
27 import android
.app
.Dialog
;
28 import android
.app
.ProgressDialog
;
29 import android
.app
.DatePickerDialog
.OnDateSetListener
;
30 import android
.app
.TimePickerDialog
.OnTimeSetListener
;
31 import android
.content
.ActivityNotFoundException
;
32 import android
.content
.DialogInterface
;
33 import android
.content
.Intent
;
34 import android
.content
.SharedPreferences
;
35 import android
.database
.Cursor
;
36 import android
.net
.Uri
;
37 import android
.os
.Bundle
;
38 import android
.os
.Handler
;
39 import android
.os
.Message
;
40 import android
.preference
.PreferenceManager
;
41 import android
.provider
.Contacts
.PeopleColumns
;
42 import android
.provider
.Contacts
.Phones
;
43 import android
.provider
.Contacts
.PhonesColumns
;
44 import android
.telephony
.TelephonyManager
;
45 import android
.telephony
.gsm
.SmsMessage
;
46 import android
.text
.Editable
;
47 import android
.text
.TextWatcher
;
48 import android
.util
.Log
;
49 import android
.view
.Menu
;
50 import android
.view
.MenuInflater
;
51 import android
.view
.MenuItem
;
52 import android
.view
.View
;
53 import android
.view
.Window
;
54 import android
.view
.View
.OnClickListener
;
55 import android
.widget
.Button
;
56 import android
.widget
.CheckBox
;
57 import android
.widget
.DatePicker
;
58 import android
.widget
.EditText
;
59 import android
.widget
.MultiAutoCompleteTextView
;
60 import android
.widget
.TextView
;
61 import android
.widget
.TimePicker
;
62 import android
.widget
.Toast
;
64 import com
.admob
.android
.ads
.AdView
;
66 import de
.ub0r
.android
.websms
.connector
.common
.Connector
;
67 import de
.ub0r
.android
.websms
.connector
.common
.ConnectorCommand
;
68 import de
.ub0r
.android
.websms
.connector
.common
.ConnectorSpec
;
69 import de
.ub0r
.android
.websms
.connector
.common
.Utils
;
76 @SuppressWarnings("deprecation")
77 public class WebSMS
extends Activity
implements OnClickListener
,
78 OnDateSetListener
, OnTimeSetListener
{
79 /** Tag for output. */
80 private static final String TAG
= "WebSMS";
82 /** Static reference to running Activity. */
83 private static WebSMS me
;
84 /** Preference's name: last version run. */
85 private static final String PREFS_LAST_RUN
= "lastrun";
86 /** Preference's name: user's phonenumber. */
87 static final String PREFS_SENDER
= "sender";
88 /** Preference's name: default prefix. */
89 static final String PREFS_DEFPREFIX
= "defprefix";
90 /** Preference's name: touch keyboard. */
91 private static final String PREFS_SOFTKEYS
= "softkeyboard";
92 /** Preference's name: update balace on start. */
93 private static final String PREFS_AUTOUPDATE
= "autoupdate";
94 /** Preference's name: exit after sending. */
95 private static final String PREFS_AUTOEXIT
= "autoexit";
96 /** Preference's name: show mobile numbers only. */
97 private static final String PREFS_MOBILES_ONLY
= "mobiles_only";
98 /** Preference's name: vibrate on failed sending. */
99 static final String PREFS_FAIL_VIBRATE
= "fail_vibrate";
100 /** Preference's name: sound on failed sending. */
101 static final String PREFS_FAIL_SOUND
= "fail_sound";
102 /** Preferemce's name: enable change connector button. */
103 private static final String PREFS_CHANGE_CONNECTOR_BUTTON
= // .
104 "change_connector_button";
105 /** Preferemce's name: hide cancel button. */
106 private static final String PREFS_HIDE_CANCEL_BUTTON
= "hide_cancel_button";
108 /** Preference's name: to. */
109 private static final String PREFS_TO
= "to";
110 /** Preference's name: text. */
111 private static final String PREFS_TEXT
= "text";
112 /** Preference's name: connector name. */
113 private static final String PREFS_CONNECTOR_ID
= "connector_name";
115 /** Sleep before autoexit. */
116 private static final int SLEEP_BEFORE_EXIT
= 75;
118 /** Preferences: hide ads. */
119 static boolean prefsNoAds
= false;
121 static String imeiHash
= null;
122 /** Preferences: connector specs. */
123 static ConnectorSpec prefsConnectorSpecs
= null;
124 /** Save prefsConnectorSpecs.getID() here. */
125 static String prefsConnectorID
= null;
126 /** Preferences: show mobile numbers only. */
127 static boolean prefsMobilesOnly
;
129 /** List of available {@link ConnectorSpec}s. */
130 private final static ArrayList
<ConnectorSpec
> CONNECTORS
= new ArrayList
<ConnectorSpec
>();
132 /** Array of md5(prefsSender) for which no ads should be displayed. */
133 private static final String
[] NO_AD_HASHS
= {
134 "2986b6d93053a53ff13008b3015a77ff", // flx
135 "57a3c7c19329fd84c2252a9b2866dd93", // mirweb
136 "10b7a2712beee096acbc67416d7d71a1", // mo
137 "f6b3b72300e918436b4c4c9fdf909e8c", // joerg s.
138 "4c18f7549b643045f0ff69f61e8f7e72", // frank j.
139 "7684154558d19383552388d9bc92d446", // henning k.
140 "64c7414288e9a9b57a33e034f384ed30", // dominik l.
141 "c479a2e701291c751f0f91426bcaabf3", // bernhard g.
142 "ae7dfedf549f98a349ad8c2068473c6b", // dominik k.-v.
143 "18bc29cd511613552861da6ef51766ce", // niels b.
144 "2985011f56d0049b0f4f0caed3581123", // sven l.
145 "64724033da297a915a89023b11ac2e47", // wilfried m.
146 "cfd8d2efb3eac39705bd62c4dfe5e72d", // achim e.
147 "ca56e7518fdbda832409ef07edd4c273", // michael s.
148 "bed2f068ca8493da4179807d1afdbd83", // axel q.
149 "4c35400c4fa3ffe2aefcf1f9131eb855", // gerhard s.
150 "02158d2a80b1ef9c4d684a4ca808b93d", // camilo s.
151 "1177c6e67f98cdfed6c84d99e85d30de", // daniel p.
152 "3f082dd7e21d5c64f34a69942c474ce7", // andre j.
153 "5383540b2f8c298532f874126b021e73", // marco a.
154 "858ddfb8635d1539884086dca2726468", // lado
155 "6e8bbb35091219a80e278ae61f31cce9", // mario s.
158 /** Public Dialog ref. */
159 static Dialog dialog
= null;
160 /** Dialog String. */
161 static String dialogString
= null;
163 /** true if preferences got opened. */
164 static boolean doPreferences
= false;
166 /** Dialog: about. */
167 private static final int DIALOG_ABOUT
= 0;
168 /** Dialog: updates. */
169 private static final int DIALOG_UPDATE
= 2;
170 /** Dialog: captcha. */
171 private static final int DIALOG_CAPTCHA
= 3;
172 /** Dialog: post donate. */
173 private static final int DIALOG_POSTDONATE
= 4;
174 /** Dialog: custom sender. */
175 private static final int DIALOG_CUSTOMSENDER
= 5;
176 /** Dialog: send later: date. */
177 private static final int DIALOG_SENDLATER_DATE
= 6;
178 /** Dialog: send later: time. */
179 private static final int DIALOG_SENDLATER_TIME
= 7;
180 /** Dialog: pre donate. */
181 private static final int DIALOG_PREDONATE
= 8;
183 /** Message for logging. **/
184 static final int MESSAGE_LOG
= 0;
185 /** Message for update free sms count. **/
186 static final int MESSAGE_FREECOUNT
= 1;
187 /** Message to open settings. */
188 static final int MESSAGE_SETTINGS
= 4;
189 /** Message to reset data. */
190 static final int MESSAGE_RESET
= 5;
191 /** Message show cpatcha. */
192 static final int MESSAGE_ANTICAPTCHA
= 6;
194 /** Intent's extra for errormessages. */
195 static final String EXTRA_ERRORMESSAGE
= "de.ub0r.android.intent.extra.ERRORMESSAGE";
197 /** Persistent Message store. */
198 private static String lastMsg
= null;
199 /** Persistent Recipient store. */
200 private static String lastTo
= null;
202 /** Backup for params. */
203 private static ConnectorCommand lastParams
= null;
204 /** User wants to send the message later. */
205 private static boolean wantSendLater
= false;
207 /** Helper for API 5. */
208 static HelperAPI5Contacts helperAPI5c
= null;
211 private TextView textLabel
;
214 private boolean showExtras
= false;
216 /** MessageHandler. */
217 private Handler messageHandler
= new Handler() {
222 public final void handleMessage(final Message msg
) {
224 case MESSAGE_LOG
: // msg is String or Resource StringID
225 if (msg
.obj
instanceof String
) {
226 WebSMS
.this.log((String
) msg
.obj
);
227 } else if (msg
.obj
instanceof Integer
) {
228 WebSMS
.this.log(WebSMS
.this.getString(((Integer
) msg
.obj
)
231 WebSMS
.this.log(msg
.obj
.toString());
234 case MESSAGE_FREECOUNT
:
235 WebSMS
.this.updateBalance();
237 case MESSAGE_SETTINGS
:
238 WebSMS
.this.startActivity(new Intent(WebSMS
.this,
243 case MESSAGE_ANTICAPTCHA
:
244 WebSMS
.this.showDialog(DIALOG_CAPTCHA
);
252 /** TextWatcher updating char count on writing. */
253 private TextWatcher textWatcher
= new TextWatcher() {
257 public void afterTextChanged(final Editable s
) {
258 int[] l
= SmsMessage
.calculateLength(s
, false);
259 WebSMS
.this.textLabel
.setText(l
[0] + "/" + l
[2]);
263 public void beforeTextChanged(final CharSequence s
, final int start
,
264 final int count
, final int after
) {
268 public void onTextChanged(final CharSequence s
, final int start
,
269 final int before
, final int count
) {
277 public final void onCreate(final Bundle savedInstanceState
) {
278 super.onCreate(savedInstanceState
);
279 this.requestWindowFeature(Window
.FEATURE_INDETERMINATE_PROGRESS
);
284 WebSMS
.helperAPI5c
= new HelperAPI5Contacts();
285 if (!helperAPI5c
.isAvailable()) {
286 WebSMS
.helperAPI5c
= null;
288 } catch (VerifyError e
) {
289 WebSMS
.helperAPI5c
= null;
290 Log
.d(TAG
, "no api5 running", e
);
292 // Restore preferences
293 final SharedPreferences p
= PreferenceManager
294 .getDefaultSharedPreferences(this);
296 if (p
.getBoolean(PREFS_SOFTKEYS
, false)) {
297 this.setContentView(R
.layout
.main_touch
);
299 this.setContentView(R
.layout
.main
);
302 this.findViewById(R
.id
.to
).requestFocus();
304 // display changelog?
305 String v0
= p
.getString(PREFS_LAST_RUN
, "");
306 String v1
= this.getResources().getString(R
.string
.app_version
);
307 if (!v0
.equals(v1
)) {
308 SharedPreferences
.Editor editor
= p
.edit();
309 editor
.putString(PREFS_LAST_RUN
, v1
);
311 this.showDialog(DIALOG_UPDATE
);
316 lastTo
= p
.getString(PREFS_TO
, "");
317 lastMsg
= p
.getString(PREFS_TEXT
, "");
320 ((Button
) this.findViewById(R
.id
.send_
)).setOnClickListener(this);
321 ((Button
) this.findViewById(R
.id
.cancel
)).setOnClickListener(this);
322 ((Button
) this.findViewById(R
.id
.change_connector
))
323 .setOnClickListener(this);
324 ((Button
) this.findViewById(R
.id
.extras
)).setOnClickListener(this);
326 this.textLabel
= (TextView
) this.findViewById(R
.id
.text_
);
327 ((EditText
) this.findViewById(R
.id
.text
))
328 .addTextChangedListener(this.textWatcher
);
330 ((TextView
) this.findViewById(R
.id
.freecount
)).setOnClickListener(this);
332 final MultiAutoCompleteTextView to
= (MultiAutoCompleteTextView
) this
333 .findViewById(R
.id
.to
);
334 to
.setAdapter(new MobilePhoneAdapter(this));
335 to
.setTokenizer(new MultiAutoCompleteTextView
.CommaTokenizer());
337 Intent intent
= this.getIntent();
338 final String action
= intent
.getAction();
339 if (action
!= null) {
340 // launched by clicking a sms: link, target number is in URI.
341 final Uri uri
= intent
.getData();
343 final String scheme
= uri
.getScheme();
344 if (scheme
.equals("sms") || scheme
.equals("smsto")) {
345 String s
= uri
.getSchemeSpecificPart();
348 if (s
.endsWith(",")) {
349 s
= s
.substring(0, s
.length() - 1).trim();
351 // recipient = WebSMS.cleanRecipient(recipient);
352 if (s
.indexOf('<') < 0) {
353 // try to fetch recipient's name from phonebook
355 if (helperAPI5c
!= null) {
357 n
= helperAPI5c
.getNameForNumber(this, s
);
358 } catch (NoClassDefFoundError e
) {
362 if (helperAPI5c
== null) {
367 PhonesColumns
.NUMBER
,
368 PeopleColumns
.DISPLAY_NAME
},
369 PhonesColumns
.NUMBER
+ " = '"
370 + s
+ "'", null, null);
371 if (c
.moveToFirst()) {
374 .getColumnIndex(PeopleColumns
.DISPLAY_NAME
));
378 s
= n
+ " <" + s
+ ">, ";
381 ((EditText
) this.findViewById(R
.id
.to
)).setText(s
);
384 final Bundle extras
= intent
.getExtras();
385 if (extras
!= null) {
386 s
= extras
.getCharSequence(Intent
.EXTRA_TEXT
)
389 ((EditText
) this.findViewById(R
.id
.text
))
393 s
= extras
.getString(EXTRA_ERRORMESSAGE
);
395 Toast
.makeText(this, s
, Toast
.LENGTH_LONG
).show();
399 // do not display any ads for donators
401 ((AdView
) WebSMS
.this.findViewById(R
.id
.ad
))
402 .setVisibility(View
.VISIBLE
);
408 // check default prefix
409 if (!p
.getString(PREFS_DEFPREFIX
, "").startsWith("+")) {
410 WebSMS
.this.log(R
.string
.log_error_defprefix
);
412 if (p
.getBoolean(PREFS_AUTOUPDATE
, false)) {
413 this.updateFreecount(false);
421 protected final void onResume() {
423 // set free sms count
424 this.updateBalance();
426 // restart dialog if needed
427 if (dialogString
!= null) {
428 if (dialog
!= null) {
431 } catch (Exception e
) {
435 dialog
= ProgressDialog
.show(this, null, dialogString
, true);
438 // if coming from prefs..
441 doPreferences
= false;
442 final SharedPreferences p
= PreferenceManager
443 .getDefaultSharedPreferences(this);
444 final String defPrefix
= p
.getString(PREFS_DEFPREFIX
, "+49");
445 final String defSender
= p
.getString(PREFS_SENDER
, "");
446 final Intent intent
= ConnectorCommand
.bootstrap(defPrefix
,
447 defSender
).setToIntent(null);
448 Log
.d(TAG
, "send broadcast: " + intent
.getAction());
449 this.sendBroadcast(intent
);
454 // reload text/recipient from local store
455 final EditText et0
= (EditText
) this.findViewById(R
.id
.text
);
456 if (lastMsg
!= null) {
457 et0
.setText(lastMsg
);
461 final EditText et1
= (EditText
) this.findViewById(R
.id
.to
);
462 if (lastTo
!= null) {
468 if (lastTo
!= null && lastTo
.length() > 0) {
474 // query for connectors
475 final Intent i
= new Intent(Connector
.ACTION_CONNECTOR_UPDATE
);
476 Log
.d(TAG
, "send broadcast: " + i
.getAction());
477 this.sendBroadcast(i
);
483 private void updateBalance() {
484 final StringBuilder buf
= new StringBuilder();
485 // FIXME: this method is run way to often!
486 final ConnectorSpec
[] css
= getConnectors(
487 ConnectorSpec
.CAPABILITIES_UPDATE
, // .
488 ConnectorSpec
.STATUS_ENABLED
);
489 for (ConnectorSpec cs
: css
) {
490 final String b
= cs
.getBalance();
491 if (b
== null || b
.length() == 0) {
494 if (buf
.length() > 0) {
497 buf
.append(cs
.getName());
502 TextView tw
= (TextView
) this.findViewById(R
.id
.freecount
);
503 tw
.setText(this.getString(R
.string
.free_
) + " " + buf
.toString() + " "
504 + this.getString(R
.string
.click_for_update
));
511 public final void onPause() {
513 // store input data to persitent stores
514 lastMsg
= ((EditText
) this.findViewById(R
.id
.text
)).getText()
516 lastTo
= ((EditText
) this.findViewById(R
.id
.to
)).getText().toString();
518 // store input data to preferences
519 SharedPreferences
.Editor editor
= PreferenceManager
520 .getDefaultSharedPreferences(this).edit();
522 editor
.putString(PREFS_TO
, lastTo
);
523 editor
.putString(PREFS_TEXT
, lastMsg
);
527 this.savePreferences();
531 * Read static vars holding preferences.
533 private void reloadPrefs() {
534 final SharedPreferences p
= PreferenceManager
535 .getDefaultSharedPreferences(this);
536 boolean b
= p
.getBoolean(PREFS_CHANGE_CONNECTOR_BUTTON
, false);
537 View v
= this.findViewById(R
.id
.change_connector
);
539 v
.setVisibility(View
.VISIBLE
);
541 v
.setVisibility(View
.GONE
);
544 b
= !p
.getBoolean(PREFS_HIDE_CANCEL_BUTTON
, false);
545 v
= this.findViewById(R
.id
.cancel
);
547 v
.setVisibility(View
.VISIBLE
);
549 v
.setVisibility(View
.GONE
);
552 prefsConnectorID
= p
.getString(PREFS_CONNECTOR_ID
, "");
553 prefsConnectorSpecs
= getConnectorByID(prefsConnectorID
);
555 prefsMobilesOnly
= p
.getBoolean(PREFS_MOBILES_ONLY
, false);
558 String hash
= Utils
.md5(p
.getString(PREFS_SENDER
, ""));
559 for (String h
: NO_AD_HASHS
) {
560 if (hash
.equals(h
)) {
565 if (!prefsNoAds
&& this.getImeiHash() != null) {
566 for (String h
: NO_AD_HASHS
) {
567 if (imeiHash
.equals(h
)) {
578 * Show/hide, enable/disable send buttons.
580 private void setButtons() {
581 final ConnectorSpec
[] enabled
= getConnectors(
582 ConnectorSpec
.CAPABILITIES_SEND
, ConnectorSpec
.STATUS_ENABLED
);
583 final int c
= enabled
.length
;
585 Button btn
= (Button
) this.findViewById(R
.id
.send_
);
587 btn
.setEnabled(c
> 0);
588 btn
.setVisibility(View
.VISIBLE
);
590 prefsConnectorSpecs
= enabled
[0];
593 if (prefsConnectorSpecs
!= null) {
594 final short features
= ConnectorSpec
.SubConnectorSpec
.FEATURE_NONE
;
595 // FIXME: prefsConnectorSpecs.getFeatures();
596 final boolean sFlashsms
= false;
597 // (features & ConnectorSpecs.FEATURE_FLASHSMS) ==
598 // ConnectorSpecs.FEATURE_FLASHSMS;
599 final boolean sCustomsender
= false;
600 // (features & ConnectorSpecs.FEATURE_CUSTOMSENDER) ==
601 // ConnectorSpecs.FEATURE_CUSTOMSENDER;
602 final boolean sSendLater
= false;
603 // (features & ConnectorSpecs.FEATURE_SENDLATER) ==
604 // ConnectorSpecs.FEATURE_SENDLATER;
605 if (sFlashsms
|| sCustomsender
|| sSendLater
) {
606 this.findViewById(R
.id
.extras
).setVisibility(View
.VISIBLE
);
608 this.findViewById(R
.id
.extras
).setVisibility(View
.GONE
);
610 if (this.showExtras
&& sFlashsms
) {
611 this.findViewById(R
.id
.flashsms
).setVisibility(View
.VISIBLE
);
613 this.findViewById(R
.id
.flashsms
).setVisibility(View
.GONE
);
615 if (this.showExtras
&& sCustomsender
) {
616 this.findViewById(R
.id
.custom_sender
).setVisibility(
619 this.findViewById(R
.id
.custom_sender
).setVisibility(View
.GONE
);
621 if (this.showExtras
&& sSendLater
) {
622 this.findViewById(R
.id
.send_later
).setVisibility(View
.VISIBLE
);
624 this.findViewById(R
.id
.send_later
).setVisibility(View
.GONE
);
627 this.setTitle(this.getString(R
.string
.app_name
) + " - "
628 + prefsConnectorSpecs
.getName());
633 * Resets persistent store.
635 private void reset() {
636 ((EditText
) this.findViewById(R
.id
.text
)).setText("");
637 ((EditText
) this.findViewById(R
.id
.to
)).setText("");
640 // save user preferences
641 SharedPreferences
.Editor editor
= PreferenceManager
642 .getDefaultSharedPreferences(this).edit();
643 editor
.putString(PREFS_TO
, "");
644 editor
.putString(PREFS_TEXT
, "");
650 final void savePreferences() {
651 if (prefsConnectorSpecs
!= null) {
652 PreferenceManager
.getDefaultSharedPreferences(this).edit()
653 .putString(PREFS_CONNECTOR_ID
, prefsConnectorSpecs
.getID())
659 * Run Connector.update().
662 * force update, if false only blank balances will get updated
664 private void updateFreecount(final boolean forceUpdate
) {
665 final SharedPreferences p
= PreferenceManager
666 .getDefaultSharedPreferences(this);
667 final String defPrefix
= p
.getString(PREFS_DEFPREFIX
, "+49");
668 final String defSender
= p
.getString(PREFS_SENDER
, "");
669 final Intent intent
= ConnectorCommand
.update(defPrefix
, defSender
)
671 Log
.d(TAG
, "send broadcast: " + intent
.getAction());
672 this.sendBroadcast(intent
);
678 public final void onClick(final View v
) {
681 this.updateFreecount(true);
684 this.send(prefsConnectorSpecs
);
689 // FIXME: case R.id.captcha_btn:
690 // ConnectorO2.captchaSolve = ((EditText) v.getRootView()
691 // .findViewById(R.id.captcha_edt)).getText().toString();
692 // synchronized (ConnectorO2.CAPTCHA_SYNC) {
693 // ConnectorO2.CAPTCHA_SYNC.notify();
695 // this.dismissDialog(DIALOG_CAPTCHA);
697 case R
.id
.change_connector
:
698 this.changeConnectorMenu();
701 this.showExtras
= !this.showExtras
;
713 public final boolean onCreateOptionsMenu(final Menu menu
) {
714 MenuInflater inflater
= this.getMenuInflater();
715 inflater
.inflate(R
.menu
.menu
, menu
);
720 * Display "change connector" menu.
722 private void changeConnectorMenu() {
723 AlertDialog
.Builder builder
= new AlertDialog
.Builder(this);
724 builder
.setTitle(R
.string
.change_connector_
);
725 final ArrayList
<String
> items
= new ArrayList
<String
>();
726 final ConnectorSpec
[] css
= getConnectors(
727 ConnectorSpec
.CAPABILITIES_SEND
, ConnectorSpec
.STATUS_ENABLED
);
728 for (ConnectorSpec cs
: css
) {
729 items
.add(cs
.getName());
731 // TODO: add subconnectors
733 builder
.setItems(items
.toArray(new String
[0]),
734 new DialogInterface
.OnClickListener() {
735 public void onClick(final DialogInterface d
, // .
737 prefsConnectorSpecs
= getConnectorByName(items
739 WebSMS
.this.setButtons();
740 // save user preferences
741 PreferenceManager
.getDefaultSharedPreferences(
742 WebSMS
.this).edit().putString(
744 prefsConnectorSpecs
.getName()).commit();
747 builder
.create().show();
754 public final boolean onOptionsItemSelected(final MenuItem item
) {
755 switch (item
.getItemId()) {
756 case R
.id
.item_about
: // start about dialog
757 this.showDialog(DIALOG_ABOUT
);
759 case R
.id
.item_settings
: // start settings activity
760 this.startActivity(new Intent(this, Preferences
.class));
762 case R
.id
.item_donate
:
763 this.showDialog(DIALOG_PREDONATE
);
767 this.startActivity(new Intent(Intent
.ACTION_VIEW
, Uri
768 .parse("market://search?q=pub:\"Felix Bechstein\"")));
769 } catch (ActivityNotFoundException e
) {
770 Log
.e(TAG
, "no market", e
);
773 case R
.id
.item_connector
:
774 this.changeConnectorMenu();
785 protected final Dialog
onCreateDialog(final int id
) {
787 AlertDialog
.Builder builder
;
789 case DIALOG_PREDONATE
:
790 builder
= new AlertDialog
.Builder(this);
791 builder
.setTitle(R
.string
.donate_
);
792 builder
.setMessage(R
.string
.predonate
);
793 builder
.setPositiveButton(R
.string
.donate_
,
794 new DialogInterface
.OnClickListener() {
795 public void onClick(final DialogInterface dialog
,
799 .startActivity(new Intent(
803 .getString(R
.string
.donate_url
))));
804 } catch (ActivityNotFoundException e
) {
805 Log
.e(TAG
, "no browser", e
);
807 WebSMS
.this.showDialog(DIALOG_POSTDONATE
);
811 builder
.setNegativeButton(android
.R
.string
.cancel
, null);
812 return builder
.create();
813 case DIALOG_POSTDONATE
:
814 builder
= new AlertDialog
.Builder(this);
815 builder
.setTitle(R
.string
.remove_ads_
);
816 builder
.setMessage(R
.string
.postdonate
);
817 builder
.setPositiveButton(R
.string
.send_
,
818 new DialogInterface
.OnClickListener() {
819 public void onClick(final DialogInterface dialog
,
821 final Intent in
= new Intent(Intent
.ACTION_SEND
);
827 .getString(R
.string
.donate_mail
),
828 "" }); // FIXME: "" is a k9
830 in
.putExtra(Intent
.EXTRA_TEXT
, WebSMS
.this
834 Intent
.EXTRA_SUBJECT
,
836 .getString(R
.string
.app_name
)
839 .getString(R
.string
.donate_subject
));
840 in
.setType("text/plain");
841 WebSMS
.this.startActivity(in
);
844 builder
.setNegativeButton(android
.R
.string
.cancel
, null);
845 return builder
.create();
847 d
= new Dialog(this);
848 d
.setContentView(R
.layout
.about
);
849 d
.setTitle(this.getString(R
.string
.about_
) + " v"
850 + this.getString(R
.string
.app_version
));
851 StringBuffer authors
= new StringBuffer();
852 final ConnectorSpec
[] css
= getConnectors(
853 ConnectorSpec
.CAPABILITIES_NONE
,
854 ConnectorSpec
.STATUS_INACTIVE
);
855 for (ConnectorSpec cs
: css
) {
856 final String a
= cs
.getAuthor();
857 if (a
!= null && a
.length() > 0) {
858 authors
.append(cs
.getName());
859 authors
.append(":\t");
861 authors
.append("\n");
864 ((TextView
) d
.findViewById(R
.id
.author_connectors
)).setText(authors
868 builder
= new AlertDialog
.Builder(this);
869 builder
.setTitle(R
.string
.changelog_
);
870 final String
[] changes
= this.getResources().getStringArray(
872 final StringBuilder buf
= new StringBuilder(changes
[0]);
873 for (int i
= 1; i
< changes
.length
; i
++) {
875 buf
.append(changes
[i
]);
877 builder
.setIcon(android
.R
.drawable
.ic_menu_info_details
);
878 builder
.setMessage(buf
.toString());
879 builder
.setCancelable(true);
880 builder
.setPositiveButton(android
.R
.string
.ok
, null);
881 return builder
.create();
883 d
= new Dialog(this);
884 d
.setTitle(R
.string
.captcha_
);
885 d
.setContentView(R
.layout
.captcha
);
886 d
.setCancelable(false);
887 ((Button
) d
.findViewById(R
.id
.captcha_btn
))
888 .setOnClickListener(this);
890 case DIALOG_CUSTOMSENDER
:
891 builder
= new AlertDialog
.Builder(this);
892 builder
.setTitle(R
.string
.custom_sender
);
893 builder
.setCancelable(true);
894 final EditText et
= new EditText(this);
896 builder
.setPositiveButton(android
.R
.string
.ok
,
897 new DialogInterface
.OnClickListener() {
898 public void onClick(final DialogInterface dialog
,
900 WebSMS
.lastParams
.setCustomSender(et
.getText()
902 if (WebSMS
.wantSendLater
) {
904 .showDialog(WebSMS
.DIALOG_SENDLATER_DATE
);
906 WebSMS
.this.send(WebSMS
.prefsConnectorSpecs
,
911 builder
.setNegativeButton(android
.R
.string
.cancel
, null);
912 return builder
.create();
913 case DIALOG_SENDLATER_DATE
:
914 Calendar c
= Calendar
.getInstance();
915 return new DatePickerDialog(this, this, c
.get(Calendar
.YEAR
), c
916 .get(Calendar
.MONTH
), c
.get(Calendar
.DAY_OF_MONTH
));
917 case DIALOG_SENDLATER_TIME
:
918 c
= Calendar
.getInstance();
919 return new MyTimePickerDialog(this, this, c
920 .get(Calendar
.HOUR_OF_DAY
), c
.get(Calendar
.MINUTE
), true);
930 protected final void onPrepareDialog(final int id
, final Dialog dlg
) {
932 // FIXME: case DIALOG_CAPTCHA:
933 // if (ConnectorO2.captcha != null) {
934 // ((ImageView) dlg.findViewById(R.id.captcha_img))
935 // .setImageDrawable(ConnectorO2.captcha);
936 // ConnectorO2.captcha = null;
950 public final void log(final int text
) {
951 this.log(this.getString(text
));
960 public final void log(final String text
) {
962 Toast
.makeText(this.getApplicationContext(), text
,
963 Toast
.LENGTH_LONG
).show();
964 } catch (RuntimeException e
) {
973 * which connector should be used.
975 * {@link ConnectorCommand} to push to connector
977 private void send(final ConnectorSpec connector
,
978 final ConnectorCommand command
) {
980 final Intent intent
= command
.setToIntent(null);
981 prefsConnectorSpecs
.setToIntent(intent
);
982 connector
.addStatus(ConnectorSpec
.STATUS_SENDING
);
983 Log
.d(TAG
, "send broadcast: " + intent
.getAction());
984 this.sendBroadcast(intent
);
985 } catch (Exception e
) {
989 if (PreferenceManager
.getDefaultSharedPreferences(this).getBoolean(
990 PREFS_AUTOEXIT
, false)) {
992 Thread
.sleep(SLEEP_BEFORE_EXIT
);
993 } catch (InterruptedException e
) {
1005 * which connector should be used.
1007 private void send(final ConnectorSpec connector
) {
1008 // fetch text/recipient
1009 final String to
= ((EditText
) this.findViewById(R
.id
.to
)).getText()
1011 final String text
= ((EditText
) this.findViewById(R
.id
.text
)).getText()
1013 if (to
.length() == 0 || text
.length() == 0) {
1018 // do not display any ads for donators
1020 ((AdView
) WebSMS
.this.findViewById(R
.id
.ad
))
1021 .setVisibility(View
.VISIBLE
);
1024 CheckBox v
= (CheckBox
) this.findViewById(R
.id
.flashsms
);
1025 final boolean flashSMS
= (v
.getVisibility() == View
.VISIBLE
)
1026 && v
.isEnabled() && v
.isChecked();
1027 v
= (CheckBox
) this.findViewById(R
.id
.send_later
);
1028 if ((v
.getVisibility() == View
.VISIBLE
) && v
.isEnabled()
1030 wantSendLater
= true;
1032 final SharedPreferences p
= PreferenceManager
1033 .getDefaultSharedPreferences(this);
1034 final String defPrefix
= p
.getString(PREFS_DEFPREFIX
, "+49");
1035 final String defSender
= p
.getString(PREFS_SENDER
, "");
1036 // FIXME: subconnectorid
1037 final ConnectorCommand command
= ConnectorCommand
.send(null, defPrefix
,
1038 defSender
, to
.split(","), text
, flashSMS
);
1040 v
= (CheckBox
) this.findViewById(R
.id
.custom_sender
);
1041 if ((v
.getVisibility() == View
.VISIBLE
) && v
.isEnabled()
1043 lastParams
= command
;
1044 this.showDialog(DIALOG_CUSTOMSENDER
);
1046 if (wantSendLater
) {
1047 lastParams
= command
;
1048 this.showDialog(DIALOG_SENDLATER_DATE
);
1050 this.send(connector
, command
);
1062 * @param monthOfYear
1067 public final void onDateSet(final DatePicker view
, final int year
,
1068 final int monthOfYear
, final int dayOfMonth
) {
1069 final Calendar c
= Calendar
.getInstance();
1070 if (lastParams
!= null) {
1071 final long l
= lastParams
.getSendLater();
1073 c
.setTimeInMillis(l
);
1076 c
.set(Calendar
.YEAR
, year
);
1077 c
.set(Calendar
.MONTH
, monthOfYear
);
1078 c
.set(Calendar
.DAY_OF_MONTH
, dayOfMonth
);
1079 lastParams
.setSendLater(c
.getTimeInMillis());
1081 this.showDialog(DIALOG_SENDLATER_TIME
);
1094 public final void onTimeSet(final TimePicker view
, final int hour
,
1095 final int minutes
) {
1096 if (prefsConnectorSpecs
.getName().equals("WebSMS.o2") // FIXME
1097 && minutes
% 15 != 0) {
1098 Toast
.makeText(this, R
.string
.log_error_o2_sendlater
,
1099 Toast
.LENGTH_LONG
).show();
1103 final Calendar c
= Calendar
.getInstance();
1104 if (lastParams
!= null) {
1105 c
.setTimeInMillis(lastParams
.getSendLater());
1107 c
.set(Calendar
.HOUR_OF_DAY
, hour
);
1108 c
.set(Calendar
.MINUTE
, minutes
);
1109 lastParams
.setSendLater(c
.getTimeInMillis());
1111 this.send(WebSMS
.prefsConnectorSpecs
, WebSMS
.lastParams
);
1115 * Send WebSMS a Message.
1117 * @param messageType
1122 public static final void pushMessage(final int messageType
,
1123 final Object data
) {
1124 if (WebSMS
.me
== null) {
1127 Message
.obtain(WebSMS
.me
.messageHandler
, messageType
, data
)
1132 * Get MD5 hash of the IMEI (device id).
1134 * @return MD5 hash of IMEI
1136 private String
getImeiHash() {
1137 if (imeiHash
== null) {
1139 TelephonyManager mTelephonyMgr
= (TelephonyManager
) this
1140 .getSystemService(TELEPHONY_SERVICE
);
1141 final String did
= mTelephonyMgr
.getDeviceId();
1143 imeiHash
= Utils
.md5(did
);
1150 * Add or update a {@link ConnectorSpec}.
1155 public static final void addConnector(final ConnectorSpec connector
) {
1156 synchronized (CONNECTORS
) {
1157 if (connector
== null || connector
.getID() == null
1158 || connector
.getName() == null) {
1161 final ConnectorSpec c
= getConnectorByID(connector
.getID());
1163 c
.update(connector
);
1165 final int l
= CONNECTORS
.size();
1166 final String name
= connector
.getName();
1167 Log
.d(TAG
, "add connector with id: " + connector
.getID());
1168 Log
.d(TAG
, "add connector with name: " + name
);
1169 boolean added
= false;
1171 for (int i
= 0; i
< l
; i
++) {
1172 final ConnectorSpec cs
= CONNECTORS
.get(i
);
1173 if (name
.compareToIgnoreCase(cs
.getName()) < 0) {
1174 CONNECTORS
.add(i
, connector
);
1178 } catch (NullPointerException e
) {
1179 Log
.e(TAG
, "error while sorting", e
);
1182 CONNECTORS
.add(connector
);
1185 if (prefsConnectorSpecs
== null
1186 && prefsConnectorID
.equals(connector
.getID())) {
1187 prefsConnectorSpecs
= connector
;
1191 if (connector
.getBalance() != null) {
1198 * Get {@link ConnectorSpec} by ID.
1202 * @return {@link ConnectorSpec}
1204 public static final ConnectorSpec
getConnectorByID(final String id
) {
1205 synchronized (CONNECTORS
) {
1209 final int l
= CONNECTORS
.size();
1210 for (int i
= 0; i
< l
; i
++) {
1211 final ConnectorSpec c
= CONNECTORS
.get(i
);
1212 if (id
.equals(c
.getID())) {
1221 * Get {@link ConnectorSpec} by name.
1225 * @return {@link ConnectorSpec}
1227 public static final ConnectorSpec
getConnectorByName(final String name
) {
1228 synchronized (CONNECTORS
) {
1232 final int l
= CONNECTORS
.size();
1233 for (int i
= 0; i
< l
; i
++) {
1234 final ConnectorSpec c
= CONNECTORS
.get(i
);
1235 if (name
.equals(c
.getName())) {
1244 * Get {@link ConnectorSpec}s by capabilities and/or status.
1246 * @param capabilities
1247 * capabilities needed
1250 * @return {@link ConnectorSpec}s
1252 public static final ConnectorSpec
[] getConnectors(final short capabilities
,
1253 final short status
) {
1254 synchronized (CONNECTORS
) {
1255 final ArrayList
<ConnectorSpec
> ret
= new ArrayList
<ConnectorSpec
>(
1257 final int l
= CONNECTORS
.size();
1258 for (int i
= 0; i
< l
; i
++) {
1259 final ConnectorSpec c
= CONNECTORS
.get(i
);
1260 if (c
.hasCapabilities(capabilities
) && c
.hasStatus(status
)) {
1264 return ret
.toArray(new ConnectorSpec
[0]);