switching connectors is working
[andGMXsms.git] / src / de / ub0r / android / andGMXsms / WebSMS.java
blob45666ca7a9344cf41a42f3487bbc152e9ec8b7fd
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.ConnectorCommand;
70 import de.ub0r.android.websms.connector.ConnectorSpec;
71 import de.ub0r.android.websms.connector.Constants;
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 /** Preferences: show mobile numbers only. */
123 static boolean prefsMobilesOnly;
125 /** List of available {@link ConnectorSpec}s. */
126 private final static ArrayList<ConnectorSpec> CONNECTORS = new ArrayList<ConnectorSpec>();
128 /** Array of md5(prefsSender) for which no ads should be displayed. */
129 private static final String[] NO_AD_HASHS = {
130 "2986b6d93053a53ff13008b3015a77ff", // flx
131 "57a3c7c19329fd84c2252a9b2866dd93", // mirweb
132 "10b7a2712beee096acbc67416d7d71a1", // mo
133 "f6b3b72300e918436b4c4c9fdf909e8c", // joerg s.
134 "4c18f7549b643045f0ff69f61e8f7e72", // frank j.
135 "7684154558d19383552388d9bc92d446", // henning k.
136 "64c7414288e9a9b57a33e034f384ed30", // dominik l.
137 "c479a2e701291c751f0f91426bcaabf3", // bernhard g.
138 "ae7dfedf549f98a349ad8c2068473c6b", // dominik k.-v.
139 "18bc29cd511613552861da6ef51766ce", // niels b.
140 "2985011f56d0049b0f4f0caed3581123", // sven l.
141 "64724033da297a915a89023b11ac2e47", // wilfried m.
142 "cfd8d2efb3eac39705bd62c4dfe5e72d", // achim e.
143 "ca56e7518fdbda832409ef07edd4c273", // michael s.
144 "bed2f068ca8493da4179807d1afdbd83", // axel q.
145 "4c35400c4fa3ffe2aefcf1f9131eb855", // gerhard s.
146 "02158d2a80b1ef9c4d684a4ca808b93d", // camilo s.
147 "1177c6e67f98cdfed6c84d99e85d30de", // daniel p.
148 "3f082dd7e21d5c64f34a69942c474ce7", // andre j.
149 "5383540b2f8c298532f874126b021e73", // marco a.
152 /** Public Dialog ref. */
153 static Dialog dialog = null;
154 /** Dialog String. */
155 static String dialogString = null;
157 /** true if preferences got opened. */
158 static boolean doPreferences = false;
160 /** Dialog: about. */
161 private static final int DIALOG_ABOUT = 0;
162 /** Dialog: updates. */
163 private static final int DIALOG_UPDATE = 2;
164 /** Dialog: captcha. */
165 private static final int DIALOG_CAPTCHA = 3;
166 /** Dialog: post donate. */
167 private static final int DIALOG_POSTDONATE = 4;
168 /** Dialog: custom sender. */
169 private static final int DIALOG_CUSTOMSENDER = 5;
170 /** Dialog: send later: date. */
171 private static final int DIALOG_SENDLATER_DATE = 6;
172 /** Dialog: send later: time. */
173 private static final int DIALOG_SENDLATER_TIME = 7;
174 /** Dialog: pre donate. */
175 private static final int DIALOG_PREDONATE = 8;
177 /** Message for logging. **/
178 static final int MESSAGE_LOG = 0;
179 /** Message for update free sms count. **/
180 static final int MESSAGE_FREECOUNT = 1;
181 /** Message to open settings. */
182 static final int MESSAGE_SETTINGS = 4;
183 /** Message to reset data. */
184 static final int MESSAGE_RESET = 5;
185 /** Message show cpatcha. */
186 static final int MESSAGE_ANTICAPTCHA = 6;
188 /** Intent's extra for errormessages. */
189 static final String EXTRA_ERRORMESSAGE = "de.ub0r.android.intent.extra.ERRORMESSAGE";
191 /** Persistent Message store. */
192 private static String lastMsg = null;
193 /** Persistent Recipient store. */
194 private static String lastTo = null;
196 /** Backup for params. */
197 private static ConnectorCommand lastParams = null;
198 /** User wants to send the message later. */
199 private static boolean wantSendLater = false;
201 /** Helper for API 5. */
202 static HelperAPI5Contacts helperAPI5c = null;
204 /** Text's label. */
205 private TextView textLabel;
207 /** Show extras. */
208 private boolean showExtras = false;
210 /** MessageHandler. */
211 private Handler messageHandler = new Handler() {
213 * {@inheritDoc}
215 @Override
216 public final void handleMessage(final Message msg) {
217 switch (msg.what) {
218 case MESSAGE_LOG: // msg is String or Resource StringID
219 if (msg.obj instanceof String) {
220 WebSMS.this.log((String) msg.obj);
221 } else if (msg.obj instanceof Integer) {
222 WebSMS.this.log(WebSMS.this.getString(((Integer) msg.obj)
223 .intValue()));
224 } else {
225 WebSMS.this.log(msg.obj.toString());
227 return;
228 case MESSAGE_FREECOUNT:
229 WebSMS.this.updateBalance();
230 return;
231 case MESSAGE_SETTINGS:
232 WebSMS.this.startActivity(new Intent(WebSMS.this,
233 Preferences.class));
234 case MESSAGE_RESET:
235 WebSMS.this.reset();
236 return;
237 case MESSAGE_ANTICAPTCHA:
238 WebSMS.this.showDialog(DIALOG_CAPTCHA);
239 return;
240 default:
241 return;
246 /** TextWatcher updating char count on writing. */
247 private TextWatcher textWatcher = new TextWatcher() {
249 * {@inheritDoc}
251 public void afterTextChanged(final Editable s) {
252 int[] l = SmsMessage.calculateLength(s, false);
253 WebSMS.this.textLabel.setText(l[0] + "/" + l[2]);
256 /** Needed dummy. */
257 public void beforeTextChanged(final CharSequence s, final int start,
258 final int count, final int after) {
261 /** Needed dummy. */
262 public void onTextChanged(final CharSequence s, final int start,
263 final int before, final int count) {
268 * {@inheritDoc}
270 @Override
271 public final void onCreate(final Bundle savedInstanceState) {
272 super.onCreate(savedInstanceState);
273 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
275 // save ref to me.
276 me = this;
277 try {
278 WebSMS.helperAPI5c = new HelperAPI5Contacts();
279 if (!helperAPI5c.isAvailable()) {
280 WebSMS.helperAPI5c = null;
282 } catch (VerifyError e) {
283 WebSMS.helperAPI5c = null;
284 Log.d(TAG, "no api5 running", e);
286 // Restore preferences
287 final SharedPreferences p = PreferenceManager
288 .getDefaultSharedPreferences(this);
289 // inflate XML
290 if (p.getBoolean(PREFS_SOFTKEYS, false)) {
291 this.setContentView(R.layout.main_touch);
292 } else {
293 this.setContentView(R.layout.main);
296 this.findViewById(R.id.to).requestFocus();
298 // display changelog?
299 String v0 = p.getString(PREFS_LAST_RUN, "");
300 String v1 = this.getResources().getString(R.string.app_version);
301 if (!v0.equals(v1)) {
302 SharedPreferences.Editor editor = p.edit();
303 editor.putString(PREFS_LAST_RUN, v1);
304 editor.commit();
305 this.showDialog(DIALOG_UPDATE);
308 this.reloadPrefs();
310 lastTo = p.getString(PREFS_TO, "");
311 lastMsg = p.getString(PREFS_TEXT, "");
313 // register Listener
314 ((Button) this.findViewById(R.id.send_)).setOnClickListener(this);
315 ((Button) this.findViewById(R.id.cancel)).setOnClickListener(this);
316 ((Button) this.findViewById(R.id.change_connector))
317 .setOnClickListener(this);
318 ((Button) this.findViewById(R.id.extras)).setOnClickListener(this);
320 this.textLabel = (TextView) this.findViewById(R.id.text_);
321 ((EditText) this.findViewById(R.id.text))
322 .addTextChangedListener(this.textWatcher);
324 ((TextView) this.findViewById(R.id.freecount)).setOnClickListener(this);
326 final MultiAutoCompleteTextView to = (MultiAutoCompleteTextView) this
327 .findViewById(R.id.to);
328 to.setAdapter(new MobilePhoneAdapter(this));
329 to.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
331 Intent intent = this.getIntent();
332 final String action = intent.getAction();
333 if (action != null) {
334 // launched by clicking a sms: link, target number is in URI.
335 final Uri uri = intent.getData();
336 if (uri != null) {
337 final String scheme = uri.getScheme();
338 if (scheme.equals("sms") || scheme.equals("smsto")) {
339 String s = uri.getSchemeSpecificPart();
340 if (s != null) {
341 s = s.trim();
342 if (s.endsWith(",")) {
343 s = s.substring(0, s.length() - 1).trim();
345 // recipient = WebSMS.cleanRecipient(recipient);
346 if (s.indexOf('<') < 0) {
347 // try to fetch recipient's name from phonebook
348 String n = null;
349 if (helperAPI5c != null) {
350 try {
351 n = helperAPI5c.getNameForNumber(this, s);
352 } catch (NoClassDefFoundError e) {
353 helperAPI5c = null;
356 if (helperAPI5c == null) {
357 Cursor c = this
358 .managedQuery(
359 Phones.CONTENT_URI,
360 new String[] {
361 PhonesColumns.NUMBER,
362 PeopleColumns.DISPLAY_NAME },
363 PhonesColumns.NUMBER + " = '"
364 + s + "'", null, null);
365 if (c.moveToFirst()) {
366 n = c
367 .getString(c
368 .getColumnIndex(PeopleColumns.DISPLAY_NAME));
371 if (n != null) {
372 s = n + " <" + s + ">, ";
375 ((EditText) this.findViewById(R.id.to)).setText(s);
376 lastTo = s;
378 final Bundle extras = intent.getExtras();
379 if (extras != null) {
380 s = extras.getCharSequence(Intent.EXTRA_TEXT)
381 .toString();
382 if (s != null) {
383 ((EditText) this.findViewById(R.id.text))
384 .setText(s);
385 lastMsg = s;
387 s = extras.getString(EXTRA_ERRORMESSAGE);
388 if (s != null) {
389 Toast.makeText(this, s, Toast.LENGTH_LONG).show();
392 if (!prefsNoAds) {
393 // do not display any ads for donators
394 // display ads
395 ((AdView) WebSMS.this.findViewById(R.id.ad))
396 .setVisibility(View.VISIBLE);
402 // check default prefix
403 if (!p.getString(PREFS_DEFPREFIX, "").startsWith("+")) {
404 WebSMS.this.log(R.string.log_error_defprefix);
406 if (p.getBoolean(PREFS_AUTOUPDATE, false)) {
407 this.updateFreecount(false);
412 * {@inheritDoc}
414 @Override
415 protected final void onResume() {
416 super.onResume();
417 // set free sms count
418 this.updateBalance();
420 // restart dialog if needed
421 if (dialogString != null) {
422 if (dialog != null) {
423 try {
424 dialog.dismiss();
425 } catch (Exception e) {
426 // nothing to do
429 dialog = ProgressDialog.show(this, null, dialogString, true);
432 // if coming from prefs..
433 if (doPreferences) {
434 this.reloadPrefs();
435 doPreferences = false;
436 final Intent intent = ConnectorCommand.bootstrap()
437 .setToIntent(null);
438 Log.d(TAG, "send broadcast: " + intent.getAction());
439 this.sendBroadcast(intent);
442 this.setButtons();
444 // reload text/recipient from local store
445 final EditText et0 = (EditText) this.findViewById(R.id.text);
446 if (lastMsg != null) {
447 et0.setText(lastMsg);
448 } else {
449 et0.setText("");
451 final EditText et1 = (EditText) this.findViewById(R.id.to);
452 if (lastTo != null) {
453 et1.setText(lastTo);
454 } else {
455 et1.setText("");
458 if (lastTo != null && lastTo.length() > 0) {
459 et0.requestFocus();
460 } else {
461 et1.requestFocus();
464 // query for connectors
465 final Intent i = new Intent(Constants.ACTION_CONNECTOR_UPDATE);
466 Log.d(TAG, "send broadcast: " + i.getAction());
467 this.sendBroadcast(i);
471 * Update balance.
473 final void updateBalance() {
474 final StringBuilder buf = new StringBuilder();
476 for (ConnectorSpec cs : getConnectors(
477 ConnectorSpec.CAPABILITIES_UPDATE, // .
478 ConnectorSpec.STATUS_ENABLED)) {
479 final String b = cs.getBalance();
480 if (b == null || b.length() == 0) {
481 continue;
483 if (buf.length() > 0) {
484 buf.append(", ");
486 buf.append(cs.getName());
487 buf.append(" ");
488 buf.append(b);
491 TextView tw = (TextView) this.findViewById(R.id.freecount);
492 tw.setText(this.getString(R.string.free_) + " " + buf.toString() + " "
493 + this.getString(R.string.click_for_update));
497 * {@inheritDoc}
499 @Override
500 public final void onPause() {
501 super.onPause();
502 // store input data to persitent stores
503 lastMsg = ((EditText) this.findViewById(R.id.text)).getText()
504 .toString();
505 lastTo = ((EditText) this.findViewById(R.id.to)).getText().toString();
507 // store input data to preferences
508 SharedPreferences.Editor editor = PreferenceManager
509 .getDefaultSharedPreferences(this).edit();
510 // common
511 editor.putString(PREFS_TO, lastTo);
512 editor.putString(PREFS_TEXT, lastMsg);
513 // commit changes
514 editor.commit();
516 this.savePreferences();
520 * Read static vars holding preferences.
522 private void reloadPrefs() {
523 final SharedPreferences p = PreferenceManager
524 .getDefaultSharedPreferences(this);
525 final boolean b = p.getBoolean(PREFS_CHANGE_CONNECTOR_BUTTON, false);
526 final View v = this.findViewById(R.id.change_connector);
527 if (b) {
528 v.setVisibility(View.VISIBLE);
529 } else {
530 v.setVisibility(View.GONE);
533 // FIXME: this is null at startup!
534 prefsConnectorSpecs = getConnectorByID(p.getString(PREFS_CONNECTOR_ID,
535 ""));
537 prefsMobilesOnly = p.getBoolean(PREFS_MOBILES_ONLY, false);
539 prefsNoAds = false;
540 String hash = md5(p.getString(PREFS_SENDER, ""));
541 for (String h : NO_AD_HASHS) {
542 if (hash.equals(h)) {
543 prefsNoAds = true;
544 break;
547 if (!prefsNoAds && this.getImeiHash() != null) {
548 for (String h : NO_AD_HASHS) {
549 if (imeiHash.equals(h)) {
550 prefsNoAds = true;
551 break;
556 this.setButtons();
560 * Show/hide, enable/disable send buttons.
562 private void setButtons() {
563 final ConnectorSpec[] enabled = getConnectors(
564 ConnectorSpec.CAPABILITIES_SEND, ConnectorSpec.STATUS_ENABLED);
565 final int c = enabled.length;
567 Button btn = (Button) this.findViewById(R.id.send_);
568 // show/hide buttons
569 btn.setEnabled(c > 0);
570 btn.setVisibility(View.VISIBLE);
571 if (c == 1) {
572 prefsConnectorSpecs = enabled[0];
575 if (prefsConnectorSpecs != null) {
576 final short features = ConnectorSpec.SubConnectorSpec.FEATURE_NONE;
577 // FIXME: prefsConnectorSpecs.getFeatures();
578 final boolean sFlashsms = (features & ConnectorSpecs.FEATURE_FLASHSMS) == ConnectorSpecs.FEATURE_FLASHSMS;
579 final boolean sCustomsender = (features & ConnectorSpecs.FEATURE_CUSTOMSENDER) == ConnectorSpecs.FEATURE_CUSTOMSENDER;
580 final boolean sSendLater = (features & ConnectorSpecs.FEATURE_SENDLATER) == ConnectorSpecs.FEATURE_SENDLATER;
581 if (sFlashsms || sCustomsender || sSendLater) {
582 this.findViewById(R.id.extras).setVisibility(View.VISIBLE);
583 } else {
584 this.findViewById(R.id.extras).setVisibility(View.GONE);
586 if (this.showExtras && sFlashsms) {
587 this.findViewById(R.id.flashsms).setVisibility(View.VISIBLE);
588 } else {
589 this.findViewById(R.id.flashsms).setVisibility(View.GONE);
591 if (this.showExtras && sCustomsender) {
592 this.findViewById(R.id.custom_sender).setVisibility(
593 View.VISIBLE);
594 } else {
595 this.findViewById(R.id.custom_sender).setVisibility(View.GONE);
597 if (this.showExtras && sSendLater) {
598 this.findViewById(R.id.send_later).setVisibility(View.VISIBLE);
599 } else {
600 this.findViewById(R.id.send_later).setVisibility(View.GONE);
603 this.setTitle(this.getString(R.string.app_name) + " - "
604 + prefsConnectorSpecs.getName());
609 * Resets persistent store.
611 private void reset() {
612 ((EditText) this.findViewById(R.id.text)).setText("");
613 ((EditText) this.findViewById(R.id.to)).setText("");
614 lastMsg = null;
615 lastTo = null;
616 // save user preferences
617 SharedPreferences.Editor editor = PreferenceManager
618 .getDefaultSharedPreferences(this).edit();
619 editor.putString(PREFS_TO, "");
620 editor.putString(PREFS_TEXT, "");
621 // commit changes
622 editor.commit();
625 /** Save prefs. */
626 final void savePreferences() {
627 if (prefsConnectorSpecs != null) {
628 PreferenceManager.getDefaultSharedPreferences(this).edit()
629 .putString(PREFS_CONNECTOR_ID,
630 prefsConnectorSpecs.getName()).commit();
635 * Run Connector.update().
637 * @param forceUpdate
638 * force update, if false only blank balances will get updated
640 private void updateFreecount(final boolean forceUpdate) {
641 final Intent intent = ConnectorCommand.bootstrap().setToIntent(null);
642 Log.d(TAG, "send broadcast: " + intent.getAction());
643 this.sendBroadcast(intent);
647 * {@inheritDoc}
649 public final void onClick(final View v) {
650 switch (v.getId()) {
651 case R.id.freecount:
652 this.updateFreecount(true);
653 break;
654 case R.id.send_:
655 this.send(prefsConnectorSpecs);
656 break;
657 case R.id.cancel:
658 this.reset();
659 break;
660 case R.id.captcha_btn:
661 ConnectorO2.captchaSolve = ((EditText) v.getRootView()
662 .findViewById(R.id.captcha_edt)).getText().toString();
663 synchronized (ConnectorO2.CAPTCHA_SYNC) {
664 ConnectorO2.CAPTCHA_SYNC.notify();
666 this.dismissDialog(DIALOG_CAPTCHA);
667 break;
668 case R.id.change_connector:
669 this.changeConnectorMenu();
670 break;
671 case R.id.extras:
672 this.showExtras = !this.showExtras;
673 this.setButtons();
674 break;
675 default:
676 break;
681 * {@inheritDoc}
683 @Override
684 public final boolean onCreateOptionsMenu(final Menu menu) {
685 MenuInflater inflater = this.getMenuInflater();
686 inflater.inflate(R.menu.menu, menu);
687 return true;
691 * Display "change connector" menu.
693 private void changeConnectorMenu() {
694 AlertDialog.Builder builder = new AlertDialog.Builder(this);
695 builder.setTitle(R.string.change_connector_);
696 final ArrayList<String> items = new ArrayList<String>();
697 for (ConnectorSpec cs : getConnectors(ConnectorSpec.CAPABILITIES_SEND,
698 ConnectorSpec.STATUS_ENABLED)) {
699 items.add(cs.getName());
701 // TODO: add subconnectors
703 builder.setItems(items.toArray(new String[0]),
704 new DialogInterface.OnClickListener() {
705 public void onClick(final DialogInterface d, // .
706 final int item) {
707 prefsConnectorSpecs = getConnectorByName(items
708 .get(item));
709 WebSMS.this.setButtons();
710 // save user preferences
711 PreferenceManager.getDefaultSharedPreferences(
712 WebSMS.this).edit().putString(
713 PREFS_CONNECTOR_ID,
714 prefsConnectorSpecs.getName()).commit();
717 builder.create().show();
721 *{@inheritDoc}
723 @Override
724 public final boolean onOptionsItemSelected(final MenuItem item) {
725 switch (item.getItemId()) {
726 case R.id.item_about: // start about dialog
727 this.showDialog(DIALOG_ABOUT);
728 return true;
729 case R.id.item_settings: // start settings activity
730 this.startActivity(new Intent(this, Preferences.class));
731 return true;
732 case R.id.item_donate:
733 this.showDialog(DIALOG_PREDONATE);
734 return true;
735 case R.id.item_more:
736 try {
737 this.startActivity(new Intent(Intent.ACTION_VIEW, Uri
738 .parse("market://search?q=pub:\"Felix Bechstein\"")));
739 } catch (ActivityNotFoundException e) {
740 Log.e(TAG, "no market", e);
742 return true;
743 case R.id.item_connector:
744 this.changeConnectorMenu();
745 return true;
746 default:
747 return false;
752 * {@inheritDoc}
754 @Override
755 protected final Dialog onCreateDialog(final int id) {
756 Dialog d;
757 AlertDialog.Builder builder;
758 switch (id) {
759 case DIALOG_PREDONATE:
760 builder = new AlertDialog.Builder(this);
761 builder.setTitle(R.string.donate_);
762 builder.setMessage(R.string.predonate);
763 builder.setPositiveButton(R.string.donate_,
764 new DialogInterface.OnClickListener() {
765 public void onClick(final DialogInterface dialog,
766 final int which) {
767 try {
768 WebSMS.this
769 .startActivity(new Intent(
770 Intent.ACTION_VIEW,
772 .parse(WebSMS.this
773 .getString(R.string.donate_url))));
774 } catch (ActivityNotFoundException e) {
775 Log.e(TAG, "no browser", e);
776 } finally {
777 WebSMS.this.showDialog(DIALOG_POSTDONATE);
781 builder.setNegativeButton(android.R.string.cancel, null);
782 return builder.create();
783 case DIALOG_POSTDONATE:
784 builder = new AlertDialog.Builder(this);
785 builder.setTitle(R.string.remove_ads_);
786 builder.setMessage(R.string.postdonate);
787 builder.setPositiveButton(R.string.send_,
788 new DialogInterface.OnClickListener() {
789 public void onClick(final DialogInterface dialog,
790 final int which) {
791 final Intent in = new Intent(Intent.ACTION_SEND);
793 .putExtra(
794 Intent.EXTRA_EMAIL,
795 new String[] {
796 WebSMS.this
797 .getString(R.string.donate_mail),
798 "" }); // FIXME: "" is a k9
799 // hack.
800 in.putExtra(Intent.EXTRA_TEXT, WebSMS.this
801 .getImeiHash());
803 .putExtra(
804 Intent.EXTRA_SUBJECT,
805 WebSMS.this
806 .getString(R.string.app_name)
807 + " "
808 + WebSMS.this
809 .getString(R.string.donate_subject));
810 in.setType("text/plain");
811 WebSMS.this.startActivity(in);
814 builder.setNegativeButton(android.R.string.cancel, null);
815 return builder.create();
816 case DIALOG_ABOUT:
817 d = new Dialog(this);
818 d.setContentView(R.layout.about);
819 d.setTitle(this.getString(R.string.about_) + " v"
820 + this.getString(R.string.app_version));
821 StringBuffer authors = new StringBuffer();
822 for (ConnectorSpec cs : getConnectors(
823 ConnectorSpec.CAPABILITIES_NONE,
824 ConnectorSpec.STATUS_INACTIVE)) {
825 final String a = cs.getAuthor();
826 if (a != null && a.length() > 0) {
827 authors.append(cs.getName());
828 authors.append(":\t");
829 authors.append(a);
830 authors.append("\n");
833 ((TextView) d.findViewById(R.id.author_connectors)).setText(authors
834 .toString().trim());
835 return d;
836 case DIALOG_UPDATE:
837 builder = new AlertDialog.Builder(this);
838 builder.setTitle(R.string.changelog_);
839 final String[] changes = this.getResources().getStringArray(
840 R.array.updates);
841 final StringBuilder buf = new StringBuilder(changes[0]);
842 for (int i = 1; i < changes.length; i++) {
843 buf.append("\n\n");
844 buf.append(changes[i]);
846 builder.setIcon(android.R.drawable.ic_menu_info_details);
847 builder.setMessage(buf.toString());
848 builder.setCancelable(true);
849 builder.setPositiveButton(android.R.string.ok, null);
850 return builder.create();
851 case DIALOG_CAPTCHA:
852 d = new Dialog(this);
853 d.setTitle(R.string.captcha_);
854 d.setContentView(R.layout.captcha);
855 d.setCancelable(false);
856 ((Button) d.findViewById(R.id.captcha_btn))
857 .setOnClickListener(this);
858 return d;
859 case DIALOG_CUSTOMSENDER:
860 builder = new AlertDialog.Builder(this);
861 builder.setTitle(R.string.custom_sender);
862 builder.setCancelable(true);
863 final EditText et = new EditText(this);
864 builder.setView(et);
865 builder.setPositiveButton(android.R.string.ok,
866 new DialogInterface.OnClickListener() {
867 public void onClick(final DialogInterface dialog,
868 final int id) {
869 WebSMS.lastParams.setCustomSender(et.getText()
870 .toString());
871 if (WebSMS.wantSendLater) {
872 WebSMS.this
873 .showDialog(WebSMS.DIALOG_SENDLATER_DATE);
874 } else {
875 WebSMS.this.send(WebSMS.prefsConnectorSpecs,
876 WebSMS.lastParams);
880 builder.setNegativeButton(android.R.string.cancel, null);
881 return builder.create();
882 case DIALOG_SENDLATER_DATE:
883 Calendar c = Calendar.getInstance();
884 return new DatePickerDialog(this, this, c.get(Calendar.YEAR), c
885 .get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
886 case DIALOG_SENDLATER_TIME:
887 c = Calendar.getInstance();
888 return new MyTimePickerDialog(this, this, c
889 .get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true);
890 default:
891 return null;
896 * {@inheritDoc}
898 @Override
899 protected final void onPrepareDialog(final int id, final Dialog dlg) {
900 switch (id) {
901 case DIALOG_CAPTCHA:
902 if (ConnectorO2.captcha != null) {
903 ((ImageView) dlg.findViewById(R.id.captcha_img))
904 .setImageDrawable(ConnectorO2.captcha);
905 ConnectorO2.captcha = null;
907 break;
908 default:
909 break;
914 * Log text.
916 * @param text
917 * text as resID
919 public final void log(final int text) {
920 this.log(this.getString(text));
924 * Log text.
926 * @param text
927 * text
929 public final void log(final String text) {
930 try {
931 Toast.makeText(this.getApplicationContext(), text,
932 Toast.LENGTH_LONG).show();
933 } catch (RuntimeException e) {
934 Log.e(TAG, null, e);
939 * Send Text.
941 * @param connector
942 * which connector should be used.
943 * @param command
944 * {@link ConnectorCommand} to push to connector
946 private void send(final ConnectorSpec connector,
947 final ConnectorCommand command) {
948 try {
949 final Intent intent = command.setToIntent(null);
950 prefsConnectorSpecs.setToIntent(intent);
951 // TODO: change status of connector
952 this.sendBroadcast(intent);
953 } catch (Exception e) {
954 Log.e(TAG, null, e);
955 } finally {
956 this.reset();
957 if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
958 PREFS_AUTOEXIT, false)) {
959 try {
960 Thread.sleep(SLEEP_BEFORE_EXIT);
961 } catch (InterruptedException e) {
962 Log.e(TAG, null, e);
964 this.finish();
970 * Send Text.
972 * @param connector
973 * which connector should be used.
975 private void send(final ConnectorSpec connector) {
976 // fetch text/recipient
977 final String to = ((EditText) this.findViewById(R.id.to)).getText()
978 .toString();
979 final String text = ((EditText) this.findViewById(R.id.text)).getText()
980 .toString();
981 if (to.length() == 0 || text.length() == 0) {
982 return;
985 if (!prefsNoAds) {
986 // do not display any ads for donators
987 // display ads
988 ((AdView) WebSMS.this.findViewById(R.id.ad))
989 .setVisibility(View.VISIBLE);
992 CheckBox v = (CheckBox) this.findViewById(R.id.flashsms);
993 final boolean flashSMS = (v.getVisibility() == View.VISIBLE)
994 && v.isEnabled() && v.isChecked();
995 v = (CheckBox) this.findViewById(R.id.send_later);
996 if ((v.getVisibility() == View.VISIBLE) && v.isEnabled()
997 && v.isChecked()) {
998 wantSendLater = true;
1000 SharedPreferences p = PreferenceManager
1001 .getDefaultSharedPreferences(this);
1002 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
1003 final String defSender = p.getString(PREFS_SENDER, "");
1005 final ConnectorCommand command = ConnectorCommand.send(defPrefix,
1006 defSender, to.split(","), text, flashSMS);
1008 v = (CheckBox) this.findViewById(R.id.custom_sender);
1009 if ((v.getVisibility() == View.VISIBLE) && v.isEnabled()
1010 && v.isChecked()) {
1011 lastParams = command;
1012 this.showDialog(DIALOG_CUSTOMSENDER);
1013 } else {
1014 if (wantSendLater) {
1015 lastParams = command;
1016 this.showDialog(DIALOG_SENDLATER_DATE);
1017 } else {
1018 this.send(connector, command);
1024 * A Date was set.
1026 * @param view
1027 * DatePicker View
1028 * @param year
1029 * year set
1030 * @param monthOfYear
1031 * month set
1032 * @param dayOfMonth
1033 * day set
1035 public final void onDateSet(final DatePicker view, final int year,
1036 final int monthOfYear, final int dayOfMonth) {
1037 final Calendar c = Calendar.getInstance();
1038 if (lastParams != null) {
1039 final long l = lastParams.getSendLater();
1040 if (l > 0) {
1041 c.setTimeInMillis(l);
1044 c.set(Calendar.YEAR, year);
1045 c.set(Calendar.MONTH, monthOfYear);
1046 c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
1047 lastParams.setSendLater(c.getTimeInMillis());
1049 this.showDialog(DIALOG_SENDLATER_TIME);
1053 * A Time was set.
1055 * @param view
1056 * TimePicker View
1057 * @param hour
1058 * hour set
1059 * @param minutes
1060 * minutes set
1062 public final void onTimeSet(final TimePicker view, final int hour,
1063 final int minutes) {
1064 if (prefsConnectorSpecs.getName().equals("WebSMS.o2") // FIXME
1065 && minutes % 15 != 0) {
1066 Toast.makeText(this, R.string.log_error_o2_sendlater,
1067 Toast.LENGTH_LONG).show();
1068 return;
1071 final Calendar c = Calendar.getInstance();
1072 if (lastParams != null) {
1073 c.setTimeInMillis(lastParams.getSendLater());
1075 c.set(Calendar.HOUR_OF_DAY, hour);
1076 c.set(Calendar.MINUTE, minutes);
1077 lastParams.setSendLater(c.getTimeInMillis());
1079 this.send(WebSMS.prefsConnectorSpecs, WebSMS.lastParams);
1083 * Send WebSMS a Message.
1085 * @param messageType
1086 * type
1087 * @param data
1088 * data
1090 public static final void pushMessage(final int messageType,
1091 final Object data) {
1092 if (WebSMS.me == null) {
1093 return;
1095 Message.obtain(WebSMS.me.messageHandler, messageType, data)
1096 .sendToTarget();
1100 * Calc MD5 Hash from String.
1102 * @param s
1103 * input
1104 * @return hash
1106 static String md5(final String s) {
1107 try {
1108 // Create MD5 Hash
1109 MessageDigest digest = java.security.MessageDigest
1110 .getInstance("MD5");
1111 digest.update(s.getBytes());
1112 byte[] messageDigest = digest.digest();
1113 // Create Hex String
1114 StringBuilder hexString = new StringBuilder(32);
1115 int b;
1116 for (int i = 0; i < messageDigest.length; i++) {
1117 b = 0xFF & messageDigest[i];
1118 if (b < 0x10) {
1119 hexString.append('0' + Integer.toHexString(b));
1120 } else {
1121 hexString.append(Integer.toHexString(b));
1124 return hexString.toString();
1125 } catch (NoSuchAlgorithmException e) {
1126 Log.e(TAG, null, e);
1128 return "";
1132 * Get MD5 hash of the IMEI (device id).
1134 * @return MD5 hash of IMEI
1136 private String getImeiHash() {
1137 if (imeiHash == null) {
1138 // get imei
1139 TelephonyManager mTelephonyMgr = (TelephonyManager) this
1140 .getSystemService(TELEPHONY_SERVICE);
1141 final String did = mTelephonyMgr.getDeviceId();
1142 if (did != null) {
1143 imeiHash = md5(did);
1146 return imeiHash;
1150 * Add or update a {@link ConnectorSpec}.
1152 * @param connector
1153 * connector
1155 public static final void addConnector(final ConnectorSpec connector) {
1156 synchronized (CONNECTORS) {
1157 final ConnectorSpec c = getConnectorByID(connector.getID());
1158 if (c != null) {
1159 c.update(connector);
1160 } else {
1161 CONNECTORS.add(connector);
1167 * Get {@link ConnectorSpec} by ID.
1169 * @param id
1170 * ID
1171 * @return {@link ConnectorSpec}
1173 public static final ConnectorSpec getConnectorByID(final String id) {
1174 synchronized (CONNECTORS) {
1175 final int l = CONNECTORS.size();
1176 for (int i = 0; i < l; i++) {
1177 final ConnectorSpec c = CONNECTORS.get(i);
1178 if (id.equals(c.getID())) {
1179 return c;
1183 return null;
1187 * Get {@link ConnectorSpec} by name.
1189 * @param name
1190 * name
1191 * @return {@link ConnectorSpec}
1193 public static final ConnectorSpec getConnectorByName(final String name) {
1194 synchronized (CONNECTORS) {
1195 final int l = CONNECTORS.size();
1196 for (int i = 0; i < l; i++) {
1197 final ConnectorSpec c = CONNECTORS.get(i);
1198 if (name.equals(c.getName())) {
1199 return c;
1203 return null;
1207 * Get {@link ConnectorSpec}s by capabilities and/or status.
1209 * @param capabilities
1210 * capabilities needed
1211 * @param status
1212 * status required
1213 * @return {@link ConnectorSpec}s
1215 public static final ConnectorSpec[] getConnectors(final short capabilities,
1216 final short status) {
1217 synchronized (CONNECTORS) {
1218 final ArrayList<ConnectorSpec> ret = new ArrayList<ConnectorSpec>(
1219 CONNECTORS.size());
1220 final int l = CONNECTORS.size();
1221 for (int i = 0; i < l; i++) {
1222 final ConnectorSpec c = CONNECTORS.get(i);
1223 if (c.hasCapabilities(capabilities) && c.hasStatus(status)) {
1224 ret.add(c);
1227 return ret.toArray(new ConnectorSpec[0]);