fix unnecessary updates on balance
[andGMXsms.git] / src / de / ub0r / android / websms / WebSMS.java
blob7a7a6e35e2e1237372d9339e7ac18d5f7111e7d1
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.DatePickerDialog.OnDateSetListener;
29 import android.app.TimePickerDialog.OnTimeSetListener;
30 import android.content.ActivityNotFoundException;
31 import android.content.DialogInterface;
32 import android.content.Intent;
33 import android.content.SharedPreferences;
34 import android.content.SharedPreferences.Editor;
35 import android.database.Cursor;
36 import android.net.Uri;
37 import android.os.Bundle;
38 import android.preference.PreferenceManager;
39 import android.provider.Contacts.PeopleColumns;
40 import android.provider.Contacts.Phones;
41 import android.provider.Contacts.PhonesColumns;
42 import android.telephony.TelephonyManager;
43 import android.telephony.gsm.SmsMessage;
44 import android.text.Editable;
45 import android.text.TextWatcher;
46 import android.util.Log;
47 import android.view.Menu;
48 import android.view.MenuInflater;
49 import android.view.MenuItem;
50 import android.view.View;
51 import android.view.Window;
52 import android.view.View.OnClickListener;
53 import android.widget.Button;
54 import android.widget.CheckBox;
55 import android.widget.DatePicker;
56 import android.widget.EditText;
57 import android.widget.MultiAutoCompleteTextView;
58 import android.widget.TextView;
59 import android.widget.TimePicker;
60 import android.widget.Toast;
62 import com.admob.android.ads.AdView;
64 import de.ub0r.android.websms.connector.common.Connector;
65 import de.ub0r.android.websms.connector.common.ConnectorCommand;
66 import de.ub0r.android.websms.connector.common.ConnectorSpec;
67 import de.ub0r.android.websms.connector.common.Utils;
68 import de.ub0r.android.websms.connector.common.ConnectorSpec.SubConnectorSpec;
70 /**
71 * Main Activity.
73 * @author flx
75 @SuppressWarnings("deprecation")
76 public class WebSMS extends Activity implements OnClickListener,
77 OnDateSetListener, OnTimeSetListener {
78 /** Tag for output. */
79 private static final String TAG = "WebSMS";
81 /** Static reference to running Activity. */
82 private static WebSMS me;
83 /** Preference's name: last version run. */
84 private static final String PREFS_LAST_RUN = "lastrun";
85 /** Preference's name: user's phonenumber. */
86 static final String PREFS_SENDER = "sender";
87 /** Preference's name: default prefix. */
88 static final String PREFS_DEFPREFIX = "defprefix";
89 /** Preference's name: touch keyboard. */
90 private static final String PREFS_SOFTKEYS = "softkeyboard";
91 /** Preference's name: update balace on start. */
92 private static final String PREFS_AUTOUPDATE = "autoupdate";
93 /** Preference's name: exit after sending. */
94 private static final String PREFS_AUTOEXIT = "autoexit";
95 /** Preference's name: show mobile numbers only. */
96 private static final String PREFS_MOBILES_ONLY = "mobiles_only";
97 /** Preference's name: vibrate on failed sending. */
98 static final String PREFS_FAIL_VIBRATE = "fail_vibrate";
99 /** Preference's name: sound on failed sending. */
100 static final String PREFS_FAIL_SOUND = "fail_sound";
101 /** Preferemce's name: enable change connector button. */
102 private static final String PREFS_CHANGE_CONNECTOR_BUTTON = // .
103 "change_connector_button";
104 /** Preferemce's name: hide cancel button. */
105 private static final String PREFS_HIDE_CANCEL_BUTTON = "hide_cancel_button";
107 /** Preference's name: to. */
108 private static final String PREFS_TO = "to";
109 /** Preference's name: text. */
110 private static final String PREFS_TEXT = "text";
111 /** Preference's name: selected {@link ConnectorSpec} ID. */
112 private static final String PREFS_CONNECTOR_ID = "connector_id";
113 /** Preference's name: selected {@link SubConnectorSpec} ID. */
114 private static final String PREFS_SUBCONNECTOR_ID = "subconnector_id";
116 /** Sleep before autoexit. */
117 private static final int SLEEP_BEFORE_EXIT = 75;
119 /** Preferences: hide ads. */
120 private static boolean prefsNoAds = false;
121 /** Hased IMEI. */
122 private static String imeiHash = null;
123 /** Preferences: selected {@link ConnectorSpec}. */
124 private static ConnectorSpec prefsConnectorSpec = null;
125 /** Preferences: selected {@link SubConnectorSpec}. */
126 private static SubConnectorSpec prefsSubConnectorSpec = null;
127 /** Save prefsConnectorSpec.getID() here. */
128 private static String prefsConnectorID = null;
130 /** List of available {@link ConnectorSpec}s. */
131 private static final ArrayList<ConnectorSpec> CONNECTORS = // .
132 new ArrayList<ConnectorSpec>();
134 /** Array of md5(prefsSender) for which no ads should be displayed. */
135 private static final String[] NO_AD_HASHS = {
136 "43dcb861b9588fb733300326b61dbab9", // flx
137 "57a3c7c19329fd84c2252a9b2866dd93", // mirweb
138 "10b7a2712beee096acbc67416d7d71a1", // mo
139 "f6b3b72300e918436b4c4c9fdf909e8c", // joerg s.
140 "4c18f7549b643045f0ff69f61e8f7e72", // frank j.
141 "7684154558d19383552388d9bc92d446", // henning k.
142 "64c7414288e9a9b57a33e034f384ed30", // dominik l.
143 "c479a2e701291c751f0f91426bcaabf3", // bernhard g.
144 "ae7dfedf549f98a349ad8c2068473c6b", // dominik k.-v.
145 "18bc29cd511613552861da6ef51766ce", // niels b.
146 "2985011f56d0049b0f4f0caed3581123", // sven l.
147 "64724033da297a915a89023b11ac2e47", // wilfried m.
148 "cfd8d2efb3eac39705bd62c4dfe5e72d", // achim e.
149 "ca56e7518fdbda832409ef07edd4c273", // michael s.
150 "bed2f068ca8493da4179807d1afdbd83", // axel q.
151 "4c35400c4fa3ffe2aefcf1f9131eb855", // gerhard s.
152 "02158d2a80b1ef9c4d684a4ca808b93d", // camilo s.
153 "1177c6e67f98cdfed6c84d99e85d30de", // daniel p.
154 "3f082dd7e21d5c64f34a69942c474ce7", // andre j.
155 "5383540b2f8c298532f874126b021e73", // marco a.
156 "858ddfb8635d1539884086dca2726468", // lado
157 "6e8bbb35091219a80e278ae61f31cce9", // mario s.
158 "9f01eae4eaecd9158a2caddc04bad77e", // andreas p.
161 /** true if preferences got opened. */
162 static boolean doPreferences = false;
164 /** Dialog: about. */
165 private static final int DIALOG_ABOUT = 0;
166 /** Dialog: updates. */
167 private static final int DIALOG_UPDATE = 2;
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 /** Intent's extra for error messages. */
180 static final String EXTRA_ERRORMESSAGE = // .
181 "de.ub0r.android.intent.extra.ERRORMESSAGE";
183 /** Persistent Message store. */
184 private static String lastMsg = null;
185 /** Persistent Recipient store. */
186 private static String lastTo = null;
187 /** Backup for params: custom sender. */
188 private static String lastCustomSender = null;
189 /** Backup for params: send later. */
190 private static long lastSendLater = -1;
192 /** Helper for API 5. */
193 static HelperAPI5Contacts helperAPI5c = null;
195 /** Text's label. */
196 private TextView textLabel;
198 /** Show extras. */
199 private boolean showExtras = false;
201 /** TextWatcher updating char count on writing. */
202 private TextWatcher textWatcher = new TextWatcher() {
204 * {@inheritDoc}
206 public void afterTextChanged(final Editable s) {
207 int[] l = SmsMessage.calculateLength(s, false);
208 WebSMS.this.textLabel.setText(l[0] + "/" + l[2]);
211 /** Needed dummy. */
212 public void beforeTextChanged(final CharSequence s, final int start,
213 final int count, final int after) {
216 /** Needed dummy. */
217 public void onTextChanged(final CharSequence s, final int start,
218 final int before, final int count) {
223 * {@inheritDoc}
225 @Override
226 public final void onCreate(final Bundle savedInstanceState) {
227 super.onCreate(savedInstanceState);
228 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
230 // save ref to me.
231 me = this;
232 try {
233 WebSMS.helperAPI5c = new HelperAPI5Contacts();
234 if (!helperAPI5c.isAvailable()) {
235 WebSMS.helperAPI5c = null;
237 } catch (VerifyError e) {
238 WebSMS.helperAPI5c = null;
239 Log.d(TAG, "no api5 running", e);
241 // Restore preferences
242 final SharedPreferences p = PreferenceManager
243 .getDefaultSharedPreferences(this);
244 // inflate XML
245 if (p.getBoolean(PREFS_SOFTKEYS, false)) {
246 this.setContentView(R.layout.main_touch);
247 } else {
248 this.setContentView(R.layout.main);
251 this.findViewById(R.id.to).requestFocus();
253 // display changelog?
254 String v0 = p.getString(PREFS_LAST_RUN, "");
255 String v1 = this.getResources().getString(R.string.app_version);
256 if (!v0.equals(v1)) {
257 SharedPreferences.Editor editor = p.edit();
258 editor.putString(PREFS_LAST_RUN, v1);
259 editor.commit();
260 this.showDialog(DIALOG_UPDATE);
263 this.reloadPrefs();
265 lastTo = p.getString(PREFS_TO, "");
266 lastMsg = p.getString(PREFS_TEXT, "");
268 // register Listener
269 this.findViewById(R.id.send_).setOnClickListener(this);
270 this.findViewById(R.id.cancel).setOnClickListener(this);
271 this.findViewById(R.id.change_connector).setOnClickListener(this);
272 this.findViewById(R.id.extras).setOnClickListener(this);
273 this.findViewById(R.id.custom_sender).setOnClickListener(this);
274 this.findViewById(R.id.send_later).setOnClickListener(this);
276 this.textLabel = (TextView) this.findViewById(R.id.text_);
277 ((EditText) this.findViewById(R.id.text))
278 .addTextChangedListener(this.textWatcher);
280 ((TextView) this.findViewById(R.id.freecount)).setOnClickListener(this);
282 final MultiAutoCompleteTextView to = (MultiAutoCompleteTextView) this
283 .findViewById(R.id.to);
284 to.setAdapter(new MobilePhoneAdapter(this));
285 to.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
287 Intent intent = this.getIntent();
288 final String action = intent.getAction();
289 if (action != null) {
290 // launched by clicking a sms: link, target number is in URI.
291 final Uri uri = intent.getData();
292 if (uri != null) {
293 final String scheme = uri.getScheme();
294 if (scheme.equals("sms") || scheme.equals("smsto")) {
295 String s = uri.getSchemeSpecificPart();
296 if (s != null) {
297 s = s.trim();
298 if (s.endsWith(",")) {
299 s = s.substring(0, s.length() - 1).trim();
301 // recipient = WebSMS.cleanRecipient(recipient);
302 if (s.indexOf('<') < 0) {
303 // try to fetch recipient's name from phonebook
304 String n = null;
305 if (helperAPI5c != null) {
306 try {
307 n = helperAPI5c.getNameForNumber(this, s);
308 } catch (NoClassDefFoundError e) {
309 helperAPI5c = null;
312 if (helperAPI5c == null) {
313 Cursor c = this
314 .managedQuery(Phones.CONTENT_URI,
315 new String[] {
316 PhonesColumns.NUMBER,
317 PeopleColumns.// .
318 DISPLAY_NAME },
319 PhonesColumns.NUMBER + " = '"
320 + s + "'", null, null);
321 if (c.moveToFirst()) {
322 n = c.getString(c.getColumnIndex(// .
323 PeopleColumns.DISPLAY_NAME));
326 if (n != null) {
327 s = n + " <" + s + ">, ";
330 ((EditText) this.findViewById(R.id.to)).setText(s);
331 lastTo = s;
333 final Bundle extras = intent.getExtras();
334 if (extras != null) {
335 s = extras.getCharSequence(Intent.EXTRA_TEXT)
336 .toString();
337 if (s != null) {
338 ((EditText) this.findViewById(R.id.text))
339 .setText(s);
340 lastMsg = s;
342 s = extras.getString(EXTRA_ERRORMESSAGE);
343 if (s != null) {
344 Toast.makeText(this, s, Toast.LENGTH_LONG).show();
347 if (!prefsNoAds) {
348 // do not display any ads for donators
349 // display ads
350 ((AdView) WebSMS.this.findViewById(R.id.ad))
351 .setVisibility(View.VISIBLE);
357 // check default prefix
358 if (!p.getString(PREFS_DEFPREFIX, "").startsWith("+")) {
359 WebSMS.this.log(R.string.log_error_defprefix);
361 if (p.getBoolean(PREFS_AUTOUPDATE, false)) {
362 this.updateFreecount(false);
367 * {@inheritDoc}
369 @Override
370 protected final void onResume() {
371 super.onResume();
372 // set free sms count
373 this.updateBalance();
375 // if coming from prefs..
376 if (doPreferences) {
377 this.reloadPrefs();
378 doPreferences = false;
379 final SharedPreferences p = PreferenceManager
380 .getDefaultSharedPreferences(this);
381 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
382 final String defSender = p.getString(PREFS_SENDER, "");
383 final ConnectorSpec[] css = getConnectors(
384 ConnectorSpec.CAPABILITIES_BOOTSTRAP, // .
385 ConnectorSpec.STATUS_ENABLED);
386 for (ConnectorSpec cs : css) {
387 final Intent intent = new Intent(cs.getPackage()
388 + Connector.ACTION_RUN_BOOTSTRAP);
389 ConnectorCommand.bootstrap(defPrefix, defSender).setToIntent(
390 intent);
391 Log.d(TAG, "send broadcast: " + intent.getAction());
392 this.sendBroadcast(intent);
396 this.setButtons();
398 // reload text/recipient from local store
399 final EditText et0 = (EditText) this.findViewById(R.id.text);
400 if (lastMsg != null) {
401 et0.setText(lastMsg);
402 } else {
403 et0.setText("");
405 final EditText et1 = (EditText) this.findViewById(R.id.to);
406 if (lastTo != null) {
407 et1.setText(lastTo);
408 } else {
409 et1.setText("");
412 if (lastTo != null && lastTo.length() > 0) {
413 et0.requestFocus();
414 } else {
415 et1.requestFocus();
418 // query for connectors
419 final Intent i = new Intent(Connector.ACTION_CONNECTOR_UPDATE);
420 Log.d(TAG, "send broadcast: " + i.getAction());
421 this.sendBroadcast(i);
425 * Update balance.
427 private void updateBalance() {
428 final StringBuilder buf = new StringBuilder();
429 final ConnectorSpec[] css = getConnectors(
430 ConnectorSpec.CAPABILITIES_UPDATE, // .
431 ConnectorSpec.STATUS_ENABLED);
432 for (ConnectorSpec cs : css) {
433 final String b = cs.getBalance();
434 if (b == null || b.length() == 0) {
435 continue;
437 if (buf.length() > 0) {
438 buf.append(", ");
440 buf.append(cs.getName());
441 buf.append(": ");
442 buf.append(b);
445 TextView tw = (TextView) this.findViewById(R.id.freecount);
446 tw.setText(this.getString(R.string.free_) + " " + buf.toString() + " "
447 + this.getString(R.string.click_for_update));
451 * {@inheritDoc}
453 @Override
454 public final void onPause() {
455 super.onPause();
456 // store input data to persitent stores
457 lastMsg = ((EditText) this.findViewById(R.id.text)).getText()
458 .toString();
459 lastTo = ((EditText) this.findViewById(R.id.to)).getText().toString();
461 // store input data to preferences
462 SharedPreferences.Editor editor = PreferenceManager
463 .getDefaultSharedPreferences(this).edit();
464 // common
465 editor.putString(PREFS_TO, lastTo);
466 editor.putString(PREFS_TEXT, lastMsg);
467 // commit changes
468 editor.commit();
470 this.savePreferences();
474 * Read static vars holding preferences.
476 private void reloadPrefs() {
477 final SharedPreferences p = PreferenceManager
478 .getDefaultSharedPreferences(this);
479 boolean b = p.getBoolean(PREFS_CHANGE_CONNECTOR_BUTTON, false);
480 View v = this.findViewById(R.id.change_connector);
481 if (b) {
482 v.setVisibility(View.VISIBLE);
483 } else {
484 v.setVisibility(View.GONE);
487 b = !p.getBoolean(PREFS_HIDE_CANCEL_BUTTON, false);
488 v = this.findViewById(R.id.cancel);
489 if (b) {
490 v.setVisibility(View.VISIBLE);
491 } else {
492 v.setVisibility(View.GONE);
495 prefsConnectorID = p.getString(PREFS_CONNECTOR_ID, "");
496 prefsConnectorSpec = getConnectorByID(prefsConnectorID);
497 if (prefsConnectorSpec != null) {
498 prefsSubConnectorSpec = prefsConnectorSpec.getSubConnector(p
499 .getString(PREFS_SUBCONNECTOR_ID, ""));
502 MobilePhoneAdapter.setMoileNubersObly(p.getBoolean(PREFS_MOBILES_ONLY,
503 false));
505 prefsNoAds = false;
506 String hash = Utils.md5(p.getString(PREFS_SENDER, ""));
507 for (String h : NO_AD_HASHS) {
508 if (hash.equals(h)) {
509 prefsNoAds = true;
510 break;
513 if (!prefsNoAds && this.getImeiHash() != null) {
514 for (String h : NO_AD_HASHS) {
515 if (imeiHash.equals(h)) {
516 prefsNoAds = true;
517 break;
522 this.setButtons();
526 * Show/hide, enable/disable send buttons.
528 private void setButtons() {
529 // final ConnectorSpec[] enabled = getConnectors(
530 // ConnectorSpec.CAPABILITIES_SEND, ConnectorSpec.STATUS_ENABLED);
532 Button btn = (Button) this.findViewById(R.id.send_);
533 // show/hide buttons
534 btn.setEnabled(prefsConnectorSpec != null
535 && prefsSubConnectorSpec != null);
536 btn.setVisibility(View.VISIBLE);
538 if (prefsConnectorSpec != null && prefsSubConnectorSpec != null) {
539 final boolean sFlashsms = prefsSubConnectorSpec
540 .hasFeatures(SubConnectorSpec.FEATURE_FLASHSMS);
541 final boolean sCustomsender = prefsSubConnectorSpec
542 .hasFeatures(SubConnectorSpec.FEATURE_CUSTOMSENDER);
543 final boolean sSendLater = prefsSubConnectorSpec
544 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER);
545 if (sFlashsms || sCustomsender || sSendLater) {
546 this.findViewById(R.id.extras).setVisibility(View.VISIBLE);
547 } else {
548 this.findViewById(R.id.extras).setVisibility(View.GONE);
550 if (this.showExtras && sFlashsms) {
551 this.findViewById(R.id.flashsms).setVisibility(View.VISIBLE);
552 } else {
553 this.findViewById(R.id.flashsms).setVisibility(View.GONE);
555 if (this.showExtras && sCustomsender) {
556 this.findViewById(R.id.custom_sender).setVisibility(
557 View.VISIBLE);
558 } else {
559 this.findViewById(R.id.custom_sender).setVisibility(View.GONE);
561 if (this.showExtras && sSendLater) {
562 this.findViewById(R.id.send_later).setVisibility(View.VISIBLE);
563 } else {
564 this.findViewById(R.id.send_later).setVisibility(View.GONE);
567 String t = this.getString(R.string.app_name) + " - "
568 + prefsConnectorSpec.getName();
569 if (prefsSubConnectorSpec != null
570 && prefsConnectorSpec.getSubConnectorCount() > 1) {
571 t += " - " + prefsSubConnectorSpec.getName();
573 this.setTitle(t);
578 * Resets persistent store.
580 private void reset() {
581 ((EditText) this.findViewById(R.id.text)).setText("");
582 ((EditText) this.findViewById(R.id.to)).setText("");
583 lastMsg = null;
584 lastTo = null;
585 // save user preferences
586 SharedPreferences.Editor editor = PreferenceManager
587 .getDefaultSharedPreferences(this).edit();
588 editor.putString(PREFS_TO, "");
589 editor.putString(PREFS_TEXT, "");
590 // commit changes
591 editor.commit();
594 /** Save prefs. */
595 final void savePreferences() {
596 if (prefsConnectorSpec != null) {
597 PreferenceManager.getDefaultSharedPreferences(this).edit()
598 .putString(PREFS_CONNECTOR_ID, prefsConnectorSpec.getID())
599 .commit();
604 * Run Connector.update().
606 * @param forceUpdate
607 * force update, if false only blank balances will get updated
609 private void updateFreecount(final boolean forceUpdate) {
610 final SharedPreferences p = PreferenceManager
611 .getDefaultSharedPreferences(this);
612 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
613 final String defSender = p.getString(PREFS_SENDER, "");
614 final ConnectorSpec[] css = getConnectors(
615 ConnectorSpec.CAPABILITIES_UPDATE, // .
616 ConnectorSpec.STATUS_ENABLED);
617 for (ConnectorSpec cs : css) {
618 final Intent intent = new Intent(cs.getPackage()
619 + Connector.ACTION_RUN_UPDATE);
620 ConnectorCommand.update(defPrefix, defSender).setToIntent(intent);
621 Log.d(TAG, "send broadcast: " + intent.getAction());
622 this.sendBroadcast(intent);
627 * {@inheritDoc}
629 public final void onClick(final View v) {
630 switch (v.getId()) {
631 case R.id.freecount:
632 this.updateFreecount(true);
633 return;
634 case R.id.send_:
635 this.send(prefsConnectorSpec, WebSMS.getSelectedSubConnectorID());
636 return;
637 case R.id.cancel:
638 this.reset();
639 return;
640 case R.id.change_connector:
641 this.changeConnectorMenu();
642 return;
643 case R.id.extras:
644 this.showExtras = !this.showExtras;
645 this.setButtons();
646 return;
647 case R.id.custom_sender:
648 final CheckBox cs = (CheckBox) this
649 .findViewById(R.id.custom_sender);
650 if (cs.isChecked()) {
651 this.showDialog(DIALOG_CUSTOMSENDER);
652 } else {
653 lastCustomSender = null;
655 return;
656 case R.id.send_later:
657 final CheckBox sl = (CheckBox) this.findViewById(R.id.send_later);
658 if (sl.isChecked()) {
659 this.showDialog(DIALOG_SENDLATER_DATE);
660 } else {
661 lastSendLater = -1;
663 return;
664 default:
665 return;
670 * {@inheritDoc}
672 @Override
673 public final boolean onCreateOptionsMenu(final Menu menu) {
674 MenuInflater inflater = this.getMenuInflater();
675 inflater.inflate(R.menu.menu, menu);
676 if (prefsNoAds) {
677 menu.removeItem(R.id.item_donate);
679 return true;
683 * Display "change connector" menu.
685 private void changeConnectorMenu() {
686 AlertDialog.Builder builder = new AlertDialog.Builder(this);
687 builder.setTitle(R.string.change_connector_);
688 final ArrayList<String> items = new ArrayList<String>();
689 final ConnectorSpec[] css = getConnectors(
690 ConnectorSpec.CAPABILITIES_SEND, ConnectorSpec.STATUS_ENABLED);
691 for (ConnectorSpec cs : css) {
692 final SubConnectorSpec[] scs = cs.getSubConnectors();
693 if (scs.length <= 1) {
694 items.add(cs.getName());
695 } else {
696 final String n = cs.getName() + " - ";
697 for (SubConnectorSpec sc : scs) {
698 items.add(n + sc.getName());
703 builder.setItems(items.toArray(new String[0]),
704 new DialogInterface.OnClickListener() {
705 public void onClick(final DialogInterface d, // .
706 final int item) {
707 final SubConnectorSpec[] ret = ConnectorSpec
708 .getSubConnectorReturnArray();
709 prefsConnectorSpec = getConnectorByName(
710 items.get(item), ret);
711 prefsSubConnectorSpec = ret[0];
712 WebSMS.this.setButtons();
713 // save user preferences
714 final Editor e = PreferenceManager
715 .getDefaultSharedPreferences(WebSMS.this)
716 .edit();
717 e.putString(PREFS_CONNECTOR_ID, prefsConnectorSpec
718 .getID());
719 e.putString(PREFS_SUBCONNECTOR_ID,
720 prefsSubConnectorSpec.getID());
721 e.commit();
724 builder.create().show();
728 *{@inheritDoc}
730 @Override
731 public final boolean onOptionsItemSelected(final MenuItem item) {
732 switch (item.getItemId()) {
733 case R.id.item_about: // start about dialog
734 this.showDialog(DIALOG_ABOUT);
735 return true;
736 case R.id.item_settings: // start settings activity
737 this.startActivity(new Intent(this, Preferences.class));
738 return true;
739 case R.id.item_donate:
740 this.showDialog(DIALOG_PREDONATE);
741 return true;
742 case R.id.item_more:
743 try {
744 this.startActivity(new Intent(Intent.ACTION_VIEW, Uri
745 .parse("market://search?q=pub:\"Felix Bechstein\"")));
746 } catch (ActivityNotFoundException e) {
747 Log.e(TAG, "no market", e);
749 return true;
750 case R.id.item_connector:
751 this.changeConnectorMenu();
752 return true;
753 default:
754 return false;
759 * {@inheritDoc}
761 @Override
762 protected final Dialog onCreateDialog(final int id) {
763 Dialog d;
764 AlertDialog.Builder builder;
765 switch (id) {
766 case DIALOG_PREDONATE:
767 builder = new AlertDialog.Builder(this);
768 builder.setIcon(R.drawable.ic_menu_star);
769 builder.setTitle(R.string.donate_);
770 builder.setMessage(R.string.predonate);
771 builder.setPositiveButton(R.string.donate_,
772 new DialogInterface.OnClickListener() {
773 public void onClick(final DialogInterface dialog,
774 final int which) {
775 try {
776 WebSMS.this.startActivity(new Intent(
777 Intent.ACTION_VIEW, Uri.parse(// .
778 WebSMS.this.getString(// .
779 R.string.donate_url))));
780 } catch (ActivityNotFoundException e) {
781 Log.e(TAG, "no browser", e);
782 } finally {
783 WebSMS.this.showDialog(DIALOG_POSTDONATE);
787 builder.setNegativeButton(android.R.string.cancel, null);
788 return builder.create();
789 case DIALOG_POSTDONATE:
790 builder = new AlertDialog.Builder(this);
791 builder.setIcon(R.drawable.ic_menu_star);
792 builder.setTitle(R.string.remove_ads_);
793 builder.setMessage(R.string.postdonate);
794 builder.setPositiveButton(R.string.send_,
795 new DialogInterface.OnClickListener() {
796 public void onClick(final DialogInterface dialog,
797 final int which) {
798 final Intent in = new Intent(Intent.ACTION_SEND);
799 in.putExtra(Intent.EXTRA_EMAIL, new String[] {
800 WebSMS.this.getString(// .
801 R.string.donate_mail), "" });
802 // FIXME: "" is a k9 hack. This is fixed in market
803 // on 26.01.10. wait some more time..
804 in.putExtra(Intent.EXTRA_TEXT, WebSMS.this
805 .getImeiHash());
806 in.putExtra(Intent.EXTRA_SUBJECT, WebSMS.this
807 .getString(// .
808 R.string.app_name)
809 + " " + WebSMS.this.getString(// .
810 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 final ConnectorSpec[] css = getConnectors(
824 ConnectorSpec.CAPABILITIES_NONE,
825 ConnectorSpec.STATUS_INACTIVE);
826 for (ConnectorSpec cs : css) {
827 final String a = cs.getAuthor();
828 if (a != null && a.length() > 0) {
829 authors.append(cs.getName());
830 authors.append(":\t");
831 authors.append(a);
832 authors.append("\n");
835 ((TextView) d.findViewById(R.id.author_connectors)).setText(authors
836 .toString().trim());
837 return d;
838 case DIALOG_UPDATE:
839 builder = new AlertDialog.Builder(this);
840 builder.setTitle(R.string.changelog_);
841 final String[] changes = this.getResources().getStringArray(
842 R.array.updates);
843 final StringBuilder buf = new StringBuilder(changes[0]);
844 for (int i = 1; i < changes.length; i++) {
845 buf.append("\n\n");
846 buf.append(changes[i]);
848 builder.setIcon(android.R.drawable.ic_menu_info_details);
849 builder.setMessage(buf.toString());
850 builder.setCancelable(true);
851 builder.setPositiveButton(android.R.string.ok, null);
852 return builder.create();
853 case DIALOG_CUSTOMSENDER:
854 builder = new AlertDialog.Builder(this);
855 builder.setTitle(R.string.custom_sender);
856 builder.setCancelable(true);
857 final EditText et = new EditText(this);
858 builder.setView(et);
859 builder.setPositiveButton(android.R.string.ok,
860 new DialogInterface.OnClickListener() {
861 public void onClick(final DialogInterface dialog,
862 final int id) {
863 WebSMS.lastCustomSender = et.getText().toString();
866 builder.setNegativeButton(android.R.string.cancel, null);
867 return builder.create();
868 case DIALOG_SENDLATER_DATE:
869 Calendar c = Calendar.getInstance();
870 return new DatePickerDialog(this, this, c.get(Calendar.YEAR), c
871 .get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
872 case DIALOG_SENDLATER_TIME:
873 c = Calendar.getInstance();
874 return new MyTimePickerDialog(this, this, c
875 .get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true);
876 default:
877 return null;
882 * Log text.
884 * @param text
885 * text as resID
887 public final void log(final int text) {
888 this.log(this.getString(text));
892 * Log text.
894 * @param text
895 * text
897 public final void log(final String text) {
898 try {
899 Toast.makeText(this.getApplicationContext(), text,
900 Toast.LENGTH_LONG).show();
901 } catch (RuntimeException e) {
902 Log.e(TAG, null, e);
907 * Send text to {@link Connector}.
909 * @param connector
910 * which connector should be used.
911 * @param command
912 * {@link ConnectorCommand} to push to connector
914 private void send(final ConnectorSpec connector,
915 final ConnectorCommand command) {
916 try {
917 final Intent intent = new Intent(connector.getPackage()
918 + Connector.ACTION_RUN_SEND);
919 command.setToIntent(intent);
920 prefsConnectorSpec.setToIntent(intent);
921 connector.addStatus(ConnectorSpec.STATUS_SENDING);
922 Log.d(TAG, "send broadcast: " + intent.getAction());
923 this.sendBroadcast(intent);
924 } catch (Exception e) {
925 Log.e(TAG, null, e);
926 } finally {
927 this.reset();
928 if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
929 PREFS_AUTOEXIT, false)) {
930 try {
931 Thread.sleep(SLEEP_BEFORE_EXIT);
932 } catch (InterruptedException e) {
933 Log.e(TAG, null, e);
935 this.finish();
941 * Send text.
943 * @param connector
944 * which connector should be used.
945 * @param subconnector
946 * selected {@link SubConnectorSpec} ID
948 private void send(final ConnectorSpec connector, // .
949 final String subconnector) {
950 // fetch text/recipient
951 final String to = ((EditText) this.findViewById(R.id.to)).getText()
952 .toString();
953 final String text = ((EditText) this.findViewById(R.id.text)).getText()
954 .toString();
955 if (to.length() == 0 || text.length() == 0) {
956 return;
959 if (!prefsNoAds) {
960 // do not display any ads for donators
961 // display ads
962 ((AdView) WebSMS.this.findViewById(R.id.ad))
963 .setVisibility(View.VISIBLE);
966 CheckBox v = (CheckBox) this.findViewById(R.id.flashsms);
967 final boolean flashSMS = (v.getVisibility() == View.VISIBLE)
968 && v.isEnabled() && v.isChecked();
969 final SharedPreferences p = PreferenceManager
970 .getDefaultSharedPreferences(this);
971 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
972 final String defSender = p.getString(PREFS_SENDER, "");
974 final ConnectorCommand command = ConnectorCommand.send(subconnector,
975 defPrefix, defSender, to.split(","), text, flashSMS);
976 command.setCustomSender(lastCustomSender);
977 command.setSendLater(lastSendLater);
978 this.send(connector, command);
982 * @return ID of selected {@link SubConnectorSpec}
984 private static String getSelectedSubConnectorID() {
985 if (prefsSubConnectorSpec == null) {
986 return null;
988 return prefsSubConnectorSpec.getID();
992 * A Date was set.
994 * @param view
995 * DatePicker View
996 * @param year
997 * year set
998 * @param monthOfYear
999 * month set
1000 * @param dayOfMonth
1001 * day set
1003 public final void onDateSet(final DatePicker view, final int year,
1004 final int monthOfYear, final int dayOfMonth) {
1005 final Calendar c = Calendar.getInstance();
1006 if (lastSendLater > 0) {
1007 c.setTimeInMillis(lastSendLater);
1009 c.set(Calendar.YEAR, year);
1010 c.set(Calendar.MONTH, monthOfYear);
1011 c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
1012 lastSendLater = c.getTimeInMillis();
1014 MyTimePickerDialog.setOnlyQuaters(prefsSubConnectorSpec
1015 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER_QUARTERS));
1016 this.showDialog(DIALOG_SENDLATER_TIME);
1020 * A Time was set.
1022 * @param view
1023 * TimePicker View
1024 * @param hour
1025 * hour set
1026 * @param minutes
1027 * minutes set
1029 public final void onTimeSet(final TimePicker view, final int hour,
1030 final int minutes) {
1031 if (prefsSubConnectorSpec
1032 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER_QUARTERS)
1033 && minutes % 15 != 0) {
1034 Toast.makeText(this, R.string.log_error_o2_sendlater,
1035 Toast.LENGTH_LONG).show();
1036 return;
1039 final Calendar c = Calendar.getInstance();
1040 if (lastSendLater > 0) {
1041 c.setTimeInMillis(lastSendLater);
1043 c.set(Calendar.HOUR_OF_DAY, hour);
1044 c.set(Calendar.MINUTE, minutes);
1045 lastSendLater = c.getTimeInMillis();
1049 * Get MD5 hash of the IMEI (device id).
1051 * @return MD5 hash of IMEI
1053 private String getImeiHash() {
1054 if (imeiHash == null) {
1055 // get imei
1056 TelephonyManager mTelephonyMgr = (TelephonyManager) this
1057 .getSystemService(TELEPHONY_SERVICE);
1058 final String did = mTelephonyMgr.getDeviceId();
1059 if (did != null) {
1060 imeiHash = Utils.md5(did);
1063 return imeiHash;
1067 * Add or update a {@link ConnectorSpec}.
1069 * @param connector
1070 * connector
1072 public static final void addConnector(final ConnectorSpec connector) {
1073 synchronized (CONNECTORS) {
1074 if (connector == null || connector.getID() == null
1075 || connector.getName() == null) {
1076 return;
1078 ConnectorSpec c = getConnectorByID(connector.getID());
1079 if (c != null) {
1080 c.update(connector);
1081 } else {
1082 final int l = CONNECTORS.size();
1083 final String name = connector.getName();
1084 Log.d(TAG, "add connector with id: " + connector.getID());
1085 Log.d(TAG, "add connector with name: " + name);
1086 boolean added = false;
1087 try {
1088 for (int i = 0; i < l; i++) {
1089 final ConnectorSpec cs = CONNECTORS.get(i);
1090 if (name.compareToIgnoreCase(cs.getName()) < 0) {
1091 CONNECTORS.add(i, connector);
1092 added = true;
1093 break;
1096 } catch (NullPointerException e) {
1097 Log.e(TAG, "error while sorting", e);
1099 if (!added) {
1100 CONNECTORS.add(connector);
1102 c = connector;
1104 if (prefsConnectorSpec == null
1105 && prefsConnectorID.equals(connector.getID())) {
1106 prefsConnectorSpec = connector;
1107 final SharedPreferences p = PreferenceManager
1108 .getDefaultSharedPreferences(me);
1109 prefsSubConnectorSpec = connector.getSubConnector(p
1110 .getString(PREFS_SUBCONNECTOR_ID, ""));
1111 me.setButtons();
1114 final String b = c.getBalance();
1115 final String ob = c.getOldBalance();
1116 if (b != null && (ob == null || !b.equals(ob))) {
1117 me.updateBalance();
1123 * Get {@link ConnectorSpec} by ID.
1125 * @param id
1126 * ID
1127 * @return {@link ConnectorSpec}
1129 private static ConnectorSpec getConnectorByID(final String id) {
1130 synchronized (CONNECTORS) {
1131 if (id == null) {
1132 return null;
1134 final int l = CONNECTORS.size();
1135 for (int i = 0; i < l; i++) {
1136 final ConnectorSpec c = CONNECTORS.get(i);
1137 if (id.equals(c.getID())) {
1138 return c;
1142 return null;
1146 * Get {@link ConnectorSpec} by name.
1148 * @param name
1149 * name
1150 * @param returnSelectedSubConnector
1151 * if not null, array[0] will be set to selected
1152 * {@link SubConnectorSpec}
1153 * @return {@link ConnectorSpec}
1155 private static ConnectorSpec getConnectorByName(final String name,
1156 final SubConnectorSpec[] returnSelectedSubConnector) {
1157 synchronized (CONNECTORS) {
1158 if (name == null) {
1159 return null;
1161 final int l = CONNECTORS.size();
1162 for (int i = 0; i < l; i++) {
1163 final ConnectorSpec c = CONNECTORS.get(i);
1164 final String n = c.getName();
1165 if (name.startsWith(n)) {
1166 if (name.length() == n.length()) {
1167 if (returnSelectedSubConnector != null) {
1168 returnSelectedSubConnector[0] = c
1169 .getSubConnectors()[0];
1171 return c;
1172 } else if (returnSelectedSubConnector != null) {
1174 final SubConnectorSpec[] scs = c.getSubConnectors();
1175 if (scs == null || scs.length == 0) {
1176 continue;
1178 for (SubConnectorSpec sc : scs) {
1179 if (name.endsWith(sc.getName())) {
1180 returnSelectedSubConnector[0] = sc;
1181 return c;
1188 return null;
1192 * Get {@link ConnectorSpec}s by capabilities and/or status.
1194 * @param capabilities
1195 * capabilities needed
1196 * @param status
1197 * status required {@link SubConnectorSpec}
1198 * @return {@link ConnectorSpec}s
1200 static final ConnectorSpec[] getConnectors(final short capabilities,
1201 final short status) {
1202 synchronized (CONNECTORS) {
1203 final ArrayList<ConnectorSpec> ret = new ArrayList<ConnectorSpec>(
1204 CONNECTORS.size());
1205 final int l = CONNECTORS.size();
1206 for (int i = 0; i < l; i++) {
1207 final ConnectorSpec c = CONNECTORS.get(i);
1208 if (c.hasCapabilities(capabilities) && c.hasStatus(status)) {
1209 ret.add(c);
1212 return ret.toArray(new ConnectorSpec[0]);