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
.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
;
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;
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;
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;
214 private TextView etTextLabel
;
217 private boolean showExtras
= false;
219 /** TextWatcher updating char count on writing. */
220 private TextWatcher textWatcher
= new TextWatcher() {
224 public void afterTextChanged(final Editable s
) {
225 int[] l
= SmsMessage
.calculateLength(s
, false);
226 WebSMS
.this.etTextLabel
.setText(l
[0] + "/" + l
[2]);
230 public void beforeTextChanged(final CharSequence s
, final int start
,
231 final int count
, final int after
) {
235 public void onTextChanged(final CharSequence s
, final int start
,
236 final int before
, final int count
) {
241 * Parse data pushed by {@link Intent}.
246 private void parseIntent(final Intent intent
) {
247 final String action
= intent
.getAction();
248 if (action
== null) {
252 // launched by clicking a sms: link, target number is in URI.
253 final Uri uri
= intent
.getData();
255 final String scheme
= uri
.getScheme();
256 if (scheme
.equals("sms") || scheme
.equals("smsto")) {
257 String s
= uri
.getSchemeSpecificPart();
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
266 if (helperAPI5c
!= null) {
268 n
= helperAPI5c
.getNameForNumber(this, s
);
269 } catch (NoClassDefFoundError e
) {
273 if (helperAPI5c
== null) {
274 Cursor c
= this.managedQuery(Phones
.CONTENT_URI
,
275 new String
[] { PhonesColumns
.NUMBER
,
278 PhonesColumns
.NUMBER
+ " = '" + s
+ "'",
280 if (c
.moveToFirst()) {
281 n
= c
.getString(c
.getColumnIndex(// .
282 PeopleColumns
.DISPLAY_NAME
));
286 s
= n
+ " <" + s
+ ">, ";
289 ((EditText
) this.findViewById(R
.id
.to
)).setText(s
);
292 final Bundle extras
= intent
.getExtras();
293 if (extras
!= null) {
294 s
= extras
.getCharSequence(Intent
.EXTRA_TEXT
).toString();
296 ((EditText
) this.findViewById(R
.id
.text
)).setText(s
);
299 s
= extras
.getString(EXTRA_ERRORMESSAGE
);
301 Toast
.makeText(this, s
, Toast
.LENGTH_LONG
).show();
305 // do not display any ads for donators
307 ((AdView
) WebSMS
.this.findViewById(R
.id
.ad
))
308 .setVisibility(View
.VISIBLE
);
318 public final void onCreate(final Bundle savedInstanceState
) {
319 super.onCreate(savedInstanceState
);
320 this.requestWindowFeature(Window
.FEATURE_INDETERMINATE_PROGRESS
);
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);
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
);
351 this.showDialog(DIALOG_UPDATE
);
356 lastTo
= p
.getString(PREFS_TO
, "");
357 lastMsg
= p
.getString(PREFS_TEXT
, "");
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
);
387 protected final void onNewIntent(final Intent intent
) {
388 super.onNewIntent(intent
);
389 this.parseIntent(intent
);
396 protected final void onResume() {
398 // set free sms count
399 this.updateBalance();
401 // if coming from prefs..
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
,
421 // reload text/recipient from local store
422 if (lastMsg
!= null) {
423 this.etText
.setText(lastMsg
);
425 this.etText
.setText("");
427 if (lastTo
!= null) {
428 this.etTo
.setText(lastTo
);
430 this.etTo
.setText("");
433 if (lastTo
!= null && lastTo
.length() > 0) {
434 this.etText
.requestFocus();
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
);
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) {
458 if (buf
.length() > 0) {
461 buf
.append(cs
.getName());
466 this.tvBalances
.setText(this.getString(R
.string
.free_
) + " "
467 + buf
.toString() + " "
468 + this.getString(R
.string
.click_for_update
));
475 public final void 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(
485 editor
.putString(PREFS_TO
, lastTo
);
486 editor
.putString(PREFS_TEXT
, lastMsg
);
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
,
503 final boolean bShowCancel
= !p
.getBoolean(PREFS_HIDE_CANCEL_BUTTON
,
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
);
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
);
517 v
.setVisibility(View
.GONE
);
520 v
= this.findViewById(R
.id
.emo
);
521 if (bShowEmoticons
) {
522 v
.setVisibility(View
.VISIBLE
);
524 v
.setVisibility(View
.GONE
);
527 v
= this.findViewById(R
.id
.cancel
);
529 v
.setVisibility(View
.VISIBLE
);
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
,
546 String hash
= Utils
.md5(p
.getString(PREFS_SENDER
, ""));
547 for (String h
: NO_AD_HASHS
) {
548 if (hash
.equals(h
)) {
553 if (!prefsNoAds
&& this.getImeiHash() != null) {
554 for (String h
: NO_AD_HASHS
) {
555 if (imeiHash
.equals(h
)) {
566 * Show/hide, enable/disable send buttons.
568 private void setButtons() {
569 Button btn
= (Button
) this.findViewById(R
.id
.send_
);
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
);
585 this.findViewById(R
.id
.extras
).setVisibility(View
.GONE
);
587 if (this.showExtras
&& sFlashsms
) {
588 this.findViewById(R
.id
.flashsms
).setVisibility(View
.VISIBLE
);
590 this.findViewById(R
.id
.flashsms
).setVisibility(View
.GONE
);
592 if (this.showExtras
&& sCustomsender
) {
593 this.findViewById(R
.id
.custom_sender
).setVisibility(
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
);
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();
615 * Resets persistent store.
617 private void reset() {
618 this.etText
.setText("");
619 this.etTo
.setText("");
622 lastCustomSender
= null;
624 // save user preferences
625 SharedPreferences
.Editor editor
= PreferenceManager
626 .getDefaultSharedPreferences(this).edit();
627 editor
.putString(PREFS_TO
, "");
628 editor
.putString(PREFS_TEXT
, "");
634 final void savePreferences() {
635 if (prefsConnectorSpec
!= null) {
636 PreferenceManager
.getDefaultSharedPreferences(this).edit()
637 .putString(PREFS_CONNECTOR_ID
, prefsConnectorSpec
.getID())
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
659 runCommand(this, cs
, ConnectorCommand
.update(defPrefix
, defSender
));
664 * Send a command as broadcast.
667 * WebSMS required for performance issues
669 * {@link ConnectorSpec}
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
);
682 case ConnectorCommand
.TYPE_SEND
:
683 intent
.setAction(connector
.getPackage() // .
684 + Connector
.ACTION_RUN_SEND
);
685 connector
.addStatus(ConnectorSpec
.STATUS_SENDING
);
687 case ConnectorCommand
.TYPE_UPDATE
:
688 intent
.setAction(connector
.getPackage()
689 + Connector
.ACTION_RUN_UPDATE
);
690 connector
.addStatus(ConnectorSpec
.STATUS_UPDATING
);
695 Log
.d(TAG
, "send broadcast: " + intent
.getAction());
696 context
.sendBroadcast(intent
);
702 public final void onClick(final View v
) {
705 this.updateFreecount();
708 this.send(prefsConnectorSpec
, WebSMS
.getSelectedSubConnectorID());
713 case R
.id
.change_connector
:
714 case R
.id
.change_connector_u
:
715 this.changeConnectorMenu();
718 this.showExtras
= !this.showExtras
;
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
);
727 lastCustomSender
= null;
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
);
740 this.showDialog(DIALOG_EMO
);
751 public final boolean onCreateOptionsMenu(final Menu menu
) {
752 MenuInflater inflater
= this.getMenuInflater();
753 inflater
.inflate(R
.menu
.menu
, menu
);
755 menu
.removeItem(R
.id
.item_donate
);
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());
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
, // .
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)
795 e
.putString(PREFS_CONNECTOR_ID
, prefsConnectorSpec
797 e
.putString(PREFS_SUBCONNECTOR_ID
,
798 prefsSubConnectorSpec
.getID());
802 builder
.create().show();
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
);
814 case R
.id
.item_settings
: // start settings activity
815 this.startActivity(new Intent(this, Preferences
.class));
817 case R
.id
.item_donate
:
818 this.showDialog(DIALOG_PREDONATE
);
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
);
828 case R
.id
.item_connector
:
829 this.changeConnectorMenu();
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
};
861 public long getItemId(final int position
) {
866 public Object
getItem(final int position
) {
871 public int getCount() {
872 return this.mThumbIds
.length
;
876 public View
getView(final int position
, final View convertView
,
877 final ViewGroup parent
) {
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);
887 imageView
= (ImageView
) convertView
;
890 imageView
.setImageResource(this.mThumbIds
[position
]);
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;
929 public void onItemClick(final AdapterView
<?
> adapter
, final View v
,
930 final int id
, final long arg3
) {
931 EditText et
= WebSMS
.this.etText
;
943 case EMO_FOOT_IN_MOUTH
:
955 case EMO_LIPS_SEALED
:
985 et
.setText(et
.getText() + e
);
996 protected final Dialog
onCreateDialog(final int id
) {
998 AlertDialog
.Builder builder
;
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
,
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
);
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
,
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
1040 in
.putExtra(Intent
.EXTRA_SUBJECT
, WebSMS
.this
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();
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");
1066 authors
.append("\n");
1069 ((TextView
) d
.findViewById(R
.id
.author_connectors
)).setText(authors
1070 .toString().trim());
1073 builder
= new AlertDialog
.Builder(this);
1074 builder
.setTitle(R
.string
.changelog_
);
1075 final String
[] changes
= this.getResources().getStringArray(
1077 final StringBuilder buf
= new StringBuilder(changes
[0]);
1078 for (int i
= 1; i
< changes
.length
; i
++) {
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
,
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);
1111 return this.createEmoticonsDialog();
1123 public final void log(final int text
) {
1124 this.log(this.getString(text
));
1133 public final void log(final String text
) {
1135 Toast
.makeText(this.getApplicationContext(), text
,
1136 Toast
.LENGTH_LONG
).show();
1137 } catch (RuntimeException e
) {
1138 Log
.e(TAG
, null, e
);
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) {
1160 // do not display any ads for donators
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
);
1180 runCommand(this, connector
, command
);
1181 } catch (Exception e
) {
1182 Log
.e(TAG
, null, e
);
1185 if (PreferenceManager
.getDefaultSharedPreferences(this).getBoolean(
1186 PREFS_AUTOEXIT
, false)) {
1188 Thread
.sleep(SLEEP_BEFORE_EXIT
);
1189 } catch (InterruptedException e
) {
1190 Log
.e(TAG
, null, e
);
1198 * @return ID of selected {@link SubConnectorSpec}
1200 private static String
getSelectedSubConnectorID() {
1201 if (prefsSubConnectorSpec
== null) {
1204 return prefsSubConnectorSpec
.getID();
1214 * @param monthOfYear
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
);
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();
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) {
1272 TelephonyManager mTelephonyMgr
= (TelephonyManager
) this
1273 .getSystemService(TELEPHONY_SERVICE
);
1274 final String did
= mTelephonyMgr
.getDeviceId();
1276 imeiHash
= Utils
.md5(did
);
1283 * Add or update a {@link ConnectorSpec}.
1288 static final void addConnector(final ConnectorSpec connector
) {
1289 synchronized (CONNECTORS
) {
1290 if (connector
== null || connector
.getID() == null
1291 || connector
.getName() == null) {
1294 ConnectorSpec c
= getConnectorByID(connector
.getID());
1296 c
.update(connector
);
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;
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
);
1312 } catch (NullPointerException e
) {
1313 Log
.e(TAG
, "error while sorting", e
);
1316 CONNECTORS
.add(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
,
1334 if (prefsConnectorSpec
== null
1335 && prefsConnectorID
.equals(connector
.getID())) {
1336 prefsConnectorSpec
= connector
;
1338 prefsSubConnectorSpec
= connector
.getSubConnector(p
1339 .getString(PREFS_SUBCONNECTOR_ID
, ""));
1343 final String b
= c
.getBalance();
1344 final String ob
= c
.getOldBalance();
1345 if (b
!= null && (ob
== null || !b
.equals(ob
))) {
1352 * Get {@link ConnectorSpec} by ID.
1356 * @return {@link ConnectorSpec}
1358 private static ConnectorSpec
getConnectorByID(final String id
) {
1359 synchronized (CONNECTORS
) {
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())) {
1375 * Get {@link ConnectorSpec} by 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
) {
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];
1401 } else if (returnSelectedSubConnector
!= null) {
1403 final SubConnectorSpec
[] scs
= c
.getSubConnectors();
1404 if (scs
== null || scs
.length
== 0) {
1407 for (SubConnectorSpec sc
: scs
) {
1408 if (name
.endsWith(sc
.getName())) {
1409 returnSelectedSubConnector
[0] = sc
;
1421 * Get {@link ConnectorSpec}s by capabilities and/or status.
1423 * @param capabilities
1424 * capabilities needed
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
>(
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
)) {
1441 return ret
.toArray(new ConnectorSpec
[0]);