2 * Copyright (C) 2010 Felix Bechstein
4 * This file is part of WebSMS.
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
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
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
;
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;
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;
201 private TextView etTextLabel
;
204 private boolean showExtras
= false;
206 /** TextWatcher updating char count on writing. */
207 private TextWatcher textWatcher
= new TextWatcher() {
211 public void afterTextChanged(final Editable s
) {
212 int[] l
= SmsMessage
.calculateLength(s
, false);
213 WebSMS
.this.etTextLabel
.setText(l
[0] + "/" + l
[2]);
217 public void beforeTextChanged(final CharSequence s
, final int start
,
218 final int count
, final int after
) {
222 public void onTextChanged(final CharSequence s
, final int start
,
223 final int before
, final int count
) {
228 * Parse data pushed by {@link Intent}.
233 private void parseIntent(final Intent intent
) {
234 final String action
= intent
.getAction();
235 if (action
== null) {
239 // launched by clicking a sms: link, target number is in URI.
240 final Uri uri
= intent
.getData();
242 final String scheme
= uri
.getScheme();
243 if (scheme
.equals("sms") || scheme
.equals("smsto")) {
244 String s
= uri
.getSchemeSpecificPart();
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
253 if (helperAPI5c
!= null) {
255 n
= helperAPI5c
.getNameForNumber(this, s
);
256 } catch (NoClassDefFoundError e
) {
260 if (helperAPI5c
== null) {
261 Cursor c
= this.managedQuery(Phones
.CONTENT_URI
,
262 new String
[] { PhonesColumns
.NUMBER
,
265 PhonesColumns
.NUMBER
+ " = '" + s
+ "'",
267 if (c
.moveToFirst()) {
268 n
= c
.getString(c
.getColumnIndex(// .
269 PeopleColumns
.DISPLAY_NAME
));
273 s
= n
+ " <" + s
+ ">, ";
276 ((EditText
) this.findViewById(R
.id
.to
)).setText(s
);
279 final Bundle extras
= intent
.getExtras();
280 if (extras
!= null) {
281 s
= extras
.getCharSequence(Intent
.EXTRA_TEXT
).toString();
283 ((EditText
) this.findViewById(R
.id
.text
)).setText(s
);
286 s
= extras
.getString(EXTRA_ERRORMESSAGE
);
288 Toast
.makeText(this, s
, Toast
.LENGTH_LONG
).show();
292 // do not display any ads for donators
294 ((AdView
) WebSMS
.this.findViewById(R
.id
.ad
))
295 .setVisibility(View
.VISIBLE
);
305 public final void onCreate(final Bundle savedInstanceState
) {
306 super.onCreate(savedInstanceState
);
307 this.requestWindowFeature(Window
.FEATURE_INDETERMINATE_PROGRESS
);
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);
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
);
338 this.showDialog(DIALOG_UPDATE
);
343 lastTo
= p
.getString(PREFS_TO
, "");
344 lastMsg
= p
.getString(PREFS_TEXT
, "");
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);
374 protected final void onNewIntent(final Intent intent
) {
375 super.onNewIntent(intent
);
376 this.parseIntent(intent
);
383 protected final void onResume() {
385 // set free sms count
386 this.updateBalance();
388 // if coming from prefs..
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(
404 Log
.d(TAG
, "send broadcast: " + intent
.getAction());
405 this.sendBroadcast(intent
);
411 // reload text/recipient from local store
412 if (lastMsg
!= null) {
413 this.etText
.setText(lastMsg
);
415 this.etText
.setText("");
417 if (lastTo
!= null) {
418 this.etTo
.setText(lastTo
);
420 this.etTo
.setText("");
423 if (lastTo
!= null && lastTo
.length() > 0) {
424 this.etText
.requestFocus();
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
);
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) {
448 if (buf
.length() > 0) {
451 buf
.append(cs
.getName());
456 this.tvBalances
.setText(this.getString(R
.string
.free_
) + " "
457 + buf
.toString() + " "
458 + this.getString(R
.string
.click_for_update
));
465 public final void 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(
475 editor
.putString(PREFS_TO
, lastTo
);
476 editor
.putString(PREFS_TEXT
, lastMsg
);
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
);
492 v
.setVisibility(View
.VISIBLE
);
494 v
.setVisibility(View
.GONE
);
497 b
= !p
.getBoolean(PREFS_HIDE_CANCEL_BUTTON
, false);
498 v
= this.findViewById(R
.id
.cancel
);
500 v
.setVisibility(View
.VISIBLE
);
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
,
516 String hash
= Utils
.md5(p
.getString(PREFS_SENDER
, ""));
517 for (String h
: NO_AD_HASHS
) {
518 if (hash
.equals(h
)) {
523 if (!prefsNoAds
&& this.getImeiHash() != null) {
524 for (String h
: NO_AD_HASHS
) {
525 if (imeiHash
.equals(h
)) {
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_
);
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
);
558 this.findViewById(R
.id
.extras
).setVisibility(View
.GONE
);
560 if (this.showExtras
&& sFlashsms
) {
561 this.findViewById(R
.id
.flashsms
).setVisibility(View
.VISIBLE
);
563 this.findViewById(R
.id
.flashsms
).setVisibility(View
.GONE
);
565 if (this.showExtras
&& sCustomsender
) {
566 this.findViewById(R
.id
.custom_sender
).setVisibility(
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
);
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();
588 * Resets persistent store.
590 private void reset() {
591 this.etText
.setText("");
592 this.etTo
.setText("");
595 lastCustomSender
= null;
597 // save user preferences
598 SharedPreferences
.Editor editor
= PreferenceManager
599 .getDefaultSharedPreferences(this).edit();
600 editor
.putString(PREFS_TO
, "");
601 editor
.putString(PREFS_TEXT
, "");
607 final void savePreferences() {
608 if (prefsConnectorSpec
!= null) {
609 PreferenceManager
.getDefaultSharedPreferences(this).edit()
610 .putString(PREFS_CONNECTOR_ID
, prefsConnectorSpec
.getID())
616 * Run Connector.update().
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
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
);
645 public final void onClick(final View v
) {
648 this.updateFreecount(true);
651 this.send(prefsConnectorSpec
, WebSMS
.getSelectedSubConnectorID());
656 case R
.id
.change_connector
:
657 this.changeConnectorMenu();
660 this.showExtras
= !this.showExtras
;
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
);
669 lastCustomSender
= null;
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
);
689 public final boolean onCreateOptionsMenu(final Menu menu
) {
690 MenuInflater inflater
= this.getMenuInflater();
691 inflater
.inflate(R
.menu
.menu
, menu
);
693 menu
.removeItem(R
.id
.item_donate
);
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());
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
, // .
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)
733 e
.putString(PREFS_CONNECTOR_ID
, prefsConnectorSpec
735 e
.putString(PREFS_SUBCONNECTOR_ID
,
736 prefsSubConnectorSpec
.getID());
740 builder
.create().show();
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
);
752 case R
.id
.item_settings
: // start settings activity
753 this.startActivity(new Intent(this, Preferences
.class));
755 case R
.id
.item_donate
:
756 this.showDialog(DIALOG_PREDONATE
);
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
);
766 case R
.id
.item_connector
:
767 this.changeConnectorMenu();
778 protected final Dialog
onCreateDialog(final int id
) {
780 AlertDialog
.Builder builder
;
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
,
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
);
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
,
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
822 in
.putExtra(Intent
.EXTRA_SUBJECT
, WebSMS
.this
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();
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");
848 authors
.append("\n");
851 ((TextView
) d
.findViewById(R
.id
.author_connectors
)).setText(authors
855 builder
= new AlertDialog
.Builder(this);
856 builder
.setTitle(R
.string
.changelog_
);
857 final String
[] changes
= this.getResources().getStringArray(
859 final StringBuilder buf
= new StringBuilder(changes
[0]);
860 for (int i
= 1; i
< changes
.length
; i
++) {
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);
875 builder
.setPositiveButton(android
.R
.string
.ok
,
876 new DialogInterface
.OnClickListener() {
877 public void onClick(final DialogInterface dialog
,
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);
903 public final void log(final int text
) {
904 this.log(this.getString(text
));
913 public final void log(final String text
) {
915 Toast
.makeText(this.getApplicationContext(), text
,
916 Toast
.LENGTH_LONG
).show();
917 } catch (RuntimeException e
) {
923 * Send text to {@link Connector}.
926 * which connector should be used.
928 * {@link ConnectorCommand} to push to connector
930 private void send(final ConnectorSpec connector
,
931 final ConnectorCommand command
) {
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
) {
944 if (PreferenceManager
.getDefaultSharedPreferences(this).getBoolean(
945 PREFS_AUTOEXIT
, false)) {
947 Thread
.sleep(SLEEP_BEFORE_EXIT
);
948 } catch (InterruptedException e
) {
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) {
974 // do not display any ads for donators
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) {
1002 return prefsSubConnectorSpec
.getID();
1012 * @param monthOfYear
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
);
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();
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) {
1070 TelephonyManager mTelephonyMgr
= (TelephonyManager
) this
1071 .getSystemService(TELEPHONY_SERVICE
);
1072 final String did
= mTelephonyMgr
.getDeviceId();
1074 imeiHash
= Utils
.md5(did
);
1081 * Add or update a {@link ConnectorSpec}.
1086 public static final void addConnector(final ConnectorSpec connector
) {
1087 synchronized (CONNECTORS
) {
1088 if (connector
== null || connector
.getID() == null
1089 || connector
.getName() == null) {
1092 ConnectorSpec c
= getConnectorByID(connector
.getID());
1094 c
.update(connector
);
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;
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
);
1110 } catch (NullPointerException e
) {
1111 Log
.e(TAG
, "error while sorting", e
);
1114 CONNECTORS
.add(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
, ""));
1128 final String b
= c
.getBalance();
1129 final String ob
= c
.getOldBalance();
1130 if (b
!= null && (ob
== null || !b
.equals(ob
))) {
1137 * Get {@link ConnectorSpec} by ID.
1141 * @return {@link ConnectorSpec}
1143 private static ConnectorSpec
getConnectorByID(final String id
) {
1144 synchronized (CONNECTORS
) {
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())) {
1160 * Get {@link ConnectorSpec} by 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
) {
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];
1186 } else if (returnSelectedSubConnector
!= null) {
1188 final SubConnectorSpec
[] scs
= c
.getSubConnectors();
1189 if (scs
== null || scs
.length
== 0) {
1192 for (SubConnectorSpec sc
: scs
) {
1193 if (name
.endsWith(sc
.getName())) {
1194 returnSelectedSubConnector
[0] = sc
;
1206 * Get {@link ConnectorSpec}s by capabilities and/or status.
1208 * @param capabilities
1209 * capabilities needed
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
>(
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
)) {
1226 return ret
.toArray(new ConnectorSpec
[0]);