move connectors
[andGMXsms.git] / src / de / ub0r / android / andGMXsms / WebSMS.java
blob99b6be6b81946a6cc5421f7e39cadedd8fb72baa
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/>.
19 package de.ub0r.android.andGMXsms;
21 import java.security.MessageDigest;
22 import java.security.NoSuchAlgorithmException;
23 import java.util.ArrayList;
24 import java.util.Calendar;
26 import android.app.Activity;
27 import android.app.AlertDialog;
28 import android.app.DatePickerDialog;
29 import android.app.Dialog;
30 import android.app.ProgressDialog;
31 import android.app.DatePickerDialog.OnDateSetListener;
32 import android.app.TimePickerDialog.OnTimeSetListener;
33 import android.content.ActivityNotFoundException;
34 import android.content.DialogInterface;
35 import android.content.Intent;
36 import android.content.SharedPreferences;
37 import android.database.Cursor;
38 import android.net.Uri;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.Message;
42 import android.preference.PreferenceManager;
43 import android.provider.Contacts.PeopleColumns;
44 import android.provider.Contacts.Phones;
45 import android.provider.Contacts.PhonesColumns;
46 import android.telephony.TelephonyManager;
47 import android.telephony.gsm.SmsMessage;
48 import android.text.Editable;
49 import android.text.TextWatcher;
50 import android.util.Log;
51 import android.view.Menu;
52 import android.view.MenuInflater;
53 import android.view.MenuItem;
54 import android.view.View;
55 import android.view.Window;
56 import android.view.View.OnClickListener;
57 import android.widget.Button;
58 import android.widget.CheckBox;
59 import android.widget.DatePicker;
60 import android.widget.EditText;
61 import android.widget.MultiAutoCompleteTextView;
62 import android.widget.TextView;
63 import android.widget.TimePicker;
64 import android.widget.Toast;
66 import com.admob.android.ads.AdView;
68 import de.ub0r.android.websms.connector.common.CommandReceiver;
69 import de.ub0r.android.websms.connector.common.ConnectorCommand;
70 import de.ub0r.android.websms.connector.common.ConnectorSpec;
72 /**
73 * Main Activity.
75 * @author flx
77 @SuppressWarnings("deprecation")
78 public class WebSMS extends Activity implements OnClickListener,
79 OnDateSetListener, OnTimeSetListener {
80 /** Tag for output. */
81 private static final String TAG = "WebSMS";
83 /** Static reference to running Activity. */
84 private static WebSMS me;
85 /** Preference's name: last version run. */
86 private static final String PREFS_LAST_RUN = "lastrun";
87 /** Preference's name: user's phonenumber. */
88 static final String PREFS_SENDER = "sender";
89 /** Preference's name: default prefix. */
90 static final String PREFS_DEFPREFIX = "defprefix";
91 /** Preference's name: touch keyboard. */
92 private static final String PREFS_SOFTKEYS = "softkeyboard";
93 /** Preference's name: update balace on start. */
94 private static final String PREFS_AUTOUPDATE = "autoupdate";
95 /** Preference's name: exit after sending. */
96 private static final String PREFS_AUTOEXIT = "autoexit";
97 /** Preference's name: show mobile numbers only. */
98 private static final String PREFS_MOBILES_ONLY = "mobiles_only";
99 /** Preference's name: vibrate on failed sending. */
100 static final String PREFS_FAIL_VIBRATE = "fail_vibrate";
101 /** Preference's name: sound on failed sending. */
102 static final String PREFS_FAIL_SOUND = "fail_sound";
103 /** Preferemce's name: enable change connector button. */
104 private static final String PREFS_CHANGE_CONNECTOR_BUTTON = // .
105 "change_connector_button";
106 /** Preferemce's name: hide cancel button. */
107 private static final String PREFS_HIDE_CANCEL_BUTTON = "hide_cancel_button";
109 /** Preference's name: to. */
110 private static final String PREFS_TO = "to";
111 /** Preference's name: text. */
112 private static final String PREFS_TEXT = "text";
113 /** Preference's name: connector name. */
114 private static final String PREFS_CONNECTOR_ID = "connector_name";
116 /** Sleep before autoexit. */
117 private static final int SLEEP_BEFORE_EXIT = 75;
119 /** Preferences: hide ads. */
120 static boolean prefsNoAds = false;
121 /** Hased IMEI. */
122 static String imeiHash = null;
123 /** Preferences: connector specs. */
124 static ConnectorSpec prefsConnectorSpecs = null;
125 /** Save prefsConnectorSpecs.getID() here. */
126 static String prefsConnectorID = null;
127 /** Preferences: show mobile numbers only. */
128 static boolean prefsMobilesOnly;
130 /** List of available {@link ConnectorSpec}s. */
131 private final static ArrayList<ConnectorSpec> CONNECTORS = new ArrayList<ConnectorSpec>();
133 /** Array of md5(prefsSender) for which no ads should be displayed. */
134 private static final String[] NO_AD_HASHS = {
135 "2986b6d93053a53ff13008b3015a77ff", // flx
136 "57a3c7c19329fd84c2252a9b2866dd93", // mirweb
137 "10b7a2712beee096acbc67416d7d71a1", // mo
138 "f6b3b72300e918436b4c4c9fdf909e8c", // joerg s.
139 "4c18f7549b643045f0ff69f61e8f7e72", // frank j.
140 "7684154558d19383552388d9bc92d446", // henning k.
141 "64c7414288e9a9b57a33e034f384ed30", // dominik l.
142 "c479a2e701291c751f0f91426bcaabf3", // bernhard g.
143 "ae7dfedf549f98a349ad8c2068473c6b", // dominik k.-v.
144 "18bc29cd511613552861da6ef51766ce", // niels b.
145 "2985011f56d0049b0f4f0caed3581123", // sven l.
146 "64724033da297a915a89023b11ac2e47", // wilfried m.
147 "cfd8d2efb3eac39705bd62c4dfe5e72d", // achim e.
148 "ca56e7518fdbda832409ef07edd4c273", // michael s.
149 "bed2f068ca8493da4179807d1afdbd83", // axel q.
150 "4c35400c4fa3ffe2aefcf1f9131eb855", // gerhard s.
151 "02158d2a80b1ef9c4d684a4ca808b93d", // camilo s.
152 "1177c6e67f98cdfed6c84d99e85d30de", // daniel p.
153 "3f082dd7e21d5c64f34a69942c474ce7", // andre j.
154 "5383540b2f8c298532f874126b021e73", // marco a.
155 "858ddfb8635d1539884086dca2726468", // lado
156 "6e8bbb35091219a80e278ae61f31cce9", // mario s.
159 /** Public Dialog ref. */
160 static Dialog dialog = null;
161 /** Dialog String. */
162 static String dialogString = null;
164 /** true if preferences got opened. */
165 static boolean doPreferences = false;
167 /** Dialog: about. */
168 private static final int DIALOG_ABOUT = 0;
169 /** Dialog: updates. */
170 private static final int DIALOG_UPDATE = 2;
171 /** Dialog: captcha. */
172 private static final int DIALOG_CAPTCHA = 3;
173 /** Dialog: post donate. */
174 private static final int DIALOG_POSTDONATE = 4;
175 /** Dialog: custom sender. */
176 private static final int DIALOG_CUSTOMSENDER = 5;
177 /** Dialog: send later: date. */
178 private static final int DIALOG_SENDLATER_DATE = 6;
179 /** Dialog: send later: time. */
180 private static final int DIALOG_SENDLATER_TIME = 7;
181 /** Dialog: pre donate. */
182 private static final int DIALOG_PREDONATE = 8;
184 /** Message for logging. **/
185 static final int MESSAGE_LOG = 0;
186 /** Message for update free sms count. **/
187 static final int MESSAGE_FREECOUNT = 1;
188 /** Message to open settings. */
189 static final int MESSAGE_SETTINGS = 4;
190 /** Message to reset data. */
191 static final int MESSAGE_RESET = 5;
192 /** Message show cpatcha. */
193 static final int MESSAGE_ANTICAPTCHA = 6;
195 /** Intent's extra for errormessages. */
196 static final String EXTRA_ERRORMESSAGE = "de.ub0r.android.intent.extra.ERRORMESSAGE";
198 /** Persistent Message store. */
199 private static String lastMsg = null;
200 /** Persistent Recipient store. */
201 private static String lastTo = null;
203 /** Backup for params. */
204 private static ConnectorCommand lastParams = null;
205 /** User wants to send the message later. */
206 private static boolean wantSendLater = false;
208 /** Helper for API 5. */
209 static HelperAPI5Contacts helperAPI5c = null;
211 /** Text's label. */
212 private TextView textLabel;
214 /** Show extras. */
215 private boolean showExtras = false;
217 /** MessageHandler. */
218 private Handler messageHandler = new Handler() {
220 * {@inheritDoc}
222 @Override
223 public final void handleMessage(final Message msg) {
224 switch (msg.what) {
225 case MESSAGE_LOG: // msg is String or Resource StringID
226 if (msg.obj instanceof String) {
227 WebSMS.this.log((String) msg.obj);
228 } else if (msg.obj instanceof Integer) {
229 WebSMS.this.log(WebSMS.this.getString(((Integer) msg.obj)
230 .intValue()));
231 } else {
232 WebSMS.this.log(msg.obj.toString());
234 return;
235 case MESSAGE_FREECOUNT:
236 WebSMS.this.updateBalance();
237 return;
238 case MESSAGE_SETTINGS:
239 WebSMS.this.startActivity(new Intent(WebSMS.this,
240 Preferences.class));
241 case MESSAGE_RESET:
242 WebSMS.this.reset();
243 return;
244 case MESSAGE_ANTICAPTCHA:
245 WebSMS.this.showDialog(DIALOG_CAPTCHA);
246 return;
247 default:
248 return;
253 /** TextWatcher updating char count on writing. */
254 private TextWatcher textWatcher = new TextWatcher() {
256 * {@inheritDoc}
258 public void afterTextChanged(final Editable s) {
259 int[] l = SmsMessage.calculateLength(s, false);
260 WebSMS.this.textLabel.setText(l[0] + "/" + l[2]);
263 /** Needed dummy. */
264 public void beforeTextChanged(final CharSequence s, final int start,
265 final int count, final int after) {
268 /** Needed dummy. */
269 public void onTextChanged(final CharSequence s, final int start,
270 final int before, final int count) {
275 * {@inheritDoc}
277 @Override
278 public final void onCreate(final Bundle savedInstanceState) {
279 super.onCreate(savedInstanceState);
280 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
282 // save ref to me.
283 me = this;
284 try {
285 WebSMS.helperAPI5c = new HelperAPI5Contacts();
286 if (!helperAPI5c.isAvailable()) {
287 WebSMS.helperAPI5c = null;
289 } catch (VerifyError e) {
290 WebSMS.helperAPI5c = null;
291 Log.d(TAG, "no api5 running", e);
293 // Restore preferences
294 final SharedPreferences p = PreferenceManager
295 .getDefaultSharedPreferences(this);
296 // inflate XML
297 if (p.getBoolean(PREFS_SOFTKEYS, false)) {
298 this.setContentView(R.layout.main_touch);
299 } else {
300 this.setContentView(R.layout.main);
303 this.findViewById(R.id.to).requestFocus();
305 // display changelog?
306 String v0 = p.getString(PREFS_LAST_RUN, "");
307 String v1 = this.getResources().getString(R.string.app_version);
308 if (!v0.equals(v1)) {
309 SharedPreferences.Editor editor = p.edit();
310 editor.putString(PREFS_LAST_RUN, v1);
311 editor.commit();
312 this.showDialog(DIALOG_UPDATE);
315 this.reloadPrefs();
317 lastTo = p.getString(PREFS_TO, "");
318 lastMsg = p.getString(PREFS_TEXT, "");
320 // register Listener
321 ((Button) this.findViewById(R.id.send_)).setOnClickListener(this);
322 ((Button) this.findViewById(R.id.cancel)).setOnClickListener(this);
323 ((Button) this.findViewById(R.id.change_connector))
324 .setOnClickListener(this);
325 ((Button) this.findViewById(R.id.extras)).setOnClickListener(this);
327 this.textLabel = (TextView) this.findViewById(R.id.text_);
328 ((EditText) this.findViewById(R.id.text))
329 .addTextChangedListener(this.textWatcher);
331 ((TextView) this.findViewById(R.id.freecount)).setOnClickListener(this);
333 final MultiAutoCompleteTextView to = (MultiAutoCompleteTextView) this
334 .findViewById(R.id.to);
335 to.setAdapter(new MobilePhoneAdapter(this));
336 to.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
338 Intent intent = this.getIntent();
339 final String action = intent.getAction();
340 if (action != null) {
341 // launched by clicking a sms: link, target number is in URI.
342 final Uri uri = intent.getData();
343 if (uri != null) {
344 final String scheme = uri.getScheme();
345 if (scheme.equals("sms") || scheme.equals("smsto")) {
346 String s = uri.getSchemeSpecificPart();
347 if (s != null) {
348 s = s.trim();
349 if (s.endsWith(",")) {
350 s = s.substring(0, s.length() - 1).trim();
352 // recipient = WebSMS.cleanRecipient(recipient);
353 if (s.indexOf('<') < 0) {
354 // try to fetch recipient's name from phonebook
355 String n = null;
356 if (helperAPI5c != null) {
357 try {
358 n = helperAPI5c.getNameForNumber(this, s);
359 } catch (NoClassDefFoundError e) {
360 helperAPI5c = null;
363 if (helperAPI5c == null) {
364 Cursor c = this
365 .managedQuery(
366 Phones.CONTENT_URI,
367 new String[] {
368 PhonesColumns.NUMBER,
369 PeopleColumns.DISPLAY_NAME },
370 PhonesColumns.NUMBER + " = '"
371 + s + "'", null, null);
372 if (c.moveToFirst()) {
373 n = c
374 .getString(c
375 .getColumnIndex(PeopleColumns.DISPLAY_NAME));
378 if (n != null) {
379 s = n + " <" + s + ">, ";
382 ((EditText) this.findViewById(R.id.to)).setText(s);
383 lastTo = s;
385 final Bundle extras = intent.getExtras();
386 if (extras != null) {
387 s = extras.getCharSequence(Intent.EXTRA_TEXT)
388 .toString();
389 if (s != null) {
390 ((EditText) this.findViewById(R.id.text))
391 .setText(s);
392 lastMsg = s;
394 s = extras.getString(EXTRA_ERRORMESSAGE);
395 if (s != null) {
396 Toast.makeText(this, s, Toast.LENGTH_LONG).show();
399 if (!prefsNoAds) {
400 // do not display any ads for donators
401 // display ads
402 ((AdView) WebSMS.this.findViewById(R.id.ad))
403 .setVisibility(View.VISIBLE);
409 // check default prefix
410 if (!p.getString(PREFS_DEFPREFIX, "").startsWith("+")) {
411 WebSMS.this.log(R.string.log_error_defprefix);
413 if (p.getBoolean(PREFS_AUTOUPDATE, false)) {
414 this.updateFreecount(false);
419 * {@inheritDoc}
421 @Override
422 protected final void onResume() {
423 super.onResume();
424 // set free sms count
425 this.updateBalance();
427 // restart dialog if needed
428 if (dialogString != null) {
429 if (dialog != null) {
430 try {
431 dialog.dismiss();
432 } catch (Exception e) {
433 // nothing to do
436 dialog = ProgressDialog.show(this, null, dialogString, true);
439 // if coming from prefs..
440 if (doPreferences) {
441 this.reloadPrefs();
442 doPreferences = false;
443 final Intent intent = ConnectorCommand.bootstrap()
444 .setToIntent(null);
445 Log.d(TAG, "send broadcast: " + intent.getAction());
446 this.sendBroadcast(intent);
449 this.setButtons();
451 // reload text/recipient from local store
452 final EditText et0 = (EditText) this.findViewById(R.id.text);
453 if (lastMsg != null) {
454 et0.setText(lastMsg);
455 } else {
456 et0.setText("");
458 final EditText et1 = (EditText) this.findViewById(R.id.to);
459 if (lastTo != null) {
460 et1.setText(lastTo);
461 } else {
462 et1.setText("");
465 if (lastTo != null && lastTo.length() > 0) {
466 et0.requestFocus();
467 } else {
468 et1.requestFocus();
471 // query for connectors
472 final Intent i = new Intent(CommandReceiver.ACTION_CONNECTOR_UPDATE);
473 Log.d(TAG, "send broadcast: " + i.getAction());
474 this.sendBroadcast(i);
478 * Update balance.
480 private void updateBalance() {
481 final StringBuilder buf = new StringBuilder();
483 for (ConnectorSpec cs : getConnectors(
484 ConnectorSpec.CAPABILITIES_UPDATE, // .
485 ConnectorSpec.STATUS_ENABLED)) {
486 final String b = cs.getBalance();
487 if (b == null || b.length() == 0) {
488 continue;
490 if (buf.length() > 0) {
491 buf.append(", ");
493 buf.append(cs.getName());
494 buf.append(": ");
495 buf.append(b);
498 TextView tw = (TextView) this.findViewById(R.id.freecount);
499 tw.setText(this.getString(R.string.free_) + " " + buf.toString() + " "
500 + this.getString(R.string.click_for_update));
504 * {@inheritDoc}
506 @Override
507 public final void onPause() {
508 super.onPause();
509 // store input data to persitent stores
510 lastMsg = ((EditText) this.findViewById(R.id.text)).getText()
511 .toString();
512 lastTo = ((EditText) this.findViewById(R.id.to)).getText().toString();
514 // store input data to preferences
515 SharedPreferences.Editor editor = PreferenceManager
516 .getDefaultSharedPreferences(this).edit();
517 // common
518 editor.putString(PREFS_TO, lastTo);
519 editor.putString(PREFS_TEXT, lastMsg);
520 // commit changes
521 editor.commit();
523 this.savePreferences();
527 * Read static vars holding preferences.
529 private void reloadPrefs() {
530 final SharedPreferences p = PreferenceManager
531 .getDefaultSharedPreferences(this);
532 boolean b = p.getBoolean(PREFS_CHANGE_CONNECTOR_BUTTON, false);
533 View v = this.findViewById(R.id.change_connector);
534 if (b) {
535 v.setVisibility(View.VISIBLE);
536 } else {
537 v.setVisibility(View.GONE);
540 b = !p.getBoolean(PREFS_HIDE_CANCEL_BUTTON, false);
541 v = this.findViewById(R.id.cancel);
542 if (b) {
543 v.setVisibility(View.VISIBLE);
544 } else {
545 v.setVisibility(View.GONE);
548 prefsConnectorID = p.getString(PREFS_CONNECTOR_ID, "");
549 prefsConnectorSpecs = getConnectorByID(prefsConnectorID);
551 prefsMobilesOnly = p.getBoolean(PREFS_MOBILES_ONLY, false);
553 prefsNoAds = false;
554 String hash = md5(p.getString(PREFS_SENDER, ""));
555 for (String h : NO_AD_HASHS) {
556 if (hash.equals(h)) {
557 prefsNoAds = true;
558 break;
561 if (!prefsNoAds && this.getImeiHash() != null) {
562 for (String h : NO_AD_HASHS) {
563 if (imeiHash.equals(h)) {
564 prefsNoAds = true;
565 break;
570 this.setButtons();
574 * Show/hide, enable/disable send buttons.
576 private void setButtons() {
577 final ConnectorSpec[] enabled = getConnectors(
578 ConnectorSpec.CAPABILITIES_SEND, ConnectorSpec.STATUS_ENABLED);
579 final int c = enabled.length;
581 Button btn = (Button) this.findViewById(R.id.send_);
582 // show/hide buttons
583 btn.setEnabled(c > 0);
584 btn.setVisibility(View.VISIBLE);
585 if (c == 1) {
586 prefsConnectorSpecs = enabled[0];
589 if (prefsConnectorSpecs != null) {
590 final short features = ConnectorSpec.SubConnectorSpec.FEATURE_NONE;
591 // FIXME: prefsConnectorSpecs.getFeatures();
592 final boolean sFlashsms = false;
593 // (features & ConnectorSpecs.FEATURE_FLASHSMS) ==
594 // ConnectorSpecs.FEATURE_FLASHSMS;
595 final boolean sCustomsender = false;
596 // (features & ConnectorSpecs.FEATURE_CUSTOMSENDER) ==
597 // ConnectorSpecs.FEATURE_CUSTOMSENDER;
598 final boolean sSendLater = false;
599 // (features & ConnectorSpecs.FEATURE_SENDLATER) ==
600 // ConnectorSpecs.FEATURE_SENDLATER;
601 if (sFlashsms || sCustomsender || sSendLater) {
602 this.findViewById(R.id.extras).setVisibility(View.VISIBLE);
603 } else {
604 this.findViewById(R.id.extras).setVisibility(View.GONE);
606 if (this.showExtras && sFlashsms) {
607 this.findViewById(R.id.flashsms).setVisibility(View.VISIBLE);
608 } else {
609 this.findViewById(R.id.flashsms).setVisibility(View.GONE);
611 if (this.showExtras && sCustomsender) {
612 this.findViewById(R.id.custom_sender).setVisibility(
613 View.VISIBLE);
614 } else {
615 this.findViewById(R.id.custom_sender).setVisibility(View.GONE);
617 if (this.showExtras && sSendLater) {
618 this.findViewById(R.id.send_later).setVisibility(View.VISIBLE);
619 } else {
620 this.findViewById(R.id.send_later).setVisibility(View.GONE);
623 this.setTitle(this.getString(R.string.app_name) + " - "
624 + prefsConnectorSpecs.getName());
629 * Resets persistent store.
631 private void reset() {
632 ((EditText) this.findViewById(R.id.text)).setText("");
633 ((EditText) this.findViewById(R.id.to)).setText("");
634 lastMsg = null;
635 lastTo = null;
636 // save user preferences
637 SharedPreferences.Editor editor = PreferenceManager
638 .getDefaultSharedPreferences(this).edit();
639 editor.putString(PREFS_TO, "");
640 editor.putString(PREFS_TEXT, "");
641 // commit changes
642 editor.commit();
645 /** Save prefs. */
646 final void savePreferences() {
647 if (prefsConnectorSpecs != null) {
648 PreferenceManager.getDefaultSharedPreferences(this).edit()
649 .putString(PREFS_CONNECTOR_ID, prefsConnectorSpecs.getID())
650 .commit();
655 * Run Connector.update().
657 * @param forceUpdate
658 * force update, if false only blank balances will get updated
660 private void updateFreecount(final boolean forceUpdate) {
661 final Intent intent = ConnectorCommand.update().setToIntent(null);
662 Log.d(TAG, "send broadcast: " + intent.getAction());
663 this.sendBroadcast(intent);
667 * {@inheritDoc}
669 public final void onClick(final View v) {
670 switch (v.getId()) {
671 case R.id.freecount:
672 this.updateFreecount(true);
673 break;
674 case R.id.send_:
675 this.send(prefsConnectorSpecs);
676 break;
677 case R.id.cancel:
678 this.reset();
679 break;
680 // FIXME: case R.id.captcha_btn:
681 // ConnectorO2.captchaSolve = ((EditText) v.getRootView()
682 // .findViewById(R.id.captcha_edt)).getText().toString();
683 // synchronized (ConnectorO2.CAPTCHA_SYNC) {
684 // ConnectorO2.CAPTCHA_SYNC.notify();
685 // }
686 // this.dismissDialog(DIALOG_CAPTCHA);
687 // break;
688 case R.id.change_connector:
689 this.changeConnectorMenu();
690 break;
691 case R.id.extras:
692 this.showExtras = !this.showExtras;
693 this.setButtons();
694 break;
695 default:
696 break;
701 * {@inheritDoc}
703 @Override
704 public final boolean onCreateOptionsMenu(final Menu menu) {
705 MenuInflater inflater = this.getMenuInflater();
706 inflater.inflate(R.menu.menu, menu);
707 return true;
711 * Display "change connector" menu.
713 private void changeConnectorMenu() {
714 AlertDialog.Builder builder = new AlertDialog.Builder(this);
715 builder.setTitle(R.string.change_connector_);
716 final ArrayList<String> items = new ArrayList<String>();
717 for (ConnectorSpec cs : getConnectors(ConnectorSpec.CAPABILITIES_SEND,
718 ConnectorSpec.STATUS_ENABLED)) {
719 items.add(cs.getName());
721 // TODO: add subconnectors
723 builder.setItems(items.toArray(new String[0]),
724 new DialogInterface.OnClickListener() {
725 public void onClick(final DialogInterface d, // .
726 final int item) {
727 prefsConnectorSpecs = getConnectorByName(items
728 .get(item));
729 WebSMS.this.setButtons();
730 // save user preferences
731 PreferenceManager.getDefaultSharedPreferences(
732 WebSMS.this).edit().putString(
733 PREFS_CONNECTOR_ID,
734 prefsConnectorSpecs.getName()).commit();
737 builder.create().show();
741 *{@inheritDoc}
743 @Override
744 public final boolean onOptionsItemSelected(final MenuItem item) {
745 switch (item.getItemId()) {
746 case R.id.item_about: // start about dialog
747 this.showDialog(DIALOG_ABOUT);
748 return true;
749 case R.id.item_settings: // start settings activity
750 this.startActivity(new Intent(this, Preferences.class));
751 return true;
752 case R.id.item_donate:
753 this.showDialog(DIALOG_PREDONATE);
754 return true;
755 case R.id.item_more:
756 try {
757 this.startActivity(new Intent(Intent.ACTION_VIEW, Uri
758 .parse("market://search?q=pub:\"Felix Bechstein\"")));
759 } catch (ActivityNotFoundException e) {
760 Log.e(TAG, "no market", e);
762 return true;
763 case R.id.item_connector:
764 this.changeConnectorMenu();
765 return true;
766 default:
767 return false;
772 * {@inheritDoc}
774 @Override
775 protected final Dialog onCreateDialog(final int id) {
776 Dialog d;
777 AlertDialog.Builder builder;
778 switch (id) {
779 case DIALOG_PREDONATE:
780 builder = new AlertDialog.Builder(this);
781 builder.setTitle(R.string.donate_);
782 builder.setMessage(R.string.predonate);
783 builder.setPositiveButton(R.string.donate_,
784 new DialogInterface.OnClickListener() {
785 public void onClick(final DialogInterface dialog,
786 final int which) {
787 try {
788 WebSMS.this
789 .startActivity(new Intent(
790 Intent.ACTION_VIEW,
792 .parse(WebSMS.this
793 .getString(R.string.donate_url))));
794 } catch (ActivityNotFoundException e) {
795 Log.e(TAG, "no browser", e);
796 } finally {
797 WebSMS.this.showDialog(DIALOG_POSTDONATE);
801 builder.setNegativeButton(android.R.string.cancel, null);
802 return builder.create();
803 case DIALOG_POSTDONATE:
804 builder = new AlertDialog.Builder(this);
805 builder.setTitle(R.string.remove_ads_);
806 builder.setMessage(R.string.postdonate);
807 builder.setPositiveButton(R.string.send_,
808 new DialogInterface.OnClickListener() {
809 public void onClick(final DialogInterface dialog,
810 final int which) {
811 final Intent in = new Intent(Intent.ACTION_SEND);
813 .putExtra(
814 Intent.EXTRA_EMAIL,
815 new String[] {
816 WebSMS.this
817 .getString(R.string.donate_mail),
818 "" }); // FIXME: "" is a k9
819 // hack.
820 in.putExtra(Intent.EXTRA_TEXT, WebSMS.this
821 .getImeiHash());
823 .putExtra(
824 Intent.EXTRA_SUBJECT,
825 WebSMS.this
826 .getString(R.string.app_name)
827 + " "
828 + WebSMS.this
829 .getString(R.string.donate_subject));
830 in.setType("text/plain");
831 WebSMS.this.startActivity(in);
834 builder.setNegativeButton(android.R.string.cancel, null);
835 return builder.create();
836 case DIALOG_ABOUT:
837 d = new Dialog(this);
838 d.setContentView(R.layout.about);
839 d.setTitle(this.getString(R.string.about_) + " v"
840 + this.getString(R.string.app_version));
841 StringBuffer authors = new StringBuffer();
842 for (ConnectorSpec cs : getConnectors(
843 ConnectorSpec.CAPABILITIES_NONE,
844 ConnectorSpec.STATUS_INACTIVE)) {
845 final String a = cs.getAuthor();
846 if (a != null && a.length() > 0) {
847 authors.append(cs.getName());
848 authors.append(":\t");
849 authors.append(a);
850 authors.append("\n");
853 ((TextView) d.findViewById(R.id.author_connectors)).setText(authors
854 .toString().trim());
855 return d;
856 case DIALOG_UPDATE:
857 builder = new AlertDialog.Builder(this);
858 builder.setTitle(R.string.changelog_);
859 final String[] changes = this.getResources().getStringArray(
860 R.array.updates);
861 final StringBuilder buf = new StringBuilder(changes[0]);
862 for (int i = 1; i < changes.length; i++) {
863 buf.append("\n\n");
864 buf.append(changes[i]);
866 builder.setIcon(android.R.drawable.ic_menu_info_details);
867 builder.setMessage(buf.toString());
868 builder.setCancelable(true);
869 builder.setPositiveButton(android.R.string.ok, null);
870 return builder.create();
871 case DIALOG_CAPTCHA:
872 d = new Dialog(this);
873 d.setTitle(R.string.captcha_);
874 d.setContentView(R.layout.captcha);
875 d.setCancelable(false);
876 ((Button) d.findViewById(R.id.captcha_btn))
877 .setOnClickListener(this);
878 return d;
879 case DIALOG_CUSTOMSENDER:
880 builder = new AlertDialog.Builder(this);
881 builder.setTitle(R.string.custom_sender);
882 builder.setCancelable(true);
883 final EditText et = new EditText(this);
884 builder.setView(et);
885 builder.setPositiveButton(android.R.string.ok,
886 new DialogInterface.OnClickListener() {
887 public void onClick(final DialogInterface dialog,
888 final int id) {
889 WebSMS.lastParams.setCustomSender(et.getText()
890 .toString());
891 if (WebSMS.wantSendLater) {
892 WebSMS.this
893 .showDialog(WebSMS.DIALOG_SENDLATER_DATE);
894 } else {
895 WebSMS.this.send(WebSMS.prefsConnectorSpecs,
896 WebSMS.lastParams);
900 builder.setNegativeButton(android.R.string.cancel, null);
901 return builder.create();
902 case DIALOG_SENDLATER_DATE:
903 Calendar c = Calendar.getInstance();
904 return new DatePickerDialog(this, this, c.get(Calendar.YEAR), c
905 .get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
906 case DIALOG_SENDLATER_TIME:
907 c = Calendar.getInstance();
908 return new MyTimePickerDialog(this, this, c
909 .get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true);
910 default:
911 return null;
916 * {@inheritDoc}
918 @Override
919 protected final void onPrepareDialog(final int id, final Dialog dlg) {
920 switch (id) {
921 // FIXME: case DIALOG_CAPTCHA:
922 // if (ConnectorO2.captcha != null) {
923 // ((ImageView) dlg.findViewById(R.id.captcha_img))
924 // .setImageDrawable(ConnectorO2.captcha);
925 // ConnectorO2.captcha = null;
926 // }
927 // break;
928 default:
929 break;
934 * Log text.
936 * @param text
937 * text as resID
939 public final void log(final int text) {
940 this.log(this.getString(text));
944 * Log text.
946 * @param text
947 * text
949 public final void log(final String text) {
950 try {
951 Toast.makeText(this.getApplicationContext(), text,
952 Toast.LENGTH_LONG).show();
953 } catch (RuntimeException e) {
954 Log.e(TAG, null, e);
959 * Send Text.
961 * @param connector
962 * which connector should be used.
963 * @param command
964 * {@link ConnectorCommand} to push to connector
966 private void send(final ConnectorSpec connector,
967 final ConnectorCommand command) {
968 try {
969 final Intent intent = command.setToIntent(null);
970 prefsConnectorSpecs.setToIntent(intent);
971 connector.addStatus(ConnectorSpec.STATUS_SENDING);
972 Log.d(TAG, "send broadcast: " + intent.getAction());
973 this.sendBroadcast(intent);
974 } catch (Exception e) {
975 Log.e(TAG, null, e);
976 } finally {
977 this.reset();
978 if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
979 PREFS_AUTOEXIT, false)) {
980 try {
981 Thread.sleep(SLEEP_BEFORE_EXIT);
982 } catch (InterruptedException e) {
983 Log.e(TAG, null, e);
985 this.finish();
991 * Send Text.
993 * @param connector
994 * which connector should be used.
996 private void send(final ConnectorSpec connector) {
997 // fetch text/recipient
998 final String to = ((EditText) this.findViewById(R.id.to)).getText()
999 .toString();
1000 final String text = ((EditText) this.findViewById(R.id.text)).getText()
1001 .toString();
1002 if (to.length() == 0 || text.length() == 0) {
1003 return;
1006 if (!prefsNoAds) {
1007 // do not display any ads for donators
1008 // display ads
1009 ((AdView) WebSMS.this.findViewById(R.id.ad))
1010 .setVisibility(View.VISIBLE);
1013 CheckBox v = (CheckBox) this.findViewById(R.id.flashsms);
1014 final boolean flashSMS = (v.getVisibility() == View.VISIBLE)
1015 && v.isEnabled() && v.isChecked();
1016 v = (CheckBox) this.findViewById(R.id.send_later);
1017 if ((v.getVisibility() == View.VISIBLE) && v.isEnabled()
1018 && v.isChecked()) {
1019 wantSendLater = true;
1021 SharedPreferences p = PreferenceManager
1022 .getDefaultSharedPreferences(this);
1023 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
1024 final String defSender = p.getString(PREFS_SENDER, "");
1026 final ConnectorCommand command = ConnectorCommand.send(defPrefix,
1027 defSender, to.split(","), text, flashSMS);
1029 v = (CheckBox) this.findViewById(R.id.custom_sender);
1030 if ((v.getVisibility() == View.VISIBLE) && v.isEnabled()
1031 && v.isChecked()) {
1032 lastParams = command;
1033 this.showDialog(DIALOG_CUSTOMSENDER);
1034 } else {
1035 if (wantSendLater) {
1036 lastParams = command;
1037 this.showDialog(DIALOG_SENDLATER_DATE);
1038 } else {
1039 this.send(connector, command);
1045 * A Date was set.
1047 * @param view
1048 * DatePicker View
1049 * @param year
1050 * year set
1051 * @param monthOfYear
1052 * month set
1053 * @param dayOfMonth
1054 * day set
1056 public final void onDateSet(final DatePicker view, final int year,
1057 final int monthOfYear, final int dayOfMonth) {
1058 final Calendar c = Calendar.getInstance();
1059 if (lastParams != null) {
1060 final long l = lastParams.getSendLater();
1061 if (l > 0) {
1062 c.setTimeInMillis(l);
1065 c.set(Calendar.YEAR, year);
1066 c.set(Calendar.MONTH, monthOfYear);
1067 c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
1068 lastParams.setSendLater(c.getTimeInMillis());
1070 this.showDialog(DIALOG_SENDLATER_TIME);
1074 * A Time was set.
1076 * @param view
1077 * TimePicker View
1078 * @param hour
1079 * hour set
1080 * @param minutes
1081 * minutes set
1083 public final void onTimeSet(final TimePicker view, final int hour,
1084 final int minutes) {
1085 if (prefsConnectorSpecs.getName().equals("WebSMS.o2") // FIXME
1086 && minutes % 15 != 0) {
1087 Toast.makeText(this, R.string.log_error_o2_sendlater,
1088 Toast.LENGTH_LONG).show();
1089 return;
1092 final Calendar c = Calendar.getInstance();
1093 if (lastParams != null) {
1094 c.setTimeInMillis(lastParams.getSendLater());
1096 c.set(Calendar.HOUR_OF_DAY, hour);
1097 c.set(Calendar.MINUTE, minutes);
1098 lastParams.setSendLater(c.getTimeInMillis());
1100 this.send(WebSMS.prefsConnectorSpecs, WebSMS.lastParams);
1104 * Send WebSMS a Message.
1106 * @param messageType
1107 * type
1108 * @param data
1109 * data
1111 public static final void pushMessage(final int messageType,
1112 final Object data) {
1113 if (WebSMS.me == null) {
1114 return;
1116 Message.obtain(WebSMS.me.messageHandler, messageType, data)
1117 .sendToTarget();
1121 * Calc MD5 Hash from String.
1123 * @param s
1124 * input
1125 * @return hash
1127 static String md5(final String s) {
1128 try {
1129 // Create MD5 Hash
1130 MessageDigest digest = java.security.MessageDigest
1131 .getInstance("MD5");
1132 digest.update(s.getBytes());
1133 byte[] messageDigest = digest.digest();
1134 // Create Hex String
1135 StringBuilder hexString = new StringBuilder(32);
1136 int b;
1137 for (int i = 0; i < messageDigest.length; i++) {
1138 b = 0xFF & messageDigest[i];
1139 if (b < 0x10) {
1140 hexString.append('0' + Integer.toHexString(b));
1141 } else {
1142 hexString.append(Integer.toHexString(b));
1145 return hexString.toString();
1146 } catch (NoSuchAlgorithmException e) {
1147 Log.e(TAG, null, e);
1149 return "";
1153 * Get MD5 hash of the IMEI (device id).
1155 * @return MD5 hash of IMEI
1157 private String getImeiHash() {
1158 if (imeiHash == null) {
1159 // get imei
1160 TelephonyManager mTelephonyMgr = (TelephonyManager) this
1161 .getSystemService(TELEPHONY_SERVICE);
1162 final String did = mTelephonyMgr.getDeviceId();
1163 if (did != null) {
1164 imeiHash = md5(did);
1167 return imeiHash;
1171 * Add or update a {@link ConnectorSpec}.
1173 * @param connector
1174 * connector
1176 public static final void addConnector(final ConnectorSpec connector) {
1177 synchronized (CONNECTORS) {
1178 if (connector == null || connector.getID() == null
1179 || connector.getName() == null) {
1180 return;
1182 final ConnectorSpec c = getConnectorByID(connector.getID());
1183 if (c != null) {
1184 c.update(connector);
1185 } else {
1186 final int l = CONNECTORS.size();
1187 final String name = connector.getName();
1188 Log.d(TAG, "add connector with id: " + connector.getID());
1189 Log.d(TAG, "add connector with name: " + name);
1190 boolean added = false;
1191 try {
1192 for (int i = 0; i < l; i++) {
1193 final ConnectorSpec cs = CONNECTORS.get(i);
1194 if (name.compareToIgnoreCase(cs.getName()) < 0) {
1195 CONNECTORS.add(i, connector);
1196 added = true;
1199 } catch (NullPointerException e) {
1200 Log.e(TAG, "error while sorting", e);
1202 if (!added) {
1203 CONNECTORS.add(connector);
1206 if (prefsConnectorSpecs == null
1207 && prefsConnectorID.equals(connector.getID())) {
1208 prefsConnectorSpecs = connector;
1209 me.setButtons();
1212 if (connector.getBalance() != null) {
1213 me.updateBalance();
1219 * Get {@link ConnectorSpec} by ID.
1221 * @param id
1222 * ID
1223 * @return {@link ConnectorSpec}
1225 public static final ConnectorSpec getConnectorByID(final String id) {
1226 synchronized (CONNECTORS) {
1227 if (id == null) {
1228 return null;
1230 final int l = CONNECTORS.size();
1231 for (int i = 0; i < l; i++) {
1232 final ConnectorSpec c = CONNECTORS.get(i);
1233 if (id.equals(c.getID())) {
1234 return c;
1238 return null;
1242 * Get {@link ConnectorSpec} by name.
1244 * @param name
1245 * name
1246 * @return {@link ConnectorSpec}
1248 public static final ConnectorSpec getConnectorByName(final String name) {
1249 synchronized (CONNECTORS) {
1250 if (name == null) {
1251 return null;
1253 final int l = CONNECTORS.size();
1254 for (int i = 0; i < l; i++) {
1255 final ConnectorSpec c = CONNECTORS.get(i);
1256 if (name.equals(c.getName())) {
1257 return c;
1261 return null;
1265 * Get {@link ConnectorSpec}s by capabilities and/or status.
1267 * @param capabilities
1268 * capabilities needed
1269 * @param status
1270 * status required
1271 * @return {@link ConnectorSpec}s
1273 public static final ConnectorSpec[] getConnectors(final short capabilities,
1274 final short status) {
1275 synchronized (CONNECTORS) {
1276 final ArrayList<ConnectorSpec> ret = new ArrayList<ConnectorSpec>(
1277 CONNECTORS.size());
1278 final int l = CONNECTORS.size();
1279 for (int i = 0; i < l; i++) {
1280 final ConnectorSpec c = CONNECTORS.get(i);
1281 if (c.hasCapabilities(capabilities) && c.hasStatus(status)) {
1282 ret.add(c);
1285 return ret.toArray(new ConnectorSpec[0]);