put some code into methods
[andGMXsms.git] / src / de / ub0r / android / websms / WebSMS.java
blob453c88efbc8ea85564dff3ad6e40ea4ec40bb57d
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.ViewGroup;
52 import android.view.Window;
53 import android.view.View.OnClickListener;
54 import android.widget.AdapterView;
55 import android.widget.BaseAdapter;
56 import android.widget.Button;
57 import android.widget.CheckBox;
58 import android.widget.DatePicker;
59 import android.widget.EditText;
60 import android.widget.GridView;
61 import android.widget.ImageView;
62 import android.widget.MultiAutoCompleteTextView;
63 import android.widget.TextView;
64 import android.widget.TimePicker;
65 import android.widget.Toast;
66 import android.widget.AdapterView.OnItemClickListener;
68 import com.admob.android.ads.AdView;
70 import de.ub0r.android.websms.connector.common.Connector;
71 import de.ub0r.android.websms.connector.common.ConnectorCommand;
72 import de.ub0r.android.websms.connector.common.ConnectorSpec;
73 import de.ub0r.android.websms.connector.common.Utils;
74 import de.ub0r.android.websms.connector.common.ConnectorSpec.SubConnectorSpec;
76 /**
77 * Main Activity.
79 * @author flx
81 @SuppressWarnings("deprecation")
82 public class WebSMS extends Activity implements OnClickListener,
83 OnDateSetListener, OnTimeSetListener {
84 /** Tag for output. */
85 private static final String TAG = "WebSMS";
87 /** Static reference to running Activity. */
88 private static WebSMS me;
89 /** Preference's name: last version run. */
90 private static final String PREFS_LAST_RUN = "lastrun";
91 /** Preference's name: user's phonenumber. */
92 static final String PREFS_SENDER = "sender";
93 /** Preference's name: default prefix. */
94 static final String PREFS_DEFPREFIX = "defprefix";
95 /** Preference's name: update balace on start. */
96 private static final String PREFS_AUTOUPDATE = "autoupdate";
97 /** Preference's name: exit after sending. */
98 private static final String PREFS_AUTOEXIT = "autoexit";
99 /** Preference's name: show mobile numbers only. */
100 private static final String PREFS_MOBILES_ONLY = "mobiles_only";
101 /** Preference's name: vibrate on failed sending. */
102 static final String PREFS_FAIL_VIBRATE = "fail_vibrate";
103 /** Preference's name: sound on failed sending. */
104 static final String PREFS_FAIL_SOUND = "fail_sound";
105 /** Preferemce's name: enable change connector button. */
106 private static final String PREFS_HIDE_CHANGE_CONNECTOR_BUTTON = // .
107 "hide_change_connector_button";
108 /** Preferemce's name: hide emoticons button. */
109 private static final String PREFS_HIDE_EMO_BUTTON = "hide_emo_button";
110 /** Preferemce's name: hide cancel button. */
111 private static final String PREFS_HIDE_CANCEL_BUTTON = "hide_cancel_button";
113 /** Preference's name: to. */
114 private static final String PREFS_TO = "to";
115 /** Preference's name: text. */
116 private static final String PREFS_TEXT = "text";
117 /** Preference's name: selected {@link ConnectorSpec} ID. */
118 private static final String PREFS_CONNECTOR_ID = "connector_id";
119 /** Preference's name: selected {@link SubConnectorSpec} ID. */
120 private static final String PREFS_SUBCONNECTOR_ID = "subconnector_id";
122 /** Sleep before autoexit. */
123 private static final int SLEEP_BEFORE_EXIT = 75;
125 /** Preferences: hide ads. */
126 private static boolean prefsNoAds = false;
127 /** Hased IMEI. */
128 private static String imeiHash = null;
129 /** Preferences: selected {@link ConnectorSpec}. */
130 private static ConnectorSpec prefsConnectorSpec = null;
131 /** Preferences: selected {@link SubConnectorSpec}. */
132 private static SubConnectorSpec prefsSubConnectorSpec = null;
133 /** Save prefsConnectorSpec.getID() here. */
134 private static String prefsConnectorID = null;
136 /** List of available {@link ConnectorSpec}s. */
137 private static final ArrayList<ConnectorSpec> CONNECTORS = // .
138 new ArrayList<ConnectorSpec>();
140 /** Array of md5(prefsSender) for which no ads should be displayed. */
141 private static final String[] NO_AD_HASHS = {
142 "43dcb861b9588fb733300326b61dbab9", // flx
143 "57a3c7c19329fd84c2252a9b2866dd93", // mirweb
144 "10b7a2712beee096acbc67416d7d71a1", // mo
145 "f6b3b72300e918436b4c4c9fdf909e8c", // joerg s.
146 "4c18f7549b643045f0ff69f61e8f7e72", // frank j.
147 "7684154558d19383552388d9bc92d446", // henning k.
148 "64c7414288e9a9b57a33e034f384ed30", // dominik l.
149 "c479a2e701291c751f0f91426bcaabf3", // bernhard g.
150 "ae7dfedf549f98a349ad8c2068473c6b", // dominik k.-v.
151 "18bc29cd511613552861da6ef51766ce", // niels b.
152 "2985011f56d0049b0f4f0caed3581123", // sven l.
153 "64724033da297a915a89023b11ac2e47", // wilfried m.
154 "cfd8d2efb3eac39705bd62c4dfe5e72d", // achim e.
155 "ca56e7518fdbda832409ef07edd4c273", // michael s.
156 "bed2f068ca8493da4179807d1afdbd83", // axel q.
157 "4c35400c4fa3ffe2aefcf1f9131eb855", // gerhard s.
158 "02158d2a80b1ef9c4d684a4ca808b93d", // camilo s.
159 "1177c6e67f98cdfed6c84d99e85d30de", // daniel p.
160 "3f082dd7e21d5c64f34a69942c474ce7", // andre j.
161 "5383540b2f8c298532f874126b021e73", // marco a.
162 "858ddfb8635d1539884086dca2726468", // lado
163 "6e8bbb35091219a80e278ae61f31cce9", // mario s.
164 "9f01eae4eaecd9158a2caddc04bad77e", // andreas p.
167 /** true if preferences got opened. */
168 static boolean doPreferences = false;
170 /** Dialog: about. */
171 private static final int DIALOG_ABOUT = 0;
172 /** Dialog: updates. */
173 private static final int DIALOG_UPDATE = 2;
174 /** Dialog: custom sender. */
175 private static final int DIALOG_CUSTOMSENDER = 3;
176 /** Dialog: send later: date. */
177 private static final int DIALOG_SENDLATER_DATE = 4;
178 /** Dialog: send later: time. */
179 private static final int DIALOG_SENDLATER_TIME = 5;
180 /** Dialog: pre donate. */
181 private static final int DIALOG_PREDONATE = 6;
182 /** Dialog: post donate. */
183 private static final int DIALOG_POSTDONATE = 7;
184 /** Dialog: emo. */
185 private static final int DIALOG_EMO = 8;
187 /** Size of the emoticons png. */
188 private static final int EMOTICONS_SIZE = 30;
190 /** Intent's extra for error messages. */
191 static final String EXTRA_ERRORMESSAGE = // .
192 "de.ub0r.android.intent.extra.ERRORMESSAGE";
194 /** Persistent Message store. */
195 private static String lastMsg = null;
196 /** Persistent Recipient store. */
197 private static String lastTo = null;
198 /** Backup for params: custom sender. */
199 private static String lastCustomSender = null;
200 /** Backup for params: send later. */
201 private static long lastSendLater = -1;
203 /** {@link MultiAutoCompleteTextView} holding recipients. */
204 private MultiAutoCompleteTextView etTo;
205 /** {@link EditText} holding text. */
206 private EditText etText;
207 /** {@link TextView} holding balances. */
208 private TextView tvBalances;
210 /** Helper for API 5. */
211 static HelperAPI5Contacts helperAPI5c = null;
213 /** Text's label. */
214 private TextView etTextLabel;
216 /** Show extras. */
217 private boolean showExtras = false;
219 /** TextWatcher updating char count on writing. */
220 private TextWatcher textWatcher = new TextWatcher() {
222 * {@inheritDoc}
224 public void afterTextChanged(final Editable s) {
225 int[] l = SmsMessage.calculateLength(s, false);
226 WebSMS.this.etTextLabel.setText(l[0] + "/" + l[2]);
229 /** Needed dummy. */
230 public void beforeTextChanged(final CharSequence s, final int start,
231 final int count, final int after) {
234 /** Needed dummy. */
235 public void onTextChanged(final CharSequence s, final int start,
236 final int before, final int count) {
241 * Parse data pushed by {@link Intent}.
243 * @param intent
244 * {@link Intent}
246 private void parseIntent(final Intent intent) {
247 final String action = intent.getAction();
248 if (action == null) {
249 return;
252 // launched by clicking a sms: link, target number is in URI.
253 final Uri uri = intent.getData();
254 if (uri != null) {
255 final String scheme = uri.getScheme();
256 if (scheme.equals("sms") || scheme.equals("smsto")) {
257 String s = uri.getSchemeSpecificPart();
258 if (s != null) {
259 s = s.trim();
260 if (s.endsWith(",")) {
261 s = s.substring(0, s.length() - 1).trim();
263 if (s.indexOf('<') < 0) {
264 // try to fetch recipient's name from phonebook
265 String n = null;
266 if (helperAPI5c != null) {
267 try {
268 n = helperAPI5c.getNameForNumber(this, s);
269 } catch (NoClassDefFoundError e) {
270 helperAPI5c = null;
273 if (helperAPI5c == null) {
274 Cursor c = this.managedQuery(Phones.CONTENT_URI,
275 new String[] { PhonesColumns.NUMBER,
276 PeopleColumns.// .
277 DISPLAY_NAME },
278 PhonesColumns.NUMBER + " = '" + s + "'",
279 null, null);
280 if (c.moveToFirst()) {
281 n = c.getString(c.getColumnIndex(// .
282 PeopleColumns.DISPLAY_NAME));
285 if (n != null) {
286 s = n + " <" + s + ">, ";
289 ((EditText) this.findViewById(R.id.to)).setText(s);
290 lastTo = s;
292 final Bundle extras = intent.getExtras();
293 if (extras != null) {
294 s = extras.getCharSequence(Intent.EXTRA_TEXT).toString();
295 if (s != null) {
296 ((EditText) this.findViewById(R.id.text)).setText(s);
297 lastMsg = s;
299 s = extras.getString(EXTRA_ERRORMESSAGE);
300 if (s != null) {
301 Toast.makeText(this, s, Toast.LENGTH_LONG).show();
304 if (!prefsNoAds) {
305 // do not display any ads for donators
306 // display ads
307 ((AdView) WebSMS.this.findViewById(R.id.ad))
308 .setVisibility(View.VISIBLE);
315 * {@inheritDoc}
317 @Override
318 public final void onCreate(final Bundle savedInstanceState) {
319 super.onCreate(savedInstanceState);
320 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
322 // save ref to me.
323 me = this;
324 try {
325 WebSMS.helperAPI5c = new HelperAPI5Contacts();
326 if (!helperAPI5c.isAvailable()) {
327 WebSMS.helperAPI5c = null;
329 } catch (VerifyError e) {
330 WebSMS.helperAPI5c = null;
331 Log.d(TAG, "no api5 running", e);
333 // Restore preferences
334 final SharedPreferences p = PreferenceManager
335 .getDefaultSharedPreferences(this);
336 // inflate XML
337 this.setContentView(R.layout.main);
339 this.etTo = (MultiAutoCompleteTextView) this.findViewById(R.id.to);
340 this.etText = (EditText) this.findViewById(R.id.text);
341 this.etTextLabel = (TextView) this.findViewById(R.id.text_);
342 this.tvBalances = (TextView) this.findViewById(R.id.freecount);
344 // display changelog?
345 String v0 = p.getString(PREFS_LAST_RUN, "");
346 String v1 = this.getResources().getString(R.string.app_version);
347 if (!v0.equals(v1)) {
348 SharedPreferences.Editor editor = p.edit();
349 editor.putString(PREFS_LAST_RUN, v1);
350 editor.commit();
351 this.showDialog(DIALOG_UPDATE);
354 this.reloadPrefs();
356 lastTo = p.getString(PREFS_TO, "");
357 lastMsg = p.getString(PREFS_TEXT, "");
359 // register Listener
360 this.findViewById(R.id.send_).setOnClickListener(this);
361 this.findViewById(R.id.cancel).setOnClickListener(this);
362 this.findViewById(R.id.change_connector).setOnClickListener(this);
363 this.findViewById(R.id.change_connector_u).setOnClickListener(this);
364 this.findViewById(R.id.extras).setOnClickListener(this);
365 this.findViewById(R.id.custom_sender).setOnClickListener(this);
366 this.findViewById(R.id.send_later).setOnClickListener(this);
367 this.findViewById(R.id.emo).setOnClickListener(this);
368 this.findViewById(R.id.emo_u).setOnClickListener(this);
369 this.tvBalances.setOnClickListener(this);
370 this.etText.addTextChangedListener(this.textWatcher);
371 this.etTo.setAdapter(new MobilePhoneAdapter(this));
372 this.etTo.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
373 this.etTo.requestFocus();
375 this.parseIntent(this.getIntent());
377 // check default prefix
378 if (!p.getString(PREFS_DEFPREFIX, "").startsWith("+")) {
379 WebSMS.this.log(R.string.log_wrong_defprefix);
384 * {@inheritDoc}
386 @Override
387 protected final void onNewIntent(final Intent intent) {
388 super.onNewIntent(intent);
389 this.parseIntent(intent);
393 * {@inheritDoc}
395 @Override
396 protected final void onResume() {
397 super.onResume();
398 // set free sms count
399 this.updateBalance();
401 // if coming from prefs..
402 if (doPreferences) {
403 this.reloadPrefs();
404 doPreferences = false;
405 final SharedPreferences p = PreferenceManager
406 .getDefaultSharedPreferences(this);
407 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
408 final String defSender = p.getString(PREFS_SENDER, "");
409 final ConnectorSpec[] css = getConnectors(
410 ConnectorSpec.CAPABILITIES_BOOTSTRAP, // .
411 (short) (ConnectorSpec.STATUS_ENABLED | // .
412 ConnectorSpec.STATUS_READY));
413 for (ConnectorSpec cs : css) {
414 runCommand(this, cs, ConnectorCommand.bootstrap(defPrefix,
415 defSender));
419 this.setButtons();
421 // reload text/recipient from local store
422 if (lastMsg != null) {
423 this.etText.setText(lastMsg);
424 } else {
425 this.etText.setText("");
427 if (lastTo != null) {
428 this.etTo.setText(lastTo);
429 } else {
430 this.etTo.setText("");
433 if (lastTo != null && lastTo.length() > 0) {
434 this.etText.requestFocus();
435 } else {
436 this.etTo.requestFocus();
439 // query for connectors
440 final Intent i = new Intent(Connector.ACTION_CONNECTOR_UPDATE);
441 Log.d(TAG, "send broadcast: " + i.getAction());
442 this.sendBroadcast(i);
446 * Update balance.
448 private void updateBalance() {
449 final StringBuilder buf = new StringBuilder();
450 final ConnectorSpec[] css = getConnectors(
451 ConnectorSpec.CAPABILITIES_UPDATE, // .
452 ConnectorSpec.STATUS_ENABLED);
453 for (ConnectorSpec cs : css) {
454 final String b = cs.getBalance();
455 if (b == null || b.length() == 0) {
456 continue;
458 if (buf.length() > 0) {
459 buf.append(", ");
461 buf.append(cs.getName());
462 buf.append(": ");
463 buf.append(b);
466 this.tvBalances.setText(this.getString(R.string.free_) + " "
467 + buf.toString() + " "
468 + this.getString(R.string.click_for_update));
472 * {@inheritDoc}
474 @Override
475 public final void onPause() {
476 super.onPause();
477 // store input data to persitent stores
478 lastMsg = this.etText.getText().toString();
479 lastTo = this.etTo.getText().toString();
481 // store input data to preferences
482 final Editor editor = PreferenceManager.getDefaultSharedPreferences(
483 this).edit();
484 // common
485 editor.putString(PREFS_TO, lastTo);
486 editor.putString(PREFS_TEXT, lastMsg);
487 // commit changes
488 editor.commit();
490 this.savePreferences();
494 * Read static variables holding preferences.
496 private void reloadPrefs() {
497 final SharedPreferences p = PreferenceManager
498 .getDefaultSharedPreferences(this);
499 final boolean bShowChangeConnector = !p.getBoolean(
500 PREFS_HIDE_CHANGE_CONNECTOR_BUTTON, false);
501 final boolean bShowEmoticons = !p.getBoolean(PREFS_HIDE_EMO_BUTTON,
502 false);
503 final boolean bShowCancel = !p.getBoolean(PREFS_HIDE_CANCEL_BUTTON,
504 false);
506 if (bShowChangeConnector && bShowEmoticons && bShowCancel) {
507 this.findViewById(R.id.upper).setVisibility(View.VISIBLE);
508 this.findViewById(R.id.change_connector).setVisibility(View.GONE);
509 this.findViewById(R.id.emo).setVisibility(View.GONE);
510 } else {
511 this.findViewById(R.id.upper).setVisibility(View.GONE);
513 View v = this.findViewById(R.id.change_connector);
514 if (bShowChangeConnector) {
515 v.setVisibility(View.VISIBLE);
516 } else {
517 v.setVisibility(View.GONE);
520 v = this.findViewById(R.id.emo);
521 if (bShowEmoticons) {
522 v.setVisibility(View.VISIBLE);
523 } else {
524 v.setVisibility(View.GONE);
527 v = this.findViewById(R.id.cancel);
528 if (bShowCancel) {
529 v.setVisibility(View.VISIBLE);
530 } else {
531 v.setVisibility(View.GONE);
535 prefsConnectorID = p.getString(PREFS_CONNECTOR_ID, "");
536 prefsConnectorSpec = getConnectorByID(prefsConnectorID);
537 if (prefsConnectorSpec != null) {
538 prefsSubConnectorSpec = prefsConnectorSpec.getSubConnector(p
539 .getString(PREFS_SUBCONNECTOR_ID, ""));
542 MobilePhoneAdapter.setMoileNubersObly(p.getBoolean(PREFS_MOBILES_ONLY,
543 false));
545 prefsNoAds = false;
546 String hash = Utils.md5(p.getString(PREFS_SENDER, ""));
547 for (String h : NO_AD_HASHS) {
548 if (hash.equals(h)) {
549 prefsNoAds = true;
550 break;
553 if (!prefsNoAds && this.getImeiHash() != null) {
554 for (String h : NO_AD_HASHS) {
555 if (imeiHash.equals(h)) {
556 prefsNoAds = true;
557 break;
562 this.setButtons();
566 * Show/hide, enable/disable send buttons.
568 private void setButtons() {
569 Button btn = (Button) this.findViewById(R.id.send_);
570 // show/hide buttons
571 btn.setEnabled(prefsConnectorSpec != null
572 && prefsSubConnectorSpec != null);
573 btn.setVisibility(View.VISIBLE);
575 if (prefsConnectorSpec != null && prefsSubConnectorSpec != null) {
576 final boolean sFlashsms = prefsSubConnectorSpec
577 .hasFeatures(SubConnectorSpec.FEATURE_FLASHSMS);
578 final boolean sCustomsender = prefsSubConnectorSpec
579 .hasFeatures(SubConnectorSpec.FEATURE_CUSTOMSENDER);
580 final boolean sSendLater = prefsSubConnectorSpec
581 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER);
582 if (sFlashsms || sCustomsender || sSendLater) {
583 this.findViewById(R.id.extras).setVisibility(View.VISIBLE);
584 } else {
585 this.findViewById(R.id.extras).setVisibility(View.GONE);
587 if (this.showExtras && sFlashsms) {
588 this.findViewById(R.id.flashsms).setVisibility(View.VISIBLE);
589 } else {
590 this.findViewById(R.id.flashsms).setVisibility(View.GONE);
592 if (this.showExtras && sCustomsender) {
593 this.findViewById(R.id.custom_sender).setVisibility(
594 View.VISIBLE);
595 } else {
596 this.findViewById(R.id.custom_sender).setVisibility(View.GONE);
598 if (this.showExtras && sSendLater) {
599 this.findViewById(R.id.send_later).setVisibility(View.VISIBLE);
600 } else {
601 this.findViewById(R.id.send_later).setVisibility(View.GONE);
604 String t = this.getString(R.string.app_name) + " - "
605 + prefsConnectorSpec.getName();
606 if (prefsSubConnectorSpec != null
607 && prefsConnectorSpec.getSubConnectorCount() > 1) {
608 t += " - " + prefsSubConnectorSpec.getName();
610 this.setTitle(t);
615 * Resets persistent store.
617 private void reset() {
618 this.etText.setText("");
619 this.etTo.setText("");
620 lastMsg = null;
621 lastTo = null;
622 lastCustomSender = null;
623 lastSendLater = -1;
624 // save user preferences
625 SharedPreferences.Editor editor = PreferenceManager
626 .getDefaultSharedPreferences(this).edit();
627 editor.putString(PREFS_TO, "");
628 editor.putString(PREFS_TEXT, "");
629 // commit changes
630 editor.commit();
633 /** Save prefs. */
634 final void savePreferences() {
635 if (prefsConnectorSpec != null) {
636 PreferenceManager.getDefaultSharedPreferences(this).edit()
637 .putString(PREFS_CONNECTOR_ID, prefsConnectorSpec.getID())
638 .commit();
643 * Run Connector.doUpdate().
645 private void updateFreecount() {
646 final SharedPreferences p = PreferenceManager
647 .getDefaultSharedPreferences(this);
648 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
649 final String defSender = p.getString(PREFS_SENDER, "");
650 final ConnectorSpec[] css = getConnectors(
651 ConnectorSpec.CAPABILITIES_UPDATE, // .
652 (short) (ConnectorSpec.STATUS_ENABLED | // .
653 ConnectorSpec.STATUS_READY));
654 for (ConnectorSpec cs : css) {
655 if (cs.isRunning()) {
656 // skip running connectors
657 continue;
659 runCommand(this, cs, ConnectorCommand.update(defPrefix, defSender));
664 * Send a command as broadcast.
666 * @param context
667 * WebSMS required for performance issues
668 * @param connector
669 * {@link ConnectorSpec}
670 * @param command
671 * {@link ConnectorCommand}
673 static final void runCommand(final WebSMS context,
674 final ConnectorSpec connector, final ConnectorCommand command) {
675 final Intent intent = command.setToIntent(null);
676 switch (command.getType()) {
677 case ConnectorCommand.TYPE_BOOTSTRAP:
678 intent.setAction(connector.getPackage()
679 + Connector.ACTION_RUN_BOOTSTRAP);
680 connector.addStatus(ConnectorSpec.STATUS_BOOTSTRAPPING);
681 break;
682 case ConnectorCommand.TYPE_SEND:
683 intent.setAction(connector.getPackage() // .
684 + Connector.ACTION_RUN_SEND);
685 connector.addStatus(ConnectorSpec.STATUS_SENDING);
686 break;
687 case ConnectorCommand.TYPE_UPDATE:
688 intent.setAction(connector.getPackage()
689 + Connector.ACTION_RUN_UPDATE);
690 connector.addStatus(ConnectorSpec.STATUS_UPDATING);
691 break;
692 default:
693 break;
695 Log.d(TAG, "send broadcast: " + intent.getAction());
696 context.sendBroadcast(intent);
700 * {@inheritDoc}
702 public final void onClick(final View v) {
703 switch (v.getId()) {
704 case R.id.freecount:
705 this.updateFreecount();
706 return;
707 case R.id.send_:
708 this.send(prefsConnectorSpec, WebSMS.getSelectedSubConnectorID());
709 return;
710 case R.id.cancel:
711 this.reset();
712 return;
713 case R.id.change_connector:
714 case R.id.change_connector_u:
715 this.changeConnectorMenu();
716 return;
717 case R.id.extras:
718 this.showExtras = !this.showExtras;
719 this.setButtons();
720 return;
721 case R.id.custom_sender:
722 final CheckBox cs = (CheckBox) this
723 .findViewById(R.id.custom_sender);
724 if (cs.isChecked()) {
725 this.showDialog(DIALOG_CUSTOMSENDER);
726 } else {
727 lastCustomSender = null;
729 return;
730 case R.id.send_later:
731 final CheckBox sl = (CheckBox) this.findViewById(R.id.send_later);
732 if (sl.isChecked()) {
733 this.showDialog(DIALOG_SENDLATER_DATE);
734 } else {
735 lastSendLater = -1;
737 return;
738 case R.id.emo:
739 case R.id.emo_u:
740 this.showDialog(DIALOG_EMO);
741 return;
742 default:
743 return;
748 * {@inheritDoc}
750 @Override
751 public final boolean onCreateOptionsMenu(final Menu menu) {
752 MenuInflater inflater = this.getMenuInflater();
753 inflater.inflate(R.menu.menu, menu);
754 if (prefsNoAds) {
755 menu.removeItem(R.id.item_donate);
757 return true;
761 * Display "change connector" menu.
763 private void changeConnectorMenu() {
764 AlertDialog.Builder builder = new AlertDialog.Builder(this);
765 builder.setTitle(R.string.change_connector_);
766 final ArrayList<String> items = new ArrayList<String>();
767 final ConnectorSpec[] css = getConnectors(
768 ConnectorSpec.CAPABILITIES_SEND, ConnectorSpec.STATUS_ENABLED);
769 for (ConnectorSpec cs : css) {
770 final SubConnectorSpec[] scs = cs.getSubConnectors();
771 if (scs.length <= 1) {
772 items.add(cs.getName());
773 } else {
774 final String n = cs.getName() + " - ";
775 for (SubConnectorSpec sc : scs) {
776 items.add(n + sc.getName());
781 builder.setItems(items.toArray(new String[0]),
782 new DialogInterface.OnClickListener() {
783 public void onClick(final DialogInterface d, // .
784 final int item) {
785 final SubConnectorSpec[] ret = ConnectorSpec
786 .getSubConnectorReturnArray();
787 prefsConnectorSpec = getConnectorByName(
788 items.get(item), ret);
789 prefsSubConnectorSpec = ret[0];
790 WebSMS.this.setButtons();
791 // save user preferences
792 final Editor e = PreferenceManager
793 .getDefaultSharedPreferences(WebSMS.this)
794 .edit();
795 e.putString(PREFS_CONNECTOR_ID, prefsConnectorSpec
796 .getID());
797 e.putString(PREFS_SUBCONNECTOR_ID,
798 prefsSubConnectorSpec.getID());
799 e.commit();
802 builder.create().show();
806 *{@inheritDoc}
808 @Override
809 public final boolean onOptionsItemSelected(final MenuItem item) {
810 switch (item.getItemId()) {
811 case R.id.item_about: // start about dialog
812 this.showDialog(DIALOG_ABOUT);
813 return true;
814 case R.id.item_settings: // start settings activity
815 this.startActivity(new Intent(this, Preferences.class));
816 return true;
817 case R.id.item_donate:
818 this.showDialog(DIALOG_PREDONATE);
819 return true;
820 case R.id.item_more:
821 try {
822 this.startActivity(new Intent(Intent.ACTION_VIEW, Uri
823 .parse("market://search?q=pub:\"Felix Bechstein\"")));
824 } catch (ActivityNotFoundException e) {
825 Log.e(TAG, "no market", e);
827 return true;
828 case R.id.item_connector:
829 this.changeConnectorMenu();
830 return true;
831 default:
832 return false;
837 * Create a Emoticons {@link Dialog}.
839 * @return Emoticons {@link Dialog}
841 private Dialog createEmoticonsDialog() {
842 final Dialog d = new Dialog(this);
843 d.setTitle(R.string.emo_);
844 d.setContentView(R.layout.emo);
845 d.setCancelable(true);
846 final GridView gridview = (GridView) d.findViewById(R.id.gridview);
847 gridview.setAdapter(new BaseAdapter() {
848 // references to our images
849 private Integer[] mThumbIds = { R.drawable.emo_im_angel,
850 R.drawable.emo_im_cool, R.drawable.emo_im_crying,
851 R.drawable.emo_im_foot_in_mouth, R.drawable.emo_im_happy,
852 R.drawable.emo_im_kissing, R.drawable.emo_im_laughing,
853 R.drawable.emo_im_lips_are_sealed,
854 R.drawable.emo_im_money_mouth, R.drawable.emo_im_sad,
855 R.drawable.emo_im_surprised,
856 R.drawable.emo_im_tongue_sticking_out,
857 R.drawable.emo_im_undecided, R.drawable.emo_im_winking,
858 R.drawable.emo_im_wtf, R.drawable.emo_im_yelling };
860 @Override
861 public long getItemId(final int position) {
862 return 0;
865 @Override
866 public Object getItem(final int position) {
867 return null;
870 @Override
871 public int getCount() {
872 return this.mThumbIds.length;
875 @Override
876 public View getView(final int position, final View convertView,
877 final ViewGroup parent) {
878 ImageView imageView;
879 if (convertView == null) { // if it's not recycled,
880 // initialize some attributes
881 imageView = new ImageView(WebSMS.this);
882 imageView.setLayoutParams(new GridView.LayoutParams(
883 EMOTICONS_SIZE, EMOTICONS_SIZE));
884 imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
885 // imageView.setPadding(0, 0, 0, 0);
886 } else {
887 imageView = (ImageView) convertView;
890 imageView.setImageResource(this.mThumbIds[position]);
891 return imageView;
894 gridview.setOnItemClickListener(new OnItemClickListener() {
895 /** Emoticon id: angel. */
896 private static final int EMO_ANGEL = 0;
897 /** Emoticon id: cool. */
898 private static final int EMO_COOL = 1;
899 /** Emoticon id: crying. */
900 private static final int EMO_CRYING = 2;
901 /** Emoticon id: foot in mouth. */
902 private static final int EMO_FOOT_IN_MOUTH = 3;
903 /** Emoticon id: happy. */
904 private static final int EMO_HAPPY = 4;
905 /** Emoticon id: kissing. */
906 private static final int EMO_KISSING = 5;
907 /** Emoticon id: laughing. */
908 private static final int EMO_LAUGHING = 6;
909 /** Emoticon id: lips are sealed. */
910 private static final int EMO_LIPS_SEALED = 7;
911 /** Emoticon id: money. */
912 private static final int EMO_MONEY = 8;
913 /** Emoticon id: sad. */
914 private static final int EMO_SAD = 9;
915 /** Emoticon id: suprised. */
916 private static final int EMO_SUPRISED = 10;
917 /** Emoticon id: tongue sticking out. */
918 private static final int EMO_TONGUE = 11;
919 /** Emoticon id: undecided. */
920 private static final int EMO_UNDICIDED = 12;
921 /** Emoticon id: winking. */
922 private static final int EMO_WINKING = 13;
923 /** Emoticon id: wtf. */
924 private static final int EMO_WTF = 14;
925 /** Emoticon id: yell. */
926 private static final int EMO_YELL = 15;
928 @Override
929 public void onItemClick(final AdapterView<?> adapter, final View v,
930 final int id, final long arg3) {
931 EditText et = WebSMS.this.etText;
932 String e = null;
933 switch (id) {
934 case EMO_ANGEL:
935 e = "O:-)";
936 break;
937 case EMO_COOL:
938 e = "8-)";
939 break;
940 case EMO_CRYING:
941 e = ";-)";
942 break;
943 case EMO_FOOT_IN_MOUTH:
944 e = ":-?";
945 break;
946 case EMO_HAPPY:
947 e = ":-)";
948 break;
949 case EMO_KISSING:
950 e = ":-*";
951 break;
952 case EMO_LAUGHING:
953 e = ":-D";
954 break;
955 case EMO_LIPS_SEALED:
956 e = ":-X";
957 break;
958 case EMO_MONEY:
959 e = ":-$";
960 break;
961 case EMO_SAD:
962 e = ":-(";
963 break;
964 case EMO_SUPRISED:
965 e = ":o";
966 break;
967 case EMO_TONGUE:
968 e = ":-P";
969 break;
970 case EMO_UNDICIDED:
971 e = ":-\\";
972 break;
973 case EMO_WINKING:
974 e = ";-)";
975 break;
976 case EMO_WTF:
977 e = "o.O";
978 break;
979 case EMO_YELL:
980 e = ":O";
981 break;
982 default:
983 break;
985 et.setText(et.getText() + e);
986 d.dismiss();
989 return d;
993 * {@inheritDoc}
995 @Override
996 protected final Dialog onCreateDialog(final int id) {
997 Dialog d;
998 AlertDialog.Builder builder;
999 switch (id) {
1000 case DIALOG_PREDONATE:
1001 builder = new AlertDialog.Builder(this);
1002 builder.setIcon(R.drawable.ic_menu_star);
1003 builder.setTitle(R.string.donate_);
1004 builder.setMessage(R.string.predonate);
1005 builder.setPositiveButton(R.string.donate_,
1006 new DialogInterface.OnClickListener() {
1007 public void onClick(final DialogInterface dialog,
1008 final int which) {
1009 try {
1010 WebSMS.this.startActivity(new Intent(
1011 Intent.ACTION_VIEW, Uri.parse(// .
1012 WebSMS.this.getString(// .
1013 R.string.donate_url))));
1014 } catch (ActivityNotFoundException e) {
1015 Log.e(TAG, "no browser", e);
1016 } finally {
1017 WebSMS.this.showDialog(DIALOG_POSTDONATE);
1021 builder.setNegativeButton(android.R.string.cancel, null);
1022 return builder.create();
1023 case DIALOG_POSTDONATE:
1024 builder = new AlertDialog.Builder(this);
1025 builder.setIcon(R.drawable.ic_menu_star);
1026 builder.setTitle(R.string.remove_ads_);
1027 builder.setMessage(R.string.postdonate);
1028 builder.setPositiveButton(R.string.send_,
1029 new DialogInterface.OnClickListener() {
1030 public void onClick(final DialogInterface dialog,
1031 final int which) {
1032 final Intent in = new Intent(Intent.ACTION_SEND);
1033 in.putExtra(Intent.EXTRA_EMAIL, new String[] {
1034 WebSMS.this.getString(// .
1035 R.string.donate_mail), "" });
1036 // FIXME: "" is a k9 hack. This is fixed in market
1037 // on 26.01.10. wait some more time..
1038 in.putExtra(Intent.EXTRA_TEXT, WebSMS.this
1039 .getImeiHash());
1040 in.putExtra(Intent.EXTRA_SUBJECT, WebSMS.this
1041 .getString(// .
1042 R.string.app_name)
1043 + " " + WebSMS.this.getString(// .
1044 R.string.donate_subject));
1045 in.setType("text/plain");
1046 WebSMS.this.startActivity(in);
1049 builder.setNegativeButton(android.R.string.cancel, null);
1050 return builder.create();
1051 case DIALOG_ABOUT:
1052 d = new Dialog(this);
1053 d.setContentView(R.layout.about);
1054 d.setTitle(this.getString(R.string.about_) + " v"
1055 + this.getString(R.string.app_version));
1056 StringBuffer authors = new StringBuffer();
1057 final ConnectorSpec[] css = getConnectors(
1058 ConnectorSpec.CAPABILITIES_NONE,
1059 ConnectorSpec.STATUS_INACTIVE);
1060 for (ConnectorSpec cs : css) {
1061 final String a = cs.getAuthor();
1062 if (a != null && a.length() > 0) {
1063 authors.append(cs.getName());
1064 authors.append(":\t");
1065 authors.append(a);
1066 authors.append("\n");
1069 ((TextView) d.findViewById(R.id.author_connectors)).setText(authors
1070 .toString().trim());
1071 return d;
1072 case DIALOG_UPDATE:
1073 builder = new AlertDialog.Builder(this);
1074 builder.setTitle(R.string.changelog_);
1075 final String[] changes = this.getResources().getStringArray(
1076 R.array.updates);
1077 final StringBuilder buf = new StringBuilder(changes[0]);
1078 for (int i = 1; i < changes.length; i++) {
1079 buf.append("\n\n");
1080 buf.append(changes[i]);
1082 builder.setIcon(android.R.drawable.ic_menu_info_details);
1083 builder.setMessage(buf.toString());
1084 builder.setCancelable(true);
1085 builder.setPositiveButton(android.R.string.ok, null);
1086 return builder.create();
1087 case DIALOG_CUSTOMSENDER:
1088 builder = new AlertDialog.Builder(this);
1089 builder.setTitle(R.string.custom_sender);
1090 builder.setCancelable(true);
1091 final EditText et = new EditText(this);
1092 builder.setView(et);
1093 builder.setPositiveButton(android.R.string.ok,
1094 new DialogInterface.OnClickListener() {
1095 public void onClick(final DialogInterface dialog,
1096 final int id) {
1097 WebSMS.lastCustomSender = et.getText().toString();
1100 builder.setNegativeButton(android.R.string.cancel, null);
1101 return builder.create();
1102 case DIALOG_SENDLATER_DATE:
1103 Calendar c = Calendar.getInstance();
1104 return new DatePickerDialog(this, this, c.get(Calendar.YEAR), c
1105 .get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
1106 case DIALOG_SENDLATER_TIME:
1107 c = Calendar.getInstance();
1108 return new MyTimePickerDialog(this, this, c
1109 .get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true);
1110 case DIALOG_EMO:
1111 return this.createEmoticonsDialog();
1112 default:
1113 return null;
1118 * Log text.
1120 * @param text
1121 * text as resID
1123 public final void log(final int text) {
1124 this.log(this.getString(text));
1128 * Log text.
1130 * @param text
1131 * text
1133 public final void log(final String text) {
1134 try {
1135 Toast.makeText(this.getApplicationContext(), text,
1136 Toast.LENGTH_LONG).show();
1137 } catch (RuntimeException e) {
1138 Log.e(TAG, null, e);
1143 * Send text.
1145 * @param connector
1146 * which connector should be used.
1147 * @param subconnector
1148 * selected {@link SubConnectorSpec} ID
1150 private void send(final ConnectorSpec connector, // .
1151 final String subconnector) {
1152 // fetch text/recipient
1153 final String to = this.etTo.getText().toString();
1154 final String text = this.etText.getText().toString();
1155 if (to.length() == 0 || text.length() == 0) {
1156 return;
1159 if (!prefsNoAds) {
1160 // do not display any ads for donators
1161 // display ads
1162 ((AdView) WebSMS.this.findViewById(R.id.ad))
1163 .setVisibility(View.VISIBLE);
1166 CheckBox v = (CheckBox) this.findViewById(R.id.flashsms);
1167 final boolean flashSMS = (v.getVisibility() == View.VISIBLE)
1168 && v.isEnabled() && v.isChecked();
1169 final SharedPreferences p = PreferenceManager
1170 .getDefaultSharedPreferences(this);
1171 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
1172 final String defSender = p.getString(PREFS_SENDER, "");
1174 final ConnectorCommand command = ConnectorCommand.send(subconnector,
1175 defPrefix, defSender, to.split(","), text, flashSMS);
1176 command.setCustomSender(lastCustomSender);
1177 command.setSendLater(lastSendLater);
1179 try {
1180 runCommand(this, connector, command);
1181 } catch (Exception e) {
1182 Log.e(TAG, null, e);
1183 } finally {
1184 this.reset();
1185 if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
1186 PREFS_AUTOEXIT, false)) {
1187 try {
1188 Thread.sleep(SLEEP_BEFORE_EXIT);
1189 } catch (InterruptedException e) {
1190 Log.e(TAG, null, e);
1192 this.finish();
1198 * @return ID of selected {@link SubConnectorSpec}
1200 private static String getSelectedSubConnectorID() {
1201 if (prefsSubConnectorSpec == null) {
1202 return null;
1204 return prefsSubConnectorSpec.getID();
1208 * A Date was set.
1210 * @param view
1211 * DatePicker View
1212 * @param year
1213 * year set
1214 * @param monthOfYear
1215 * month set
1216 * @param dayOfMonth
1217 * day set
1219 public final void onDateSet(final DatePicker view, final int year,
1220 final int monthOfYear, final int dayOfMonth) {
1221 final Calendar c = Calendar.getInstance();
1222 if (lastSendLater > 0) {
1223 c.setTimeInMillis(lastSendLater);
1225 c.set(Calendar.YEAR, year);
1226 c.set(Calendar.MONTH, monthOfYear);
1227 c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
1228 lastSendLater = c.getTimeInMillis();
1230 MyTimePickerDialog.setOnlyQuaters(prefsSubConnectorSpec
1231 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER_QUARTERS));
1232 this.showDialog(DIALOG_SENDLATER_TIME);
1236 * A Time was set.
1238 * @param view
1239 * TimePicker View
1240 * @param hour
1241 * hour set
1242 * @param minutes
1243 * minutes set
1245 public final void onTimeSet(final TimePicker view, final int hour,
1246 final int minutes) {
1247 if (prefsSubConnectorSpec
1248 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER_QUARTERS)
1249 && minutes % 15 != 0) {
1250 Toast.makeText(this, R.string.error_sendlater_quater,
1251 Toast.LENGTH_LONG).show();
1252 return;
1255 final Calendar c = Calendar.getInstance();
1256 if (lastSendLater > 0) {
1257 c.setTimeInMillis(lastSendLater);
1259 c.set(Calendar.HOUR_OF_DAY, hour);
1260 c.set(Calendar.MINUTE, minutes);
1261 lastSendLater = c.getTimeInMillis();
1265 * Get MD5 hash of the IMEI (device id).
1267 * @return MD5 hash of IMEI
1269 private String getImeiHash() {
1270 if (imeiHash == null) {
1271 // get imei
1272 TelephonyManager mTelephonyMgr = (TelephonyManager) this
1273 .getSystemService(TELEPHONY_SERVICE);
1274 final String did = mTelephonyMgr.getDeviceId();
1275 if (did != null) {
1276 imeiHash = Utils.md5(did);
1279 return imeiHash;
1283 * Add or update a {@link ConnectorSpec}.
1285 * @param connector
1286 * connector
1288 static final void addConnector(final ConnectorSpec connector) {
1289 synchronized (CONNECTORS) {
1290 if (connector == null || connector.getID() == null
1291 || connector.getName() == null) {
1292 return;
1294 ConnectorSpec c = getConnectorByID(connector.getID());
1295 if (c != null) {
1296 c.update(connector);
1297 } else {
1298 final int l = CONNECTORS.size();
1299 final String name = connector.getName();
1300 Log.d(TAG, "add connector with id: " + connector.getID());
1301 Log.d(TAG, "add connector with name: " + name);
1302 boolean added = false;
1303 try {
1304 for (int i = 0; i < l; i++) {
1305 final ConnectorSpec cs = CONNECTORS.get(i);
1306 if (name.compareToIgnoreCase(cs.getName()) < 0) {
1307 CONNECTORS.add(i, connector);
1308 added = true;
1309 break;
1312 } catch (NullPointerException e) {
1313 Log.e(TAG, "error while sorting", e);
1315 if (!added) {
1316 CONNECTORS.add(connector);
1318 c = connector;
1320 final SharedPreferences p = PreferenceManager
1321 .getDefaultSharedPreferences(me);
1323 // update connectors balance if needed
1324 if (c.getBalance() == null && c.isReady() && !c.isRunning()
1325 && c.hasCapabilities(ConnectorSpec.CAPABILITIES_UPDATE)
1326 && p.getBoolean(PREFS_AUTOUPDATE, false)) {
1327 final String defPrefix = p
1328 .getString(PREFS_DEFPREFIX, "+49");
1329 final String defSender = p.getString(PREFS_SENDER, "");
1330 runCommand(me, c, ConnectorCommand.update(defPrefix,
1331 defSender));
1334 if (prefsConnectorSpec == null
1335 && prefsConnectorID.equals(connector.getID())) {
1336 prefsConnectorSpec = connector;
1338 prefsSubConnectorSpec = connector.getSubConnector(p
1339 .getString(PREFS_SUBCONNECTOR_ID, ""));
1340 me.setButtons();
1343 final String b = c.getBalance();
1344 final String ob = c.getOldBalance();
1345 if (b != null && (ob == null || !b.equals(ob))) {
1346 me.updateBalance();
1352 * Get {@link ConnectorSpec} by ID.
1354 * @param id
1355 * ID
1356 * @return {@link ConnectorSpec}
1358 private static ConnectorSpec getConnectorByID(final String id) {
1359 synchronized (CONNECTORS) {
1360 if (id == null) {
1361 return null;
1363 final int l = CONNECTORS.size();
1364 for (int i = 0; i < l; i++) {
1365 final ConnectorSpec c = CONNECTORS.get(i);
1366 if (id.equals(c.getID())) {
1367 return c;
1371 return null;
1375 * Get {@link ConnectorSpec} by name.
1377 * @param name
1378 * name
1379 * @param returnSelectedSubConnector
1380 * if not null, array[0] will be set to selected
1381 * {@link SubConnectorSpec}
1382 * @return {@link ConnectorSpec}
1384 private static ConnectorSpec getConnectorByName(final String name,
1385 final SubConnectorSpec[] returnSelectedSubConnector) {
1386 synchronized (CONNECTORS) {
1387 if (name == null) {
1388 return null;
1390 final int l = CONNECTORS.size();
1391 for (int i = 0; i < l; i++) {
1392 final ConnectorSpec c = CONNECTORS.get(i);
1393 final String n = c.getName();
1394 if (name.startsWith(n)) {
1395 if (name.length() == n.length()) {
1396 if (returnSelectedSubConnector != null) {
1397 returnSelectedSubConnector[0] = c
1398 .getSubConnectors()[0];
1400 return c;
1401 } else if (returnSelectedSubConnector != null) {
1403 final SubConnectorSpec[] scs = c.getSubConnectors();
1404 if (scs == null || scs.length == 0) {
1405 continue;
1407 for (SubConnectorSpec sc : scs) {
1408 if (name.endsWith(sc.getName())) {
1409 returnSelectedSubConnector[0] = sc;
1410 return c;
1417 return null;
1421 * Get {@link ConnectorSpec}s by capabilities and/or status.
1423 * @param capabilities
1424 * capabilities needed
1425 * @param status
1426 * status required {@link SubConnectorSpec}
1427 * @return {@link ConnectorSpec}s
1429 static final ConnectorSpec[] getConnectors(final short capabilities,
1430 final short status) {
1431 synchronized (CONNECTORS) {
1432 final ArrayList<ConnectorSpec> ret = new ArrayList<ConnectorSpec>(
1433 CONNECTORS.size());
1434 final int l = CONNECTORS.size();
1435 for (int i = 0; i < l; i++) {
1436 final ConnectorSpec c = CONNECTORS.get(i);
1437 if (c.hasCapabilities(capabilities) && c.hasStatus(status)) {
1438 ret.add(c);
1441 return ret.toArray(new ConnectorSpec[0]);