add CommandReceiver
[andGMXsms.git] / src / de / ub0r / android / andGMXsms / WebSMS.java
blob6dd6622ab3d6fc56784162e87785e39441d2dcdb
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.ImageView;
62 import android.widget.MultiAutoCompleteTextView;
63 import android.widget.TextView;
64 import android.widget.TimePicker;
65 import android.widget.Toast;
67 import com.admob.android.ads.AdView;
69 import de.ub0r.android.websms.connector.common.CommandReceiver;
70 import de.ub0r.android.websms.connector.common.ConnectorCommand;
71 import de.ub0r.android.websms.connector.common.ConnectorSpec;
73 /**
74 * Main Activity.
76 * @author flx
78 @SuppressWarnings("deprecation")
79 public class WebSMS extends Activity implements OnClickListener,
80 OnDateSetListener, OnTimeSetListener {
81 /** Tag for output. */
82 private static final String TAG = "WebSMS";
84 /** Static reference to running Activity. */
85 private static WebSMS me;
86 /** Preference's name: last version run. */
87 private static final String PREFS_LAST_RUN = "lastrun";
88 /** Preference's name: user's phonenumber. */
89 static final String PREFS_SENDER = "sender";
90 /** Preference's name: default prefix. */
91 static final String PREFS_DEFPREFIX = "defprefix";
92 /** Preference's name: touch keyboard. */
93 private static final String PREFS_SOFTKEYS = "softkeyboard";
94 /** Preference's name: update balace on start. */
95 private static final String PREFS_AUTOUPDATE = "autoupdate";
96 /** Preference's name: exit after sending. */
97 private static final String PREFS_AUTOEXIT = "autoexit";
98 /** Preference's name: show mobile numbers only. */
99 private static final String PREFS_MOBILES_ONLY = "mobiles_only";
100 /** Preference's name: vibrate on failed sending. */
101 static final String PREFS_FAIL_VIBRATE = "fail_vibrate";
102 /** Preference's name: sound on failed sending. */
103 static final String PREFS_FAIL_SOUND = "fail_sound";
104 /** Preferemce's name: enable change connector button. */
105 private static final String PREFS_CHANGE_CONNECTOR_BUTTON = "change_connector_button";
106 /** Preference's name: to. */
107 private static final String PREFS_TO = "to";
108 /** Preference's name: text. */
109 private static final String PREFS_TEXT = "text";
110 /** Preference's name: connector name. */
111 private static final String PREFS_CONNECTOR_ID = "connector_name";
113 /** Sleep before autoexit. */
114 private static final int SLEEP_BEFORE_EXIT = 75;
116 /** Preferences: hide ads. */
117 static boolean prefsNoAds = false;
118 /** Hased IMEI. */
119 static String imeiHash = null;
120 /** Preferences: connector specs. */
121 static ConnectorSpec prefsConnectorSpecs = null;
122 /** Save prefsConnectorSpecs.getID() here. */
123 static String prefsConnectorID = null;
124 /** Preferences: show mobile numbers only. */
125 static boolean prefsMobilesOnly;
127 /** List of available {@link ConnectorSpec}s. */
128 private final static ArrayList<ConnectorSpec> CONNECTORS = new ArrayList<ConnectorSpec>();
130 /** Array of md5(prefsSender) for which no ads should be displayed. */
131 private static final String[] NO_AD_HASHS = {
132 "2986b6d93053a53ff13008b3015a77ff", // flx
133 "57a3c7c19329fd84c2252a9b2866dd93", // mirweb
134 "10b7a2712beee096acbc67416d7d71a1", // mo
135 "f6b3b72300e918436b4c4c9fdf909e8c", // joerg s.
136 "4c18f7549b643045f0ff69f61e8f7e72", // frank j.
137 "7684154558d19383552388d9bc92d446", // henning k.
138 "64c7414288e9a9b57a33e034f384ed30", // dominik l.
139 "c479a2e701291c751f0f91426bcaabf3", // bernhard g.
140 "ae7dfedf549f98a349ad8c2068473c6b", // dominik k.-v.
141 "18bc29cd511613552861da6ef51766ce", // niels b.
142 "2985011f56d0049b0f4f0caed3581123", // sven l.
143 "64724033da297a915a89023b11ac2e47", // wilfried m.
144 "cfd8d2efb3eac39705bd62c4dfe5e72d", // achim e.
145 "ca56e7518fdbda832409ef07edd4c273", // michael s.
146 "bed2f068ca8493da4179807d1afdbd83", // axel q.
147 "4c35400c4fa3ffe2aefcf1f9131eb855", // gerhard s.
148 "02158d2a80b1ef9c4d684a4ca808b93d", // camilo s.
149 "1177c6e67f98cdfed6c84d99e85d30de", // daniel p.
150 "3f082dd7e21d5c64f34a69942c474ce7", // andre j.
151 "5383540b2f8c298532f874126b021e73", // marco a.
154 /** Public Dialog ref. */
155 static Dialog dialog = null;
156 /** Dialog String. */
157 static String dialogString = null;
159 /** true if preferences got opened. */
160 static boolean doPreferences = false;
162 /** Dialog: about. */
163 private static final int DIALOG_ABOUT = 0;
164 /** Dialog: updates. */
165 private static final int DIALOG_UPDATE = 2;
166 /** Dialog: captcha. */
167 private static final int DIALOG_CAPTCHA = 3;
168 /** Dialog: post donate. */
169 private static final int DIALOG_POSTDONATE = 4;
170 /** Dialog: custom sender. */
171 private static final int DIALOG_CUSTOMSENDER = 5;
172 /** Dialog: send later: date. */
173 private static final int DIALOG_SENDLATER_DATE = 6;
174 /** Dialog: send later: time. */
175 private static final int DIALOG_SENDLATER_TIME = 7;
176 /** Dialog: pre donate. */
177 private static final int DIALOG_PREDONATE = 8;
179 /** Message for logging. **/
180 static final int MESSAGE_LOG = 0;
181 /** Message for update free sms count. **/
182 static final int MESSAGE_FREECOUNT = 1;
183 /** Message to open settings. */
184 static final int MESSAGE_SETTINGS = 4;
185 /** Message to reset data. */
186 static final int MESSAGE_RESET = 5;
187 /** Message show cpatcha. */
188 static final int MESSAGE_ANTICAPTCHA = 6;
190 /** Intent's extra for errormessages. */
191 static final String EXTRA_ERRORMESSAGE = "de.ub0r.android.intent.extra.ERRORMESSAGE";
193 /** Persistent Message store. */
194 private static String lastMsg = null;
195 /** Persistent Recipient store. */
196 private static String lastTo = null;
198 /** Backup for params. */
199 private static ConnectorCommand lastParams = null;
200 /** User wants to send the message later. */
201 private static boolean wantSendLater = false;
203 /** Helper for API 5. */
204 static HelperAPI5Contacts helperAPI5c = null;
206 /** Text's label. */
207 private TextView textLabel;
209 /** Show extras. */
210 private boolean showExtras = false;
212 /** MessageHandler. */
213 private Handler messageHandler = new Handler() {
215 * {@inheritDoc}
217 @Override
218 public final void handleMessage(final Message msg) {
219 switch (msg.what) {
220 case MESSAGE_LOG: // msg is String or Resource StringID
221 if (msg.obj instanceof String) {
222 WebSMS.this.log((String) msg.obj);
223 } else if (msg.obj instanceof Integer) {
224 WebSMS.this.log(WebSMS.this.getString(((Integer) msg.obj)
225 .intValue()));
226 } else {
227 WebSMS.this.log(msg.obj.toString());
229 return;
230 case MESSAGE_FREECOUNT:
231 WebSMS.this.updateBalance();
232 return;
233 case MESSAGE_SETTINGS:
234 WebSMS.this.startActivity(new Intent(WebSMS.this,
235 Preferences.class));
236 case MESSAGE_RESET:
237 WebSMS.this.reset();
238 return;
239 case MESSAGE_ANTICAPTCHA:
240 WebSMS.this.showDialog(DIALOG_CAPTCHA);
241 return;
242 default:
243 return;
248 /** TextWatcher updating char count on writing. */
249 private TextWatcher textWatcher = new TextWatcher() {
251 * {@inheritDoc}
253 public void afterTextChanged(final Editable s) {
254 int[] l = SmsMessage.calculateLength(s, false);
255 WebSMS.this.textLabel.setText(l[0] + "/" + l[2]);
258 /** Needed dummy. */
259 public void beforeTextChanged(final CharSequence s, final int start,
260 final int count, final int after) {
263 /** Needed dummy. */
264 public void onTextChanged(final CharSequence s, final int start,
265 final int before, final int count) {
270 * {@inheritDoc}
272 @Override
273 public final void onCreate(final Bundle savedInstanceState) {
274 super.onCreate(savedInstanceState);
275 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
277 // save ref to me.
278 me = this;
279 try {
280 WebSMS.helperAPI5c = new HelperAPI5Contacts();
281 if (!helperAPI5c.isAvailable()) {
282 WebSMS.helperAPI5c = null;
284 } catch (VerifyError e) {
285 WebSMS.helperAPI5c = null;
286 Log.d(TAG, "no api5 running", e);
288 // Restore preferences
289 final SharedPreferences p = PreferenceManager
290 .getDefaultSharedPreferences(this);
291 // inflate XML
292 if (p.getBoolean(PREFS_SOFTKEYS, false)) {
293 this.setContentView(R.layout.main_touch);
294 } else {
295 this.setContentView(R.layout.main);
298 this.findViewById(R.id.to).requestFocus();
300 // display changelog?
301 String v0 = p.getString(PREFS_LAST_RUN, "");
302 String v1 = this.getResources().getString(R.string.app_version);
303 if (!v0.equals(v1)) {
304 SharedPreferences.Editor editor = p.edit();
305 editor.putString(PREFS_LAST_RUN, v1);
306 editor.commit();
307 this.showDialog(DIALOG_UPDATE);
310 this.reloadPrefs();
312 lastTo = p.getString(PREFS_TO, "");
313 lastMsg = p.getString(PREFS_TEXT, "");
315 // register Listener
316 ((Button) this.findViewById(R.id.send_)).setOnClickListener(this);
317 ((Button) this.findViewById(R.id.cancel)).setOnClickListener(this);
318 ((Button) this.findViewById(R.id.change_connector))
319 .setOnClickListener(this);
320 ((Button) this.findViewById(R.id.extras)).setOnClickListener(this);
322 this.textLabel = (TextView) this.findViewById(R.id.text_);
323 ((EditText) this.findViewById(R.id.text))
324 .addTextChangedListener(this.textWatcher);
326 ((TextView) this.findViewById(R.id.freecount)).setOnClickListener(this);
328 final MultiAutoCompleteTextView to = (MultiAutoCompleteTextView) this
329 .findViewById(R.id.to);
330 to.setAdapter(new MobilePhoneAdapter(this));
331 to.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
333 Intent intent = this.getIntent();
334 final String action = intent.getAction();
335 if (action != null) {
336 // launched by clicking a sms: link, target number is in URI.
337 final Uri uri = intent.getData();
338 if (uri != null) {
339 final String scheme = uri.getScheme();
340 if (scheme.equals("sms") || scheme.equals("smsto")) {
341 String s = uri.getSchemeSpecificPart();
342 if (s != null) {
343 s = s.trim();
344 if (s.endsWith(",")) {
345 s = s.substring(0, s.length() - 1).trim();
347 // recipient = WebSMS.cleanRecipient(recipient);
348 if (s.indexOf('<') < 0) {
349 // try to fetch recipient's name from phonebook
350 String n = null;
351 if (helperAPI5c != null) {
352 try {
353 n = helperAPI5c.getNameForNumber(this, s);
354 } catch (NoClassDefFoundError e) {
355 helperAPI5c = null;
358 if (helperAPI5c == null) {
359 Cursor c = this
360 .managedQuery(
361 Phones.CONTENT_URI,
362 new String[] {
363 PhonesColumns.NUMBER,
364 PeopleColumns.DISPLAY_NAME },
365 PhonesColumns.NUMBER + " = '"
366 + s + "'", null, null);
367 if (c.moveToFirst()) {
368 n = c
369 .getString(c
370 .getColumnIndex(PeopleColumns.DISPLAY_NAME));
373 if (n != null) {
374 s = n + " <" + s + ">, ";
377 ((EditText) this.findViewById(R.id.to)).setText(s);
378 lastTo = s;
380 final Bundle extras = intent.getExtras();
381 if (extras != null) {
382 s = extras.getCharSequence(Intent.EXTRA_TEXT)
383 .toString();
384 if (s != null) {
385 ((EditText) this.findViewById(R.id.text))
386 .setText(s);
387 lastMsg = s;
389 s = extras.getString(EXTRA_ERRORMESSAGE);
390 if (s != null) {
391 Toast.makeText(this, s, Toast.LENGTH_LONG).show();
394 if (!prefsNoAds) {
395 // do not display any ads for donators
396 // display ads
397 ((AdView) WebSMS.this.findViewById(R.id.ad))
398 .setVisibility(View.VISIBLE);
404 // check default prefix
405 if (!p.getString(PREFS_DEFPREFIX, "").startsWith("+")) {
406 WebSMS.this.log(R.string.log_error_defprefix);
408 if (p.getBoolean(PREFS_AUTOUPDATE, false)) {
409 this.updateFreecount(false);
414 * {@inheritDoc}
416 @Override
417 protected final void onResume() {
418 super.onResume();
419 // set free sms count
420 this.updateBalance();
422 // restart dialog if needed
423 if (dialogString != null) {
424 if (dialog != null) {
425 try {
426 dialog.dismiss();
427 } catch (Exception e) {
428 // nothing to do
431 dialog = ProgressDialog.show(this, null, dialogString, true);
434 // if coming from prefs..
435 if (doPreferences) {
436 this.reloadPrefs();
437 doPreferences = false;
438 final Intent intent = ConnectorCommand.bootstrap()
439 .setToIntent(null);
440 Log.d(TAG, "send broadcast: " + intent.getAction());
441 this.sendBroadcast(intent);
444 this.setButtons();
446 // reload text/recipient from local store
447 final EditText et0 = (EditText) this.findViewById(R.id.text);
448 if (lastMsg != null) {
449 et0.setText(lastMsg);
450 } else {
451 et0.setText("");
453 final EditText et1 = (EditText) this.findViewById(R.id.to);
454 if (lastTo != null) {
455 et1.setText(lastTo);
456 } else {
457 et1.setText("");
460 if (lastTo != null && lastTo.length() > 0) {
461 et0.requestFocus();
462 } else {
463 et1.requestFocus();
466 // query for connectors
467 final Intent i = new Intent(CommandReceiver.ACTION_CONNECTOR_UPDATE);
468 Log.d(TAG, "send broadcast: " + i.getAction());
469 this.sendBroadcast(i);
473 * Update balance.
475 private void updateBalance() {
476 final StringBuilder buf = new StringBuilder();
478 for (ConnectorSpec cs : getConnectors(
479 ConnectorSpec.CAPABILITIES_UPDATE, // .
480 ConnectorSpec.STATUS_ENABLED)) {
481 final String b = cs.getBalance();
482 if (b == null || b.length() == 0) {
483 continue;
485 if (buf.length() > 0) {
486 buf.append(", ");
488 buf.append(cs.getName());
489 buf.append(": ");
490 buf.append(b);
493 TextView tw = (TextView) this.findViewById(R.id.freecount);
494 tw.setText(this.getString(R.string.free_) + " " + buf.toString() + " "
495 + this.getString(R.string.click_for_update));
499 * {@inheritDoc}
501 @Override
502 public final void onPause() {
503 super.onPause();
504 // store input data to persitent stores
505 lastMsg = ((EditText) this.findViewById(R.id.text)).getText()
506 .toString();
507 lastTo = ((EditText) this.findViewById(R.id.to)).getText().toString();
509 // store input data to preferences
510 SharedPreferences.Editor editor = PreferenceManager
511 .getDefaultSharedPreferences(this).edit();
512 // common
513 editor.putString(PREFS_TO, lastTo);
514 editor.putString(PREFS_TEXT, lastMsg);
515 // commit changes
516 editor.commit();
518 this.savePreferences();
522 * Read static vars holding preferences.
524 private void reloadPrefs() {
525 final SharedPreferences p = PreferenceManager
526 .getDefaultSharedPreferences(this);
527 final boolean b = p.getBoolean(PREFS_CHANGE_CONNECTOR_BUTTON, false);
528 final View v = this.findViewById(R.id.change_connector);
529 if (b) {
530 v.setVisibility(View.VISIBLE);
531 } else {
532 v.setVisibility(View.GONE);
535 prefsConnectorID = p.getString(PREFS_CONNECTOR_ID, "");
536 prefsConnectorSpecs = getConnectorByID(prefsConnectorID);
538 prefsMobilesOnly = p.getBoolean(PREFS_MOBILES_ONLY, false);
540 prefsNoAds = false;
541 String hash = md5(p.getString(PREFS_SENDER, ""));
542 for (String h : NO_AD_HASHS) {
543 if (hash.equals(h)) {
544 prefsNoAds = true;
545 break;
548 if (!prefsNoAds && this.getImeiHash() != null) {
549 for (String h : NO_AD_HASHS) {
550 if (imeiHash.equals(h)) {
551 prefsNoAds = true;
552 break;
557 this.setButtons();
561 * Show/hide, enable/disable send buttons.
563 private void setButtons() {
564 final ConnectorSpec[] enabled = getConnectors(
565 ConnectorSpec.CAPABILITIES_SEND, ConnectorSpec.STATUS_ENABLED);
566 final int c = enabled.length;
568 Button btn = (Button) this.findViewById(R.id.send_);
569 // show/hide buttons
570 btn.setEnabled(c > 0);
571 btn.setVisibility(View.VISIBLE);
572 if (c == 1) {
573 prefsConnectorSpecs = enabled[0];
576 if (prefsConnectorSpecs != null) {
577 final short features = ConnectorSpec.SubConnectorSpec.FEATURE_NONE;
578 // FIXME: prefsConnectorSpecs.getFeatures();
579 final boolean sFlashsms = (features & ConnectorSpecs.FEATURE_FLASHSMS) == ConnectorSpecs.FEATURE_FLASHSMS;
580 final boolean sCustomsender = (features & ConnectorSpecs.FEATURE_CUSTOMSENDER) == ConnectorSpecs.FEATURE_CUSTOMSENDER;
581 final boolean sSendLater = (features & ConnectorSpecs.FEATURE_SENDLATER) == ConnectorSpecs.FEATURE_SENDLATER;
582 if (sFlashsms || sCustomsender || sSendLater) {
583 this.findViewById(R.id.extras).setVisibility(View.VISIBLE);
584 } else {
585 this.findViewById(R.id.extras).setVisibility(View.GONE);
587 if (this.showExtras && sFlashsms) {
588 this.findViewById(R.id.flashsms).setVisibility(View.VISIBLE);
589 } else {
590 this.findViewById(R.id.flashsms).setVisibility(View.GONE);
592 if (this.showExtras && sCustomsender) {
593 this.findViewById(R.id.custom_sender).setVisibility(
594 View.VISIBLE);
595 } else {
596 this.findViewById(R.id.custom_sender).setVisibility(View.GONE);
598 if (this.showExtras && sSendLater) {
599 this.findViewById(R.id.send_later).setVisibility(View.VISIBLE);
600 } else {
601 this.findViewById(R.id.send_later).setVisibility(View.GONE);
604 this.setTitle(this.getString(R.string.app_name) + " - "
605 + prefsConnectorSpecs.getName());
610 * Resets persistent store.
612 private void reset() {
613 ((EditText) this.findViewById(R.id.text)).setText("");
614 ((EditText) this.findViewById(R.id.to)).setText("");
615 lastMsg = null;
616 lastTo = null;
617 // save user preferences
618 SharedPreferences.Editor editor = PreferenceManager
619 .getDefaultSharedPreferences(this).edit();
620 editor.putString(PREFS_TO, "");
621 editor.putString(PREFS_TEXT, "");
622 // commit changes
623 editor.commit();
626 /** Save prefs. */
627 final void savePreferences() {
628 if (prefsConnectorSpecs != null) {
629 PreferenceManager.getDefaultSharedPreferences(this).edit()
630 .putString(PREFS_CONNECTOR_ID, prefsConnectorSpecs.getID())
631 .commit();
636 * Run Connector.update().
638 * @param forceUpdate
639 * force update, if false only blank balances will get updated
641 private void updateFreecount(final boolean forceUpdate) {
642 final Intent intent = ConnectorCommand.update().setToIntent(null);
643 Log.d(TAG, "send broadcast: " + intent.getAction());
644 this.sendBroadcast(intent);
648 * {@inheritDoc}
650 public final void onClick(final View v) {
651 switch (v.getId()) {
652 case R.id.freecount:
653 this.updateFreecount(true);
654 break;
655 case R.id.send_:
656 this.send(prefsConnectorSpecs);
657 break;
658 case R.id.cancel:
659 this.reset();
660 break;
661 case R.id.captcha_btn:
662 ConnectorO2.captchaSolve = ((EditText) v.getRootView()
663 .findViewById(R.id.captcha_edt)).getText().toString();
664 synchronized (ConnectorO2.CAPTCHA_SYNC) {
665 ConnectorO2.CAPTCHA_SYNC.notify();
667 this.dismissDialog(DIALOG_CAPTCHA);
668 break;
669 case R.id.change_connector:
670 this.changeConnectorMenu();
671 break;
672 case R.id.extras:
673 this.showExtras = !this.showExtras;
674 this.setButtons();
675 break;
676 default:
677 break;
682 * {@inheritDoc}
684 @Override
685 public final boolean onCreateOptionsMenu(final Menu menu) {
686 MenuInflater inflater = this.getMenuInflater();
687 inflater.inflate(R.menu.menu, menu);
688 return true;
692 * Display "change connector" menu.
694 private void changeConnectorMenu() {
695 AlertDialog.Builder builder = new AlertDialog.Builder(this);
696 builder.setTitle(R.string.change_connector_);
697 final ArrayList<String> items = new ArrayList<String>();
698 for (ConnectorSpec cs : getConnectors(ConnectorSpec.CAPABILITIES_SEND,
699 ConnectorSpec.STATUS_ENABLED)) {
700 items.add(cs.getName());
702 // TODO: add subconnectors
704 builder.setItems(items.toArray(new String[0]),
705 new DialogInterface.OnClickListener() {
706 public void onClick(final DialogInterface d, // .
707 final int item) {
708 prefsConnectorSpecs = getConnectorByName(items
709 .get(item));
710 WebSMS.this.setButtons();
711 // save user preferences
712 PreferenceManager.getDefaultSharedPreferences(
713 WebSMS.this).edit().putString(
714 PREFS_CONNECTOR_ID,
715 prefsConnectorSpecs.getName()).commit();
718 builder.create().show();
722 *{@inheritDoc}
724 @Override
725 public final boolean onOptionsItemSelected(final MenuItem item) {
726 switch (item.getItemId()) {
727 case R.id.item_about: // start about dialog
728 this.showDialog(DIALOG_ABOUT);
729 return true;
730 case R.id.item_settings: // start settings activity
731 this.startActivity(new Intent(this, Preferences.class));
732 return true;
733 case R.id.item_donate:
734 this.showDialog(DIALOG_PREDONATE);
735 return true;
736 case R.id.item_more:
737 try {
738 this.startActivity(new Intent(Intent.ACTION_VIEW, Uri
739 .parse("market://search?q=pub:\"Felix Bechstein\"")));
740 } catch (ActivityNotFoundException e) {
741 Log.e(TAG, "no market", e);
743 return true;
744 case R.id.item_connector:
745 this.changeConnectorMenu();
746 return true;
747 default:
748 return false;
753 * {@inheritDoc}
755 @Override
756 protected final Dialog onCreateDialog(final int id) {
757 Dialog d;
758 AlertDialog.Builder builder;
759 switch (id) {
760 case DIALOG_PREDONATE:
761 builder = new AlertDialog.Builder(this);
762 builder.setTitle(R.string.donate_);
763 builder.setMessage(R.string.predonate);
764 builder.setPositiveButton(R.string.donate_,
765 new DialogInterface.OnClickListener() {
766 public void onClick(final DialogInterface dialog,
767 final int which) {
768 try {
769 WebSMS.this
770 .startActivity(new Intent(
771 Intent.ACTION_VIEW,
773 .parse(WebSMS.this
774 .getString(R.string.donate_url))));
775 } catch (ActivityNotFoundException e) {
776 Log.e(TAG, "no browser", e);
777 } finally {
778 WebSMS.this.showDialog(DIALOG_POSTDONATE);
782 builder.setNegativeButton(android.R.string.cancel, null);
783 return builder.create();
784 case DIALOG_POSTDONATE:
785 builder = new AlertDialog.Builder(this);
786 builder.setTitle(R.string.remove_ads_);
787 builder.setMessage(R.string.postdonate);
788 builder.setPositiveButton(R.string.send_,
789 new DialogInterface.OnClickListener() {
790 public void onClick(final DialogInterface dialog,
791 final int which) {
792 final Intent in = new Intent(Intent.ACTION_SEND);
794 .putExtra(
795 Intent.EXTRA_EMAIL,
796 new String[] {
797 WebSMS.this
798 .getString(R.string.donate_mail),
799 "" }); // FIXME: "" is a k9
800 // hack.
801 in.putExtra(Intent.EXTRA_TEXT, WebSMS.this
802 .getImeiHash());
804 .putExtra(
805 Intent.EXTRA_SUBJECT,
806 WebSMS.this
807 .getString(R.string.app_name)
808 + " "
809 + WebSMS.this
810 .getString(R.string.donate_subject));
811 in.setType("text/plain");
812 WebSMS.this.startActivity(in);
815 builder.setNegativeButton(android.R.string.cancel, null);
816 return builder.create();
817 case DIALOG_ABOUT:
818 d = new Dialog(this);
819 d.setContentView(R.layout.about);
820 d.setTitle(this.getString(R.string.about_) + " v"
821 + this.getString(R.string.app_version));
822 StringBuffer authors = new StringBuffer();
823 for (ConnectorSpec cs : getConnectors(
824 ConnectorSpec.CAPABILITIES_NONE,
825 ConnectorSpec.STATUS_INACTIVE)) {
826 final String a = cs.getAuthor();
827 if (a != null && a.length() > 0) {
828 authors.append(cs.getName());
829 authors.append(":\t");
830 authors.append(a);
831 authors.append("\n");
834 ((TextView) d.findViewById(R.id.author_connectors)).setText(authors
835 .toString().trim());
836 return d;
837 case DIALOG_UPDATE:
838 builder = new AlertDialog.Builder(this);
839 builder.setTitle(R.string.changelog_);
840 final String[] changes = this.getResources().getStringArray(
841 R.array.updates);
842 final StringBuilder buf = new StringBuilder(changes[0]);
843 for (int i = 1; i < changes.length; i++) {
844 buf.append("\n\n");
845 buf.append(changes[i]);
847 builder.setIcon(android.R.drawable.ic_menu_info_details);
848 builder.setMessage(buf.toString());
849 builder.setCancelable(true);
850 builder.setPositiveButton(android.R.string.ok, null);
851 return builder.create();
852 case DIALOG_CAPTCHA:
853 d = new Dialog(this);
854 d.setTitle(R.string.captcha_);
855 d.setContentView(R.layout.captcha);
856 d.setCancelable(false);
857 ((Button) d.findViewById(R.id.captcha_btn))
858 .setOnClickListener(this);
859 return d;
860 case DIALOG_CUSTOMSENDER:
861 builder = new AlertDialog.Builder(this);
862 builder.setTitle(R.string.custom_sender);
863 builder.setCancelable(true);
864 final EditText et = new EditText(this);
865 builder.setView(et);
866 builder.setPositiveButton(android.R.string.ok,
867 new DialogInterface.OnClickListener() {
868 public void onClick(final DialogInterface dialog,
869 final int id) {
870 WebSMS.lastParams.setCustomSender(et.getText()
871 .toString());
872 if (WebSMS.wantSendLater) {
873 WebSMS.this
874 .showDialog(WebSMS.DIALOG_SENDLATER_DATE);
875 } else {
876 WebSMS.this.send(WebSMS.prefsConnectorSpecs,
877 WebSMS.lastParams);
881 builder.setNegativeButton(android.R.string.cancel, null);
882 return builder.create();
883 case DIALOG_SENDLATER_DATE:
884 Calendar c = Calendar.getInstance();
885 return new DatePickerDialog(this, this, c.get(Calendar.YEAR), c
886 .get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
887 case DIALOG_SENDLATER_TIME:
888 c = Calendar.getInstance();
889 return new MyTimePickerDialog(this, this, c
890 .get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true);
891 default:
892 return null;
897 * {@inheritDoc}
899 @Override
900 protected final void onPrepareDialog(final int id, final Dialog dlg) {
901 switch (id) {
902 case DIALOG_CAPTCHA:
903 if (ConnectorO2.captcha != null) {
904 ((ImageView) dlg.findViewById(R.id.captcha_img))
905 .setImageDrawable(ConnectorO2.captcha);
906 ConnectorO2.captcha = null;
908 break;
909 default:
910 break;
915 * Log text.
917 * @param text
918 * text as resID
920 public final void log(final int text) {
921 this.log(this.getString(text));
925 * Log text.
927 * @param text
928 * text
930 public final void log(final String text) {
931 try {
932 Toast.makeText(this.getApplicationContext(), text,
933 Toast.LENGTH_LONG).show();
934 } catch (RuntimeException e) {
935 Log.e(TAG, null, e);
940 * Send Text.
942 * @param connector
943 * which connector should be used.
944 * @param command
945 * {@link ConnectorCommand} to push to connector
947 private void send(final ConnectorSpec connector,
948 final ConnectorCommand command) {
949 try {
950 final Intent intent = command.setToIntent(null);
951 prefsConnectorSpecs.setToIntent(intent);
952 connector.addStatus(ConnectorSpec.STATUS_SENDING);
953 Log.d(TAG, "send broadcast: " + intent.getAction());
954 this.sendBroadcast(intent);
955 } catch (Exception e) {
956 Log.e(TAG, null, e);
957 } finally {
958 this.reset();
959 if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
960 PREFS_AUTOEXIT, false)) {
961 try {
962 Thread.sleep(SLEEP_BEFORE_EXIT);
963 } catch (InterruptedException e) {
964 Log.e(TAG, null, e);
966 this.finish();
972 * Send Text.
974 * @param connector
975 * which connector should be used.
977 private void send(final ConnectorSpec connector) {
978 // fetch text/recipient
979 final String to = ((EditText) this.findViewById(R.id.to)).getText()
980 .toString();
981 final String text = ((EditText) this.findViewById(R.id.text)).getText()
982 .toString();
983 if (to.length() == 0 || text.length() == 0) {
984 return;
987 if (!prefsNoAds) {
988 // do not display any ads for donators
989 // display ads
990 ((AdView) WebSMS.this.findViewById(R.id.ad))
991 .setVisibility(View.VISIBLE);
994 CheckBox v = (CheckBox) this.findViewById(R.id.flashsms);
995 final boolean flashSMS = (v.getVisibility() == View.VISIBLE)
996 && v.isEnabled() && v.isChecked();
997 v = (CheckBox) this.findViewById(R.id.send_later);
998 if ((v.getVisibility() == View.VISIBLE) && v.isEnabled()
999 && v.isChecked()) {
1000 wantSendLater = true;
1002 SharedPreferences p = PreferenceManager
1003 .getDefaultSharedPreferences(this);
1004 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
1005 final String defSender = p.getString(PREFS_SENDER, "");
1007 final ConnectorCommand command = ConnectorCommand.send(defPrefix,
1008 defSender, to.split(","), text, flashSMS);
1010 v = (CheckBox) this.findViewById(R.id.custom_sender);
1011 if ((v.getVisibility() == View.VISIBLE) && v.isEnabled()
1012 && v.isChecked()) {
1013 lastParams = command;
1014 this.showDialog(DIALOG_CUSTOMSENDER);
1015 } else {
1016 if (wantSendLater) {
1017 lastParams = command;
1018 this.showDialog(DIALOG_SENDLATER_DATE);
1019 } else {
1020 this.send(connector, command);
1026 * A Date was set.
1028 * @param view
1029 * DatePicker View
1030 * @param year
1031 * year set
1032 * @param monthOfYear
1033 * month set
1034 * @param dayOfMonth
1035 * day set
1037 public final void onDateSet(final DatePicker view, final int year,
1038 final int monthOfYear, final int dayOfMonth) {
1039 final Calendar c = Calendar.getInstance();
1040 if (lastParams != null) {
1041 final long l = lastParams.getSendLater();
1042 if (l > 0) {
1043 c.setTimeInMillis(l);
1046 c.set(Calendar.YEAR, year);
1047 c.set(Calendar.MONTH, monthOfYear);
1048 c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
1049 lastParams.setSendLater(c.getTimeInMillis());
1051 this.showDialog(DIALOG_SENDLATER_TIME);
1055 * A Time was set.
1057 * @param view
1058 * TimePicker View
1059 * @param hour
1060 * hour set
1061 * @param minutes
1062 * minutes set
1064 public final void onTimeSet(final TimePicker view, final int hour,
1065 final int minutes) {
1066 if (prefsConnectorSpecs.getName().equals("WebSMS.o2") // FIXME
1067 && minutes % 15 != 0) {
1068 Toast.makeText(this, R.string.log_error_o2_sendlater,
1069 Toast.LENGTH_LONG).show();
1070 return;
1073 final Calendar c = Calendar.getInstance();
1074 if (lastParams != null) {
1075 c.setTimeInMillis(lastParams.getSendLater());
1077 c.set(Calendar.HOUR_OF_DAY, hour);
1078 c.set(Calendar.MINUTE, minutes);
1079 lastParams.setSendLater(c.getTimeInMillis());
1081 this.send(WebSMS.prefsConnectorSpecs, WebSMS.lastParams);
1085 * Send WebSMS a Message.
1087 * @param messageType
1088 * type
1089 * @param data
1090 * data
1092 public static final void pushMessage(final int messageType,
1093 final Object data) {
1094 if (WebSMS.me == null) {
1095 return;
1097 Message.obtain(WebSMS.me.messageHandler, messageType, data)
1098 .sendToTarget();
1102 * Calc MD5 Hash from String.
1104 * @param s
1105 * input
1106 * @return hash
1108 static String md5(final String s) {
1109 try {
1110 // Create MD5 Hash
1111 MessageDigest digest = java.security.MessageDigest
1112 .getInstance("MD5");
1113 digest.update(s.getBytes());
1114 byte[] messageDigest = digest.digest();
1115 // Create Hex String
1116 StringBuilder hexString = new StringBuilder(32);
1117 int b;
1118 for (int i = 0; i < messageDigest.length; i++) {
1119 b = 0xFF & messageDigest[i];
1120 if (b < 0x10) {
1121 hexString.append('0' + Integer.toHexString(b));
1122 } else {
1123 hexString.append(Integer.toHexString(b));
1126 return hexString.toString();
1127 } catch (NoSuchAlgorithmException e) {
1128 Log.e(TAG, null, e);
1130 return "";
1134 * Get MD5 hash of the IMEI (device id).
1136 * @return MD5 hash of IMEI
1138 private String getImeiHash() {
1139 if (imeiHash == null) {
1140 // get imei
1141 TelephonyManager mTelephonyMgr = (TelephonyManager) this
1142 .getSystemService(TELEPHONY_SERVICE);
1143 final String did = mTelephonyMgr.getDeviceId();
1144 if (did != null) {
1145 imeiHash = md5(did);
1148 return imeiHash;
1152 * Add or update a {@link ConnectorSpec}.
1154 * @param connector
1155 * connector
1157 public static final void addConnector(final ConnectorSpec connector) {
1158 synchronized (CONNECTORS) {
1159 final ConnectorSpec c = getConnectorByID(connector.getID());
1160 if (c != null) {
1161 c.update(connector);
1162 } else {
1163 CONNECTORS.add(connector);
1164 if (prefsConnectorSpecs == null
1165 && prefsConnectorID.equals(connector.getID())) {
1166 prefsConnectorSpecs = connector;
1167 me.setButtons();
1170 if (connector.getBalance() != null) {
1171 me.updateBalance();
1177 * Get {@link ConnectorSpec} by ID.
1179 * @param id
1180 * ID
1181 * @return {@link ConnectorSpec}
1183 public static final ConnectorSpec getConnectorByID(final String id) {
1184 synchronized (CONNECTORS) {
1185 final int l = CONNECTORS.size();
1186 for (int i = 0; i < l; i++) {
1187 final ConnectorSpec c = CONNECTORS.get(i);
1188 if (id.equals(c.getID())) {
1189 return c;
1193 return null;
1197 * Get {@link ConnectorSpec} by name.
1199 * @param name
1200 * name
1201 * @return {@link ConnectorSpec}
1203 public static final ConnectorSpec getConnectorByName(final String name) {
1204 synchronized (CONNECTORS) {
1205 final int l = CONNECTORS.size();
1206 for (int i = 0; i < l; i++) {
1207 final ConnectorSpec c = CONNECTORS.get(i);
1208 if (name.equals(c.getName())) {
1209 return c;
1213 return null;
1217 * Get {@link ConnectorSpec}s by capabilities and/or status.
1219 * @param capabilities
1220 * capabilities needed
1221 * @param status
1222 * status required
1223 * @return {@link ConnectorSpec}s
1225 public static final ConnectorSpec[] getConnectors(final short capabilities,
1226 final short status) {
1227 synchronized (CONNECTORS) {
1228 final ArrayList<ConnectorSpec> ret = new ArrayList<ConnectorSpec>(
1229 CONNECTORS.size());
1230 final int l = CONNECTORS.size();
1231 for (int i = 0; i < l; i++) {
1232 final ConnectorSpec c = CONNECTORS.get(i);
1233 if (c.hasCapabilities(capabilities) && c.hasStatus(status)) {
1234 ret.add(c);
1237 return ret.toArray(new ConnectorSpec[0]);