use common strings
[andGMXsms.git] / src / de / ub0r / android / websms / WebSMS.java
blobafd1943997bef9fc478fcd6e6f49615d0e01d18d
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: update balace on start. */
90 private static final String PREFS_AUTOUPDATE = "autoupdate";
91 /** Preference's name: exit after sending. */
92 private static final String PREFS_AUTOEXIT = "autoexit";
93 /** Preference's name: show mobile numbers only. */
94 private static final String PREFS_MOBILES_ONLY = "mobiles_only";
95 /** Preference's name: vibrate on failed sending. */
96 static final String PREFS_FAIL_VIBRATE = "fail_vibrate";
97 /** Preference's name: sound on failed sending. */
98 static final String PREFS_FAIL_SOUND = "fail_sound";
99 /** Preferemce's name: enable change connector button. */
100 private static final String PREFS_CHANGE_CONNECTOR_BUTTON = // .
101 "change_connector_button";
102 /** Preferemce's name: hide cancel button. */
103 private static final String PREFS_HIDE_CANCEL_BUTTON = "hide_cancel_button";
105 /** Preference's name: to. */
106 private static final String PREFS_TO = "to";
107 /** Preference's name: text. */
108 private static final String PREFS_TEXT = "text";
109 /** Preference's name: selected {@link ConnectorSpec} ID. */
110 private static final String PREFS_CONNECTOR_ID = "connector_id";
111 /** Preference's name: selected {@link SubConnectorSpec} ID. */
112 private static final String PREFS_SUBCONNECTOR_ID = "subconnector_id";
114 /** Sleep before autoexit. */
115 private static final int SLEEP_BEFORE_EXIT = 75;
117 /** Preferences: hide ads. */
118 private static boolean prefsNoAds = false;
119 /** Hased IMEI. */
120 private static String imeiHash = null;
121 /** Preferences: selected {@link ConnectorSpec}. */
122 private static ConnectorSpec prefsConnectorSpec = null;
123 /** Preferences: selected {@link SubConnectorSpec}. */
124 private static SubConnectorSpec prefsSubConnectorSpec = null;
125 /** Save prefsConnectorSpec.getID() here. */
126 private static String prefsConnectorID = null;
128 /** List of available {@link ConnectorSpec}s. */
129 private static final ArrayList<ConnectorSpec> CONNECTORS = // .
130 new ArrayList<ConnectorSpec>();
132 /** Array of md5(prefsSender) for which no ads should be displayed. */
133 private static final String[] NO_AD_HASHS = {
134 "43dcb861b9588fb733300326b61dbab9", // 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.
156 "9f01eae4eaecd9158a2caddc04bad77e", // andreas p.
159 /** true if preferences got opened. */
160 static boolean doPreferences = false;
162 /** Dialog: about. */
163 private static final int DIALOG_ABOUT = 0;
164 /** Dialog: updates. */
165 private static final int DIALOG_UPDATE = 2;
166 /** Dialog: 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 /** Intent's extra for error messages. */
178 static final String EXTRA_ERRORMESSAGE = // .
179 "de.ub0r.android.intent.extra.ERRORMESSAGE";
181 /** Persistent Message store. */
182 private static String lastMsg = null;
183 /** Persistent Recipient store. */
184 private static String lastTo = null;
185 /** Backup for params: custom sender. */
186 private static String lastCustomSender = null;
187 /** Backup for params: send later. */
188 private static long lastSendLater = -1;
190 /** {@link MultiAutoCompleteTextView} holding recipients. */
191 private MultiAutoCompleteTextView etTo;
192 /** {@link EditText} holding text. */
193 private EditText etText;
194 /** {@link TextView} holding balances. */
195 private TextView tvBalances;
197 /** Helper for API 5. */
198 static HelperAPI5Contacts helperAPI5c = null;
200 /** Text's label. */
201 private TextView etTextLabel;
203 /** Show extras. */
204 private boolean showExtras = false;
206 /** TextWatcher updating char count on writing. */
207 private TextWatcher textWatcher = new TextWatcher() {
209 * {@inheritDoc}
211 public void afterTextChanged(final Editable s) {
212 int[] l = SmsMessage.calculateLength(s, false);
213 WebSMS.this.etTextLabel.setText(l[0] + "/" + l[2]);
216 /** Needed dummy. */
217 public void beforeTextChanged(final CharSequence s, final int start,
218 final int count, final int after) {
221 /** Needed dummy. */
222 public void onTextChanged(final CharSequence s, final int start,
223 final int before, final int count) {
228 * Parse data pushed by {@link Intent}.
230 * @param intent
231 * {@link Intent}
233 private void parseIntent(final Intent intent) {
234 final String action = intent.getAction();
235 if (action == null) {
236 return;
239 // launched by clicking a sms: link, target number is in URI.
240 final Uri uri = intent.getData();
241 if (uri != null) {
242 final String scheme = uri.getScheme();
243 if (scheme.equals("sms") || scheme.equals("smsto")) {
244 String s = uri.getSchemeSpecificPart();
245 if (s != null) {
246 s = s.trim();
247 if (s.endsWith(",")) {
248 s = s.substring(0, s.length() - 1).trim();
250 if (s.indexOf('<') < 0) {
251 // try to fetch recipient's name from phonebook
252 String n = null;
253 if (helperAPI5c != null) {
254 try {
255 n = helperAPI5c.getNameForNumber(this, s);
256 } catch (NoClassDefFoundError e) {
257 helperAPI5c = null;
260 if (helperAPI5c == null) {
261 Cursor c = this.managedQuery(Phones.CONTENT_URI,
262 new String[] { PhonesColumns.NUMBER,
263 PeopleColumns.// .
264 DISPLAY_NAME },
265 PhonesColumns.NUMBER + " = '" + s + "'",
266 null, null);
267 if (c.moveToFirst()) {
268 n = c.getString(c.getColumnIndex(// .
269 PeopleColumns.DISPLAY_NAME));
272 if (n != null) {
273 s = n + " <" + s + ">, ";
276 ((EditText) this.findViewById(R.id.to)).setText(s);
277 lastTo = s;
279 final Bundle extras = intent.getExtras();
280 if (extras != null) {
281 s = extras.getCharSequence(Intent.EXTRA_TEXT).toString();
282 if (s != null) {
283 ((EditText) this.findViewById(R.id.text)).setText(s);
284 lastMsg = s;
286 s = extras.getString(EXTRA_ERRORMESSAGE);
287 if (s != null) {
288 Toast.makeText(this, s, Toast.LENGTH_LONG).show();
291 if (!prefsNoAds) {
292 // do not display any ads for donators
293 // display ads
294 ((AdView) WebSMS.this.findViewById(R.id.ad))
295 .setVisibility(View.VISIBLE);
302 * {@inheritDoc}
304 @Override
305 public final void onCreate(final Bundle savedInstanceState) {
306 super.onCreate(savedInstanceState);
307 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
309 // save ref to me.
310 me = this;
311 try {
312 WebSMS.helperAPI5c = new HelperAPI5Contacts();
313 if (!helperAPI5c.isAvailable()) {
314 WebSMS.helperAPI5c = null;
316 } catch (VerifyError e) {
317 WebSMS.helperAPI5c = null;
318 Log.d(TAG, "no api5 running", e);
320 // Restore preferences
321 final SharedPreferences p = PreferenceManager
322 .getDefaultSharedPreferences(this);
323 // inflate XML
324 this.setContentView(R.layout.main);
326 this.etTo = (MultiAutoCompleteTextView) this.findViewById(R.id.to);
327 this.etText = (EditText) this.findViewById(R.id.text);
328 this.etTextLabel = (TextView) this.findViewById(R.id.text_);
329 this.tvBalances = (TextView) this.findViewById(R.id.freecount);
331 // display changelog?
332 String v0 = p.getString(PREFS_LAST_RUN, "");
333 String v1 = this.getResources().getString(R.string.app_version);
334 if (!v0.equals(v1)) {
335 SharedPreferences.Editor editor = p.edit();
336 editor.putString(PREFS_LAST_RUN, v1);
337 editor.commit();
338 this.showDialog(DIALOG_UPDATE);
341 this.reloadPrefs();
343 lastTo = p.getString(PREFS_TO, "");
344 lastMsg = p.getString(PREFS_TEXT, "");
346 // register Listener
347 this.findViewById(R.id.send_).setOnClickListener(this);
348 this.findViewById(R.id.cancel).setOnClickListener(this);
349 this.findViewById(R.id.change_connector).setOnClickListener(this);
350 this.findViewById(R.id.extras).setOnClickListener(this);
351 this.findViewById(R.id.custom_sender).setOnClickListener(this);
352 this.findViewById(R.id.send_later).setOnClickListener(this);
353 this.tvBalances.setOnClickListener(this);
354 this.etText.addTextChangedListener(this.textWatcher);
355 this.etTo.setAdapter(new MobilePhoneAdapter(this));
356 this.etTo.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
357 this.etTo.requestFocus();
359 this.parseIntent(this.getIntent());
361 // check default prefix
362 if (!p.getString(PREFS_DEFPREFIX, "").startsWith("+")) {
363 WebSMS.this.log(R.string.log_wrong_defprefix);
365 if (p.getBoolean(PREFS_AUTOUPDATE, false)) {
366 this.updateFreecount(false);
371 * {@inheritDoc}
373 @Override
374 protected final void onNewIntent(final Intent intent) {
375 super.onNewIntent(intent);
376 this.parseIntent(intent);
380 * {@inheritDoc}
382 @Override
383 protected final void onResume() {
384 super.onResume();
385 // set free sms count
386 this.updateBalance();
388 // if coming from prefs..
389 if (doPreferences) {
390 this.reloadPrefs();
391 doPreferences = false;
392 final SharedPreferences p = PreferenceManager
393 .getDefaultSharedPreferences(this);
394 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
395 final String defSender = p.getString(PREFS_SENDER, "");
396 final ConnectorSpec[] css = getConnectors(
397 ConnectorSpec.CAPABILITIES_BOOTSTRAP, // .
398 ConnectorSpec.STATUS_ENABLED);
399 for (ConnectorSpec cs : css) {
400 final Intent intent = new Intent(cs.getPackage()
401 + Connector.ACTION_RUN_BOOTSTRAP);
402 ConnectorCommand.bootstrap(defPrefix, defSender).setToIntent(
403 intent);
404 Log.d(TAG, "send broadcast: " + intent.getAction());
405 this.sendBroadcast(intent);
409 this.setButtons();
411 // reload text/recipient from local store
412 if (lastMsg != null) {
413 this.etText.setText(lastMsg);
414 } else {
415 this.etText.setText("");
417 if (lastTo != null) {
418 this.etTo.setText(lastTo);
419 } else {
420 this.etTo.setText("");
423 if (lastTo != null && lastTo.length() > 0) {
424 this.etText.requestFocus();
425 } else {
426 this.etTo.requestFocus();
429 // query for connectors
430 final Intent i = new Intent(Connector.ACTION_CONNECTOR_UPDATE);
431 Log.d(TAG, "send broadcast: " + i.getAction());
432 this.sendBroadcast(i);
436 * Update balance.
438 private void updateBalance() {
439 final StringBuilder buf = new StringBuilder();
440 final ConnectorSpec[] css = getConnectors(
441 ConnectorSpec.CAPABILITIES_UPDATE, // .
442 ConnectorSpec.STATUS_ENABLED);
443 for (ConnectorSpec cs : css) {
444 final String b = cs.getBalance();
445 if (b == null || b.length() == 0) {
446 continue;
448 if (buf.length() > 0) {
449 buf.append(", ");
451 buf.append(cs.getName());
452 buf.append(": ");
453 buf.append(b);
456 this.tvBalances.setText(this.getString(R.string.free_) + " "
457 + buf.toString() + " "
458 + this.getString(R.string.click_for_update));
462 * {@inheritDoc}
464 @Override
465 public final void onPause() {
466 super.onPause();
467 // store input data to persitent stores
468 lastMsg = this.etText.getText().toString();
469 lastTo = this.etTo.getText().toString();
471 // store input data to preferences
472 final Editor editor = PreferenceManager.getDefaultSharedPreferences(
473 this).edit();
474 // common
475 editor.putString(PREFS_TO, lastTo);
476 editor.putString(PREFS_TEXT, lastMsg);
477 // commit changes
478 editor.commit();
480 this.savePreferences();
484 * Read static variables holding preferences.
486 private void reloadPrefs() {
487 final SharedPreferences p = PreferenceManager
488 .getDefaultSharedPreferences(this);
489 boolean b = p.getBoolean(PREFS_CHANGE_CONNECTOR_BUTTON, false);
490 View v = this.findViewById(R.id.change_connector);
491 if (b) {
492 v.setVisibility(View.VISIBLE);
493 } else {
494 v.setVisibility(View.GONE);
497 b = !p.getBoolean(PREFS_HIDE_CANCEL_BUTTON, false);
498 v = this.findViewById(R.id.cancel);
499 if (b) {
500 v.setVisibility(View.VISIBLE);
501 } else {
502 v.setVisibility(View.GONE);
505 prefsConnectorID = p.getString(PREFS_CONNECTOR_ID, "");
506 prefsConnectorSpec = getConnectorByID(prefsConnectorID);
507 if (prefsConnectorSpec != null) {
508 prefsSubConnectorSpec = prefsConnectorSpec.getSubConnector(p
509 .getString(PREFS_SUBCONNECTOR_ID, ""));
512 MobilePhoneAdapter.setMoileNubersObly(p.getBoolean(PREFS_MOBILES_ONLY,
513 false));
515 prefsNoAds = false;
516 String hash = Utils.md5(p.getString(PREFS_SENDER, ""));
517 for (String h : NO_AD_HASHS) {
518 if (hash.equals(h)) {
519 prefsNoAds = true;
520 break;
523 if (!prefsNoAds && this.getImeiHash() != null) {
524 for (String h : NO_AD_HASHS) {
525 if (imeiHash.equals(h)) {
526 prefsNoAds = true;
527 break;
532 this.setButtons();
536 * Show/hide, enable/disable send buttons.
538 private void setButtons() {
539 // final ConnectorSpec[] enabled = getConnectors(
540 // ConnectorSpec.CAPABILITIES_SEND, ConnectorSpec.STATUS_ENABLED);
542 Button btn = (Button) this.findViewById(R.id.send_);
543 // show/hide buttons
544 btn.setEnabled(prefsConnectorSpec != null
545 && prefsSubConnectorSpec != null);
546 btn.setVisibility(View.VISIBLE);
548 if (prefsConnectorSpec != null && prefsSubConnectorSpec != null) {
549 final boolean sFlashsms = prefsSubConnectorSpec
550 .hasFeatures(SubConnectorSpec.FEATURE_FLASHSMS);
551 final boolean sCustomsender = prefsSubConnectorSpec
552 .hasFeatures(SubConnectorSpec.FEATURE_CUSTOMSENDER);
553 final boolean sSendLater = prefsSubConnectorSpec
554 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER);
555 if (sFlashsms || sCustomsender || sSendLater) {
556 this.findViewById(R.id.extras).setVisibility(View.VISIBLE);
557 } else {
558 this.findViewById(R.id.extras).setVisibility(View.GONE);
560 if (this.showExtras && sFlashsms) {
561 this.findViewById(R.id.flashsms).setVisibility(View.VISIBLE);
562 } else {
563 this.findViewById(R.id.flashsms).setVisibility(View.GONE);
565 if (this.showExtras && sCustomsender) {
566 this.findViewById(R.id.custom_sender).setVisibility(
567 View.VISIBLE);
568 } else {
569 this.findViewById(R.id.custom_sender).setVisibility(View.GONE);
571 if (this.showExtras && sSendLater) {
572 this.findViewById(R.id.send_later).setVisibility(View.VISIBLE);
573 } else {
574 this.findViewById(R.id.send_later).setVisibility(View.GONE);
577 String t = this.getString(R.string.app_name) + " - "
578 + prefsConnectorSpec.getName();
579 if (prefsSubConnectorSpec != null
580 && prefsConnectorSpec.getSubConnectorCount() > 1) {
581 t += " - " + prefsSubConnectorSpec.getName();
583 this.setTitle(t);
588 * Resets persistent store.
590 private void reset() {
591 this.etText.setText("");
592 this.etTo.setText("");
593 lastMsg = null;
594 lastTo = null;
595 lastCustomSender = null;
596 lastSendLater = -1;
597 // save user preferences
598 SharedPreferences.Editor editor = PreferenceManager
599 .getDefaultSharedPreferences(this).edit();
600 editor.putString(PREFS_TO, "");
601 editor.putString(PREFS_TEXT, "");
602 // commit changes
603 editor.commit();
606 /** Save prefs. */
607 final void savePreferences() {
608 if (prefsConnectorSpec != null) {
609 PreferenceManager.getDefaultSharedPreferences(this).edit()
610 .putString(PREFS_CONNECTOR_ID, prefsConnectorSpec.getID())
611 .commit();
616 * Run Connector.update().
618 * @param forceUpdate
619 * force update, if false only blank balances will get updated
621 private void updateFreecount(final boolean forceUpdate) {
622 final SharedPreferences p = PreferenceManager
623 .getDefaultSharedPreferences(this);
624 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
625 final String defSender = p.getString(PREFS_SENDER, "");
626 final ConnectorSpec[] css = getConnectors(
627 ConnectorSpec.CAPABILITIES_UPDATE, // .
628 ConnectorSpec.STATUS_ENABLED);
629 for (ConnectorSpec cs : css) {
630 if (!forceUpdate && cs.getBalance() != null) {
631 // skip unnecessary updates
632 continue;
634 final Intent intent = new Intent(cs.getPackage()
635 + Connector.ACTION_RUN_UPDATE);
636 ConnectorCommand.update(defPrefix, defSender).setToIntent(intent);
637 Log.d(TAG, "send broadcast: " + intent.getAction());
638 this.sendBroadcast(intent);
643 * {@inheritDoc}
645 public final void onClick(final View v) {
646 switch (v.getId()) {
647 case R.id.freecount:
648 this.updateFreecount(true);
649 return;
650 case R.id.send_:
651 this.send(prefsConnectorSpec, WebSMS.getSelectedSubConnectorID());
652 return;
653 case R.id.cancel:
654 this.reset();
655 return;
656 case R.id.change_connector:
657 this.changeConnectorMenu();
658 return;
659 case R.id.extras:
660 this.showExtras = !this.showExtras;
661 this.setButtons();
662 return;
663 case R.id.custom_sender:
664 final CheckBox cs = (CheckBox) this
665 .findViewById(R.id.custom_sender);
666 if (cs.isChecked()) {
667 this.showDialog(DIALOG_CUSTOMSENDER);
668 } else {
669 lastCustomSender = null;
671 return;
672 case R.id.send_later:
673 final CheckBox sl = (CheckBox) this.findViewById(R.id.send_later);
674 if (sl.isChecked()) {
675 this.showDialog(DIALOG_SENDLATER_DATE);
676 } else {
677 lastSendLater = -1;
679 return;
680 default:
681 return;
686 * {@inheritDoc}
688 @Override
689 public final boolean onCreateOptionsMenu(final Menu menu) {
690 MenuInflater inflater = this.getMenuInflater();
691 inflater.inflate(R.menu.menu, menu);
692 if (prefsNoAds) {
693 menu.removeItem(R.id.item_donate);
695 return true;
699 * Display "change connector" menu.
701 private void changeConnectorMenu() {
702 AlertDialog.Builder builder = new AlertDialog.Builder(this);
703 builder.setTitle(R.string.change_connector_);
704 final ArrayList<String> items = new ArrayList<String>();
705 final ConnectorSpec[] css = getConnectors(
706 ConnectorSpec.CAPABILITIES_SEND, ConnectorSpec.STATUS_ENABLED);
707 for (ConnectorSpec cs : css) {
708 final SubConnectorSpec[] scs = cs.getSubConnectors();
709 if (scs.length <= 1) {
710 items.add(cs.getName());
711 } else {
712 final String n = cs.getName() + " - ";
713 for (SubConnectorSpec sc : scs) {
714 items.add(n + sc.getName());
719 builder.setItems(items.toArray(new String[0]),
720 new DialogInterface.OnClickListener() {
721 public void onClick(final DialogInterface d, // .
722 final int item) {
723 final SubConnectorSpec[] ret = ConnectorSpec
724 .getSubConnectorReturnArray();
725 prefsConnectorSpec = getConnectorByName(
726 items.get(item), ret);
727 prefsSubConnectorSpec = ret[0];
728 WebSMS.this.setButtons();
729 // save user preferences
730 final Editor e = PreferenceManager
731 .getDefaultSharedPreferences(WebSMS.this)
732 .edit();
733 e.putString(PREFS_CONNECTOR_ID, prefsConnectorSpec
734 .getID());
735 e.putString(PREFS_SUBCONNECTOR_ID,
736 prefsSubConnectorSpec.getID());
737 e.commit();
740 builder.create().show();
744 *{@inheritDoc}
746 @Override
747 public final boolean onOptionsItemSelected(final MenuItem item) {
748 switch (item.getItemId()) {
749 case R.id.item_about: // start about dialog
750 this.showDialog(DIALOG_ABOUT);
751 return true;
752 case R.id.item_settings: // start settings activity
753 this.startActivity(new Intent(this, Preferences.class));
754 return true;
755 case R.id.item_donate:
756 this.showDialog(DIALOG_PREDONATE);
757 return true;
758 case R.id.item_more:
759 try {
760 this.startActivity(new Intent(Intent.ACTION_VIEW, Uri
761 .parse("market://search?q=pub:\"Felix Bechstein\"")));
762 } catch (ActivityNotFoundException e) {
763 Log.e(TAG, "no market", e);
765 return true;
766 case R.id.item_connector:
767 this.changeConnectorMenu();
768 return true;
769 default:
770 return false;
775 * {@inheritDoc}
777 @Override
778 protected final Dialog onCreateDialog(final int id) {
779 Dialog d;
780 AlertDialog.Builder builder;
781 switch (id) {
782 case DIALOG_PREDONATE:
783 builder = new AlertDialog.Builder(this);
784 builder.setIcon(R.drawable.ic_menu_star);
785 builder.setTitle(R.string.donate_);
786 builder.setMessage(R.string.predonate);
787 builder.setPositiveButton(R.string.donate_,
788 new DialogInterface.OnClickListener() {
789 public void onClick(final DialogInterface dialog,
790 final int which) {
791 try {
792 WebSMS.this.startActivity(new Intent(
793 Intent.ACTION_VIEW, Uri.parse(// .
794 WebSMS.this.getString(// .
795 R.string.donate_url))));
796 } catch (ActivityNotFoundException e) {
797 Log.e(TAG, "no browser", e);
798 } finally {
799 WebSMS.this.showDialog(DIALOG_POSTDONATE);
803 builder.setNegativeButton(android.R.string.cancel, null);
804 return builder.create();
805 case DIALOG_POSTDONATE:
806 builder = new AlertDialog.Builder(this);
807 builder.setIcon(R.drawable.ic_menu_star);
808 builder.setTitle(R.string.remove_ads_);
809 builder.setMessage(R.string.postdonate);
810 builder.setPositiveButton(R.string.send_,
811 new DialogInterface.OnClickListener() {
812 public void onClick(final DialogInterface dialog,
813 final int which) {
814 final Intent in = new Intent(Intent.ACTION_SEND);
815 in.putExtra(Intent.EXTRA_EMAIL, new String[] {
816 WebSMS.this.getString(// .
817 R.string.donate_mail), "" });
818 // FIXME: "" is a k9 hack. This is fixed in market
819 // on 26.01.10. wait some more time..
820 in.putExtra(Intent.EXTRA_TEXT, WebSMS.this
821 .getImeiHash());
822 in.putExtra(Intent.EXTRA_SUBJECT, WebSMS.this
823 .getString(// .
824 R.string.app_name)
825 + " " + WebSMS.this.getString(// .
826 R.string.donate_subject));
827 in.setType("text/plain");
828 WebSMS.this.startActivity(in);
831 builder.setNegativeButton(android.R.string.cancel, null);
832 return builder.create();
833 case DIALOG_ABOUT:
834 d = new Dialog(this);
835 d.setContentView(R.layout.about);
836 d.setTitle(this.getString(R.string.about_) + " v"
837 + this.getString(R.string.app_version));
838 StringBuffer authors = new StringBuffer();
839 final ConnectorSpec[] css = getConnectors(
840 ConnectorSpec.CAPABILITIES_NONE,
841 ConnectorSpec.STATUS_INACTIVE);
842 for (ConnectorSpec cs : css) {
843 final String a = cs.getAuthor();
844 if (a != null && a.length() > 0) {
845 authors.append(cs.getName());
846 authors.append(":\t");
847 authors.append(a);
848 authors.append("\n");
851 ((TextView) d.findViewById(R.id.author_connectors)).setText(authors
852 .toString().trim());
853 return d;
854 case DIALOG_UPDATE:
855 builder = new AlertDialog.Builder(this);
856 builder.setTitle(R.string.changelog_);
857 final String[] changes = this.getResources().getStringArray(
858 R.array.updates);
859 final StringBuilder buf = new StringBuilder(changes[0]);
860 for (int i = 1; i < changes.length; i++) {
861 buf.append("\n\n");
862 buf.append(changes[i]);
864 builder.setIcon(android.R.drawable.ic_menu_info_details);
865 builder.setMessage(buf.toString());
866 builder.setCancelable(true);
867 builder.setPositiveButton(android.R.string.ok, null);
868 return builder.create();
869 case DIALOG_CUSTOMSENDER:
870 builder = new AlertDialog.Builder(this);
871 builder.setTitle(R.string.custom_sender);
872 builder.setCancelable(true);
873 final EditText et = new EditText(this);
874 builder.setView(et);
875 builder.setPositiveButton(android.R.string.ok,
876 new DialogInterface.OnClickListener() {
877 public void onClick(final DialogInterface dialog,
878 final int id) {
879 WebSMS.lastCustomSender = et.getText().toString();
882 builder.setNegativeButton(android.R.string.cancel, null);
883 return builder.create();
884 case DIALOG_SENDLATER_DATE:
885 Calendar c = Calendar.getInstance();
886 return new DatePickerDialog(this, this, c.get(Calendar.YEAR), c
887 .get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
888 case DIALOG_SENDLATER_TIME:
889 c = Calendar.getInstance();
890 return new MyTimePickerDialog(this, this, c
891 .get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true);
892 default:
893 return null;
898 * Log text.
900 * @param text
901 * text as resID
903 public final void log(final int text) {
904 this.log(this.getString(text));
908 * Log text.
910 * @param text
911 * text
913 public final void log(final String text) {
914 try {
915 Toast.makeText(this.getApplicationContext(), text,
916 Toast.LENGTH_LONG).show();
917 } catch (RuntimeException e) {
918 Log.e(TAG, null, e);
923 * Send text to {@link Connector}.
925 * @param connector
926 * which connector should be used.
927 * @param command
928 * {@link ConnectorCommand} to push to connector
930 private void send(final ConnectorSpec connector,
931 final ConnectorCommand command) {
932 try {
933 final Intent intent = new Intent(connector.getPackage()
934 + Connector.ACTION_RUN_SEND);
935 command.setToIntent(intent);
936 prefsConnectorSpec.setToIntent(intent);
937 connector.addStatus(ConnectorSpec.STATUS_SENDING);
938 Log.d(TAG, "send broadcast: " + intent.getAction());
939 this.sendBroadcast(intent);
940 } catch (Exception e) {
941 Log.e(TAG, null, e);
942 } finally {
943 this.reset();
944 if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
945 PREFS_AUTOEXIT, false)) {
946 try {
947 Thread.sleep(SLEEP_BEFORE_EXIT);
948 } catch (InterruptedException e) {
949 Log.e(TAG, null, e);
951 this.finish();
957 * Send text.
959 * @param connector
960 * which connector should be used.
961 * @param subconnector
962 * selected {@link SubConnectorSpec} ID
964 private void send(final ConnectorSpec connector, // .
965 final String subconnector) {
966 // fetch text/recipient
967 final String to = this.etTo.getText().toString();
968 final String text = this.etText.getText().toString();
969 if (to.length() == 0 || text.length() == 0) {
970 return;
973 if (!prefsNoAds) {
974 // do not display any ads for donators
975 // display ads
976 ((AdView) WebSMS.this.findViewById(R.id.ad))
977 .setVisibility(View.VISIBLE);
980 CheckBox v = (CheckBox) this.findViewById(R.id.flashsms);
981 final boolean flashSMS = (v.getVisibility() == View.VISIBLE)
982 && v.isEnabled() && v.isChecked();
983 final SharedPreferences p = PreferenceManager
984 .getDefaultSharedPreferences(this);
985 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
986 final String defSender = p.getString(PREFS_SENDER, "");
988 final ConnectorCommand command = ConnectorCommand.send(subconnector,
989 defPrefix, defSender, to.split(","), text, flashSMS);
990 command.setCustomSender(lastCustomSender);
991 command.setSendLater(lastSendLater);
992 this.send(connector, command);
996 * @return ID of selected {@link SubConnectorSpec}
998 private static String getSelectedSubConnectorID() {
999 if (prefsSubConnectorSpec == null) {
1000 return null;
1002 return prefsSubConnectorSpec.getID();
1006 * A Date was set.
1008 * @param view
1009 * DatePicker View
1010 * @param year
1011 * year set
1012 * @param monthOfYear
1013 * month set
1014 * @param dayOfMonth
1015 * day set
1017 public final void onDateSet(final DatePicker view, final int year,
1018 final int monthOfYear, final int dayOfMonth) {
1019 final Calendar c = Calendar.getInstance();
1020 if (lastSendLater > 0) {
1021 c.setTimeInMillis(lastSendLater);
1023 c.set(Calendar.YEAR, year);
1024 c.set(Calendar.MONTH, monthOfYear);
1025 c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
1026 lastSendLater = c.getTimeInMillis();
1028 MyTimePickerDialog.setOnlyQuaters(prefsSubConnectorSpec
1029 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER_QUARTERS));
1030 this.showDialog(DIALOG_SENDLATER_TIME);
1034 * A Time was set.
1036 * @param view
1037 * TimePicker View
1038 * @param hour
1039 * hour set
1040 * @param minutes
1041 * minutes set
1043 public final void onTimeSet(final TimePicker view, final int hour,
1044 final int minutes) {
1045 if (prefsSubConnectorSpec
1046 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER_QUARTERS)
1047 && minutes % 15 != 0) {
1048 Toast.makeText(this, R.string.error_sendlater_quater,
1049 Toast.LENGTH_LONG).show();
1050 return;
1053 final Calendar c = Calendar.getInstance();
1054 if (lastSendLater > 0) {
1055 c.setTimeInMillis(lastSendLater);
1057 c.set(Calendar.HOUR_OF_DAY, hour);
1058 c.set(Calendar.MINUTE, minutes);
1059 lastSendLater = c.getTimeInMillis();
1063 * Get MD5 hash of the IMEI (device id).
1065 * @return MD5 hash of IMEI
1067 private String getImeiHash() {
1068 if (imeiHash == null) {
1069 // get imei
1070 TelephonyManager mTelephonyMgr = (TelephonyManager) this
1071 .getSystemService(TELEPHONY_SERVICE);
1072 final String did = mTelephonyMgr.getDeviceId();
1073 if (did != null) {
1074 imeiHash = Utils.md5(did);
1077 return imeiHash;
1081 * Add or update a {@link ConnectorSpec}.
1083 * @param connector
1084 * connector
1086 public static final void addConnector(final ConnectorSpec connector) {
1087 synchronized (CONNECTORS) {
1088 if (connector == null || connector.getID() == null
1089 || connector.getName() == null) {
1090 return;
1092 ConnectorSpec c = getConnectorByID(connector.getID());
1093 if (c != null) {
1094 c.update(connector);
1095 } else {
1096 final int l = CONNECTORS.size();
1097 final String name = connector.getName();
1098 Log.d(TAG, "add connector with id: " + connector.getID());
1099 Log.d(TAG, "add connector with name: " + name);
1100 boolean added = false;
1101 try {
1102 for (int i = 0; i < l; i++) {
1103 final ConnectorSpec cs = CONNECTORS.get(i);
1104 if (name.compareToIgnoreCase(cs.getName()) < 0) {
1105 CONNECTORS.add(i, connector);
1106 added = true;
1107 break;
1110 } catch (NullPointerException e) {
1111 Log.e(TAG, "error while sorting", e);
1113 if (!added) {
1114 CONNECTORS.add(connector);
1116 c = connector;
1118 if (prefsConnectorSpec == null
1119 && prefsConnectorID.equals(connector.getID())) {
1120 prefsConnectorSpec = connector;
1121 final SharedPreferences p = PreferenceManager
1122 .getDefaultSharedPreferences(me);
1123 prefsSubConnectorSpec = connector.getSubConnector(p
1124 .getString(PREFS_SUBCONNECTOR_ID, ""));
1125 me.setButtons();
1128 final String b = c.getBalance();
1129 final String ob = c.getOldBalance();
1130 if (b != null && (ob == null || !b.equals(ob))) {
1131 me.updateBalance();
1137 * Get {@link ConnectorSpec} by ID.
1139 * @param id
1140 * ID
1141 * @return {@link ConnectorSpec}
1143 private static ConnectorSpec getConnectorByID(final String id) {
1144 synchronized (CONNECTORS) {
1145 if (id == null) {
1146 return null;
1148 final int l = CONNECTORS.size();
1149 for (int i = 0; i < l; i++) {
1150 final ConnectorSpec c = CONNECTORS.get(i);
1151 if (id.equals(c.getID())) {
1152 return c;
1156 return null;
1160 * Get {@link ConnectorSpec} by name.
1162 * @param name
1163 * name
1164 * @param returnSelectedSubConnector
1165 * if not null, array[0] will be set to selected
1166 * {@link SubConnectorSpec}
1167 * @return {@link ConnectorSpec}
1169 private static ConnectorSpec getConnectorByName(final String name,
1170 final SubConnectorSpec[] returnSelectedSubConnector) {
1171 synchronized (CONNECTORS) {
1172 if (name == null) {
1173 return null;
1175 final int l = CONNECTORS.size();
1176 for (int i = 0; i < l; i++) {
1177 final ConnectorSpec c = CONNECTORS.get(i);
1178 final String n = c.getName();
1179 if (name.startsWith(n)) {
1180 if (name.length() == n.length()) {
1181 if (returnSelectedSubConnector != null) {
1182 returnSelectedSubConnector[0] = c
1183 .getSubConnectors()[0];
1185 return c;
1186 } else if (returnSelectedSubConnector != null) {
1188 final SubConnectorSpec[] scs = c.getSubConnectors();
1189 if (scs == null || scs.length == 0) {
1190 continue;
1192 for (SubConnectorSpec sc : scs) {
1193 if (name.endsWith(sc.getName())) {
1194 returnSelectedSubConnector[0] = sc;
1195 return c;
1202 return null;
1206 * Get {@link ConnectorSpec}s by capabilities and/or status.
1208 * @param capabilities
1209 * capabilities needed
1210 * @param status
1211 * status required {@link SubConnectorSpec}
1212 * @return {@link ConnectorSpec}s
1214 static final ConnectorSpec[] getConnectors(final short capabilities,
1215 final short status) {
1216 synchronized (CONNECTORS) {
1217 final ArrayList<ConnectorSpec> ret = new ArrayList<ConnectorSpec>(
1218 CONNECTORS.size());
1219 final int l = CONNECTORS.size();
1220 for (int i = 0; i < l; i++) {
1221 final ConnectorSpec c = CONNECTORS.get(i);
1222 if (c.hasCapabilities(capabilities) && c.hasStatus(status)) {
1223 ret.add(c);
1226 return ret.toArray(new ConnectorSpec[0]);