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