skip defect connectors
[andGMXsms.git] / src / de / ub0r / android / websms / WebSMS.java
blob4a4bb34c0e650d0aab6122001f27d3e9f4712eb4
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.io.BufferedInputStream;
22 import java.io.BufferedOutputStream;
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.IOException;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.util.ArrayList;
29 import java.util.Calendar;
30 import java.util.List;
32 import android.app.Activity;
33 import android.app.AlertDialog;
34 import android.app.DatePickerDialog;
35 import android.app.Dialog;
36 import android.app.DatePickerDialog.OnDateSetListener;
37 import android.app.TimePickerDialog.OnTimeSetListener;
38 import android.content.ActivityNotFoundException;
39 import android.content.DialogInterface;
40 import android.content.Intent;
41 import android.content.SharedPreferences;
42 import android.content.SharedPreferences.Editor;
43 import android.content.pm.ResolveInfo;
44 import android.database.Cursor;
45 import android.net.Uri;
46 import android.os.Bundle;
47 import android.preference.PreferenceManager;
48 import android.provider.Contacts.PeopleColumns;
49 import android.provider.Contacts.Phones;
50 import android.provider.Contacts.PhonesColumns;
51 import android.telephony.TelephonyManager;
52 import android.telephony.gsm.SmsMessage;
53 import android.text.Editable;
54 import android.text.TextWatcher;
55 import android.util.Log;
56 import android.view.Menu;
57 import android.view.MenuInflater;
58 import android.view.MenuItem;
59 import android.view.View;
60 import android.view.ViewGroup;
61 import android.view.Window;
62 import android.view.View.OnClickListener;
63 import android.widget.AdapterView;
64 import android.widget.BaseAdapter;
65 import android.widget.Button;
66 import android.widget.CheckBox;
67 import android.widget.DatePicker;
68 import android.widget.EditText;
69 import android.widget.GridView;
70 import android.widget.ImageView;
71 import android.widget.MultiAutoCompleteTextView;
72 import android.widget.TextView;
73 import android.widget.TimePicker;
74 import android.widget.Toast;
75 import android.widget.AdapterView.OnItemClickListener;
77 import com.admob.android.ads.AdView;
79 import de.ub0r.android.websms.connector.common.Connector;
80 import de.ub0r.android.websms.connector.common.ConnectorCommand;
81 import de.ub0r.android.websms.connector.common.ConnectorSpec;
82 import de.ub0r.android.websms.connector.common.Utils;
83 import de.ub0r.android.websms.connector.common.ConnectorSpec.SubConnectorSpec;
85 /**
86 * Main Activity.
88 * @author flx
90 @SuppressWarnings("deprecation")
91 public class WebSMS extends Activity implements OnClickListener,
92 OnDateSetListener, OnTimeSetListener {
93 /** Tag for output. */
94 private static final String TAG = "WebSMS";
96 /** Static reference to running Activity. */
97 private static WebSMS me;
98 /** Preference's name: last version run. */
99 private static final String PREFS_LAST_RUN = "lastrun";
100 /** Preference's name: user's phonenumber. */
101 static final String PREFS_SENDER = "sender";
102 /** Preference's name: default prefix. */
103 static final String PREFS_DEFPREFIX = "defprefix";
104 /** Preference's name: update balace on start. */
105 private static final String PREFS_AUTOUPDATE = "autoupdate";
106 /** Preference's name: exit after sending. */
107 private static final String PREFS_AUTOEXIT = "autoexit";
108 /** Preference's name: show mobile numbers only. */
109 private static final String PREFS_MOBILES_ONLY = "mobiles_only";
110 /** Preference's name: vibrate on failed sending. */
111 static final String PREFS_FAIL_VIBRATE = "fail_vibrate";
112 /** Preference's name: sound on failed sending. */
113 static final String PREFS_FAIL_SOUND = "fail_sound";
114 /** Preferemce's name: enable change connector button. */
115 private static final String PREFS_HIDE_CHANGE_CONNECTOR_BUTTON = // .
116 "hide_change_connector_button";
117 /** Preferemce's name: hide emoticons button. */
118 private static final String PREFS_HIDE_EMO_BUTTON = "hide_emo_button";
119 /** Preferemce's name: hide cancel button. */
120 private static final String PREFS_HIDE_CANCEL_BUTTON = "hide_cancel_button";
121 /** Cache {@link ConnectorSpec}s. */
122 private static final String PREFS_CONNECTORS = "connectors";
124 /** Preference's name: to. */
125 private static final String PREFS_TO = "to";
126 /** Preference's name: text. */
127 private static final String PREFS_TEXT = "text";
128 /** Preference's name: selected {@link ConnectorSpec} ID. */
129 private static final String PREFS_CONNECTOR_ID = "connector_id";
130 /** Preference's name: selected {@link SubConnectorSpec} ID. */
131 private static final String PREFS_SUBCONNECTOR_ID = "subconnector_id";
133 /** Sleep before autoexit. */
134 private static final int SLEEP_BEFORE_EXIT = 75;
136 /** Buffersize for saving and loading Connectors. */
137 private static final int BUFSIZE = 4096;
139 /** Preferences: hide ads. */
140 private static boolean prefsNoAds = false;
141 /** Hased IMEI. */
142 private static String imeiHash = null;
143 /** Preferences: selected {@link ConnectorSpec}. */
144 private static ConnectorSpec prefsConnectorSpec = null;
145 /** Preferences: selected {@link SubConnectorSpec}. */
146 private static SubConnectorSpec prefsSubConnectorSpec = null;
147 /** Save prefsConnectorSpec.getID() here. */
148 private static String prefsConnectorID = null;
150 /** List of available {@link ConnectorSpec}s. */
151 private static final ArrayList<ConnectorSpec> CONNECTORS = // .
152 new ArrayList<ConnectorSpec>();
154 /** Array of md5(prefsSender) for which no ads should be displayed. */
155 private static final String[] NO_AD_HASHS = {
156 "43dcb861b9588fb733300326b61dbab9", // flx
157 "57a3c7c19329fd84c2252a9b2866dd93", // mirweb
158 "10b7a2712beee096acbc67416d7d71a1", // mo
159 "f6b3b72300e918436b4c4c9fdf909e8c", // joerg s.
160 "4c18f7549b643045f0ff69f61e8f7e72", // frank j.
161 "7684154558d19383552388d9bc92d446", // henning k.
162 "64c7414288e9a9b57a33e034f384ed30", // dominik l.
163 "c479a2e701291c751f0f91426bcaabf3", // bernhard g.
164 "ae7dfedf549f98a349ad8c2068473c6b", // dominik k.-v.
165 "18bc29cd511613552861da6ef51766ce", // niels b.
166 "2985011f56d0049b0f4f0caed3581123", // sven l.
167 "64724033da297a915a89023b11ac2e47", // wilfried m.
168 "cfd8d2efb3eac39705bd62c4dfe5e72d", // achim e.
169 "ca56e7518fdbda832409ef07edd4c273", // michael s.
170 "bed2f068ca8493da4179807d1afdbd83", // axel q.
171 "4c35400c4fa3ffe2aefcf1f9131eb855", // gerhard s.
172 "02158d2a80b1ef9c4d684a4ca808b93d", // camilo s.
173 "1177c6e67f98cdfed6c84d99e85d30de", // daniel p.
174 "3f082dd7e21d5c64f34a69942c474ce7", // andre j.
175 "5383540b2f8c298532f874126b021e73", // marco a.
176 "858ddfb8635d1539884086dca2726468", // lado
177 "6e8bbb35091219a80e278ae61f31cce9", // mario s.
178 "9f01eae4eaecd9158a2caddc04bad77e", // andreas p.
181 /** true if preferences got opened. */
182 static boolean doPreferences = false;
184 /** Dialog: about. */
185 private static final int DIALOG_ABOUT = 0;
186 /** Dialog: updates. */
187 private static final int DIALOG_UPDATE = 2;
188 /** Dialog: custom sender. */
189 private static final int DIALOG_CUSTOMSENDER = 3;
190 /** Dialog: send later: date. */
191 private static final int DIALOG_SENDLATER_DATE = 4;
192 /** Dialog: send later: time. */
193 private static final int DIALOG_SENDLATER_TIME = 5;
194 /** Dialog: pre donate. */
195 private static final int DIALOG_PREDONATE = 6;
196 /** Dialog: post donate. */
197 private static final int DIALOG_POSTDONATE = 7;
198 /** Dialog: emo. */
199 private static final int DIALOG_EMO = 8;
201 /** Size of the emoticons png. */
202 private static final int EMOTICONS_SIZE = 30;
204 /** Intent's extra for error messages. */
205 static final String EXTRA_ERRORMESSAGE = // .
206 "de.ub0r.android.intent.extra.ERRORMESSAGE";
208 /** Persistent Message store. */
209 private static String lastMsg = null;
210 /** Persistent Recipient store. */
211 private static String lastTo = null;
212 /** Backup for params: custom sender. */
213 private static String lastCustomSender = null;
214 /** Backup for params: send later. */
215 private static long lastSendLater = -1;
217 /** {@link MultiAutoCompleteTextView} holding recipients. */
218 private MultiAutoCompleteTextView etTo;
219 /** {@link EditText} holding text. */
220 private EditText etText;
221 /** {@link TextView} holding balances. */
222 private TextView tvBalances;
224 /** Helper for API 5. */
225 static HelperAPI5Contacts helperAPI5c = null;
227 /** Text's label. */
228 private TextView etTextLabel;
230 /** Show extras. */
231 private boolean showExtras = false;
233 /** TextWatcher updating char count on writing. */
234 private TextWatcher textWatcher = new TextWatcher() {
236 * {@inheritDoc}
238 public void afterTextChanged(final Editable s) {
239 int[] l = SmsMessage.calculateLength(s, false);
240 WebSMS.this.etTextLabel.setText(l[0] + "/" + l[2]);
243 /** Needed dummy. */
244 public void beforeTextChanged(final CharSequence s, final int start,
245 final int count, final int after) {
248 /** Needed dummy. */
249 public void onTextChanged(final CharSequence s, final int start,
250 final int before, final int count) {
255 * Parse data pushed by {@link Intent}.
257 * @param intent
258 * {@link Intent}
260 private void parseIntent(final Intent intent) {
261 final String action = intent.getAction();
262 if (action == null) {
263 return;
266 // launched by clicking a sms: link, target number is in URI.
267 final Uri uri = intent.getData();
268 if (uri != null) {
269 final String scheme = uri.getScheme();
270 if (scheme.equals("sms") || scheme.equals("smsto")) {
271 String s = uri.getSchemeSpecificPart();
272 if (s != null) {
273 s = s.trim();
274 if (s.endsWith(",")) {
275 s = s.substring(0, s.length() - 1).trim();
277 if (s.indexOf('<') < 0) {
278 // try to fetch recipient's name from phonebook
279 String n = null;
280 if (helperAPI5c != null) {
281 try {
282 n = helperAPI5c.getNameForNumber(this, s);
283 } catch (NoClassDefFoundError e) {
284 helperAPI5c = null;
287 if (helperAPI5c == null) {
288 Cursor c = this.managedQuery(Phones.CONTENT_URI,
289 new String[] { PhonesColumns.NUMBER,
290 PeopleColumns.// .
291 DISPLAY_NAME },
292 PhonesColumns.NUMBER + " = '" + s + "'",
293 null, null);
294 if (c.moveToFirst()) {
295 n = c.getString(c.getColumnIndex(// .
296 PeopleColumns.DISPLAY_NAME));
299 if (n != null) {
300 s = n + " <" + s + ">, ";
303 ((EditText) this.findViewById(R.id.to)).setText(s);
304 lastTo = s;
306 final Bundle extras = intent.getExtras();
307 if (extras != null) {
308 s = extras.getCharSequence(Intent.EXTRA_TEXT).toString();
309 if (s != null) {
310 ((EditText) this.findViewById(R.id.text)).setText(s);
311 lastMsg = s;
313 s = extras.getString(EXTRA_ERRORMESSAGE);
314 if (s != null) {
315 Toast.makeText(this, s, Toast.LENGTH_LONG).show();
318 if (!prefsNoAds) {
319 // do not display any ads for donators
320 // display ads
321 ((AdView) WebSMS.this.findViewById(R.id.ad))
322 .setVisibility(View.VISIBLE);
329 * {@inheritDoc}
331 @SuppressWarnings("unchecked")
332 @Override
333 public final void onCreate(final Bundle savedInstanceState) {
334 super.onCreate(savedInstanceState);
335 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
337 // save ref to me.
338 me = this;
339 try {
340 WebSMS.helperAPI5c = new HelperAPI5Contacts();
341 if (!helperAPI5c.isAvailable()) {
342 WebSMS.helperAPI5c = null;
344 } catch (VerifyError e) {
345 WebSMS.helperAPI5c = null;
346 Log.d(TAG, "no api5 running", e);
348 // Restore preferences
349 final SharedPreferences p = PreferenceManager
350 .getDefaultSharedPreferences(this);
351 // inflate XML
352 this.setContentView(R.layout.main);
354 this.etTo = (MultiAutoCompleteTextView) this.findViewById(R.id.to);
355 this.etText = (EditText) this.findViewById(R.id.text);
356 this.etTextLabel = (TextView) this.findViewById(R.id.text_);
357 this.tvBalances = (TextView) this.findViewById(R.id.freecount);
359 // display changelog?
360 String v0 = p.getString(PREFS_LAST_RUN, "");
361 String v1 = this.getResources().getString(R.string.app_version);
362 if (!v0.equals(v1)) {
363 SharedPreferences.Editor editor = p.edit();
364 editor.putString(PREFS_LAST_RUN, v1);
365 editor.commit();
366 this.showDialog(DIALOG_UPDATE);
368 v0 = null;
369 v1 = null;
371 // get cached Connectors
372 String s = p.getString(PREFS_CONNECTORS, "");
373 if (s.length() == 0) {
374 this.updateConnectors();
375 } else if (CONNECTORS.size() == 0) {
376 // skip static remaining connectors
377 try {
378 ArrayList<ConnectorSpec> cache;
379 cache = (ArrayList<ConnectorSpec>) (new ObjectInputStream(
380 new BufferedInputStream(new ByteArrayInputStream(
381 Base64Coder.decode(s)), BUFSIZE))).readObject();
382 CONNECTORS.addAll(cache);
383 } catch (Exception e) {
384 Log.d(TAG, "error loading connectors", e);
387 s = null;
388 Log.d(TAG, "loaded connectors: " + CONNECTORS.size());
390 this.reloadPrefs();
392 lastTo = p.getString(PREFS_TO, "");
393 lastMsg = p.getString(PREFS_TEXT, "");
395 // register Listener
396 this.findViewById(R.id.send_).setOnClickListener(this);
397 this.findViewById(R.id.cancel).setOnClickListener(this);
398 this.findViewById(R.id.change_connector).setOnClickListener(this);
399 this.findViewById(R.id.change_connector_u).setOnClickListener(this);
400 this.findViewById(R.id.extras).setOnClickListener(this);
401 this.findViewById(R.id.custom_sender).setOnClickListener(this);
402 this.findViewById(R.id.send_later).setOnClickListener(this);
403 this.findViewById(R.id.emo).setOnClickListener(this);
404 this.findViewById(R.id.emo_u).setOnClickListener(this);
405 this.tvBalances.setOnClickListener(this);
406 this.etText.addTextChangedListener(this.textWatcher);
407 this.etTo.setAdapter(new MobilePhoneAdapter(this));
408 this.etTo.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
409 this.etTo.requestFocus();
411 this.parseIntent(this.getIntent());
413 // check default prefix
414 if (!p.getString(PREFS_DEFPREFIX, "").startsWith("+")) {
415 WebSMS.this.log(R.string.log_wrong_defprefix);
420 * {@inheritDoc}
422 @Override
423 protected final void onNewIntent(final Intent intent) {
424 super.onNewIntent(intent);
425 this.parseIntent(intent);
429 * Update {@link ConnectorSpec}s.
431 private void updateConnectors() {
432 // query for connectors
433 final Intent i = new Intent(Connector.ACTION_CONNECTOR_UPDATE);
434 Log.d(TAG, "send broadcast: " + i.getAction());
435 this.sendBroadcast(i);
439 * {@inheritDoc}
441 @Override
442 protected final void onResume() {
443 super.onResume();
444 // set accounts' balance to gui
445 this.updateBalance();
447 // if coming from prefs..
448 if (doPreferences) {
449 this.reloadPrefs();
450 this.updateConnectors();
451 doPreferences = false;
452 final SharedPreferences p = PreferenceManager
453 .getDefaultSharedPreferences(this);
454 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
455 final String defSender = p.getString(PREFS_SENDER, "");
456 final ConnectorSpec[] css = getConnectors(
457 ConnectorSpec.CAPABILITIES_BOOTSTRAP, // .
458 (short) (ConnectorSpec.STATUS_ENABLED | // .
459 ConnectorSpec.STATUS_READY));
460 for (ConnectorSpec cs : css) {
461 runCommand(this, cs, ConnectorCommand.bootstrap(defPrefix,
462 defSender));
464 } else {
465 // check is count of connectors changed
466 final List<ResolveInfo> ri = this.getPackageManager()
467 .queryBroadcastReceivers(
468 new Intent(Connector.ACTION_CONNECTOR_UPDATE), 0);
469 final int s1 = ri.size();
470 final int s2 = CONNECTORS.size();
471 if (s1 != s2) {
472 Log.d(TAG, "clear connector cache (" + s1 + "/" + s2 + ")");
473 CONNECTORS.clear();
474 this.updateConnectors();
478 this.setButtons();
480 // reload text/recipient from local store
481 if (lastMsg != null) {
482 this.etText.setText(lastMsg);
483 } else {
484 this.etText.setText("");
486 if (lastTo != null) {
487 this.etTo.setText(lastTo);
488 } else {
489 this.etTo.setText("");
492 if (lastTo != null && lastTo.length() > 0) {
493 this.etText.requestFocus();
494 } else {
495 this.etTo.requestFocus();
500 * Update balance.
502 private void updateBalance() {
503 final StringBuilder buf = new StringBuilder();
504 final ConnectorSpec[] css = getConnectors(
505 ConnectorSpec.CAPABILITIES_UPDATE, // .
506 ConnectorSpec.STATUS_ENABLED);
507 for (ConnectorSpec cs : css) {
508 final String b = cs.getBalance();
509 if (b == null || b.length() == 0) {
510 continue;
512 if (buf.length() > 0) {
513 buf.append(", ");
515 buf.append(cs.getName());
516 buf.append(": ");
517 buf.append(b);
520 this.tvBalances.setText(this.getString(R.string.free_) + " "
521 + buf.toString() + " "
522 + this.getString(R.string.click_for_update));
526 * {@inheritDoc}
528 @Override
529 protected final void onPause() {
530 super.onPause();
531 // store input data to persitent stores
532 lastMsg = this.etText.getText().toString();
533 lastTo = this.etTo.getText().toString();
535 // store input data to preferences
536 final Editor editor = PreferenceManager.getDefaultSharedPreferences(
537 this).edit();
538 // common
539 editor.putString(PREFS_TO, lastTo);
540 editor.putString(PREFS_TEXT, lastMsg);
541 // commit changes
542 editor.commit();
544 this.savePreferences();
547 @Override
548 protected final void onDestroy() {
549 super.onDestroy();
550 final Editor editor = PreferenceManager.getDefaultSharedPreferences(
551 this).edit();
552 try {
553 final ByteArrayOutputStream out = new ByteArrayOutputStream();
554 ObjectOutputStream objOut = new ObjectOutputStream(
555 new BufferedOutputStream(out, BUFSIZE));
556 objOut.writeObject(CONNECTORS);
557 objOut.close();
558 final String s = String.valueOf(Base64Coder.encode(out
559 .toByteArray()));
560 Log.d(TAG, s);
561 editor.putString(PREFS_CONNECTORS, s);
562 } catch (IOException e) {
563 editor.remove(PREFS_CONNECTORS);
564 Log.e(TAG, "IO", e);
566 editor.commit();
570 * Read static variables holding preferences.
572 private void reloadPrefs() {
573 final SharedPreferences p = PreferenceManager
574 .getDefaultSharedPreferences(this);
575 final boolean bShowChangeConnector = !p.getBoolean(
576 PREFS_HIDE_CHANGE_CONNECTOR_BUTTON, false);
577 final boolean bShowEmoticons = !p.getBoolean(PREFS_HIDE_EMO_BUTTON,
578 false);
579 final boolean bShowCancel = !p.getBoolean(PREFS_HIDE_CANCEL_BUTTON,
580 false);
582 if (bShowChangeConnector && bShowEmoticons && bShowCancel) {
583 this.findViewById(R.id.upper).setVisibility(View.VISIBLE);
584 this.findViewById(R.id.change_connector).setVisibility(View.GONE);
585 this.findViewById(R.id.emo).setVisibility(View.GONE);
586 } else {
587 this.findViewById(R.id.upper).setVisibility(View.GONE);
589 View v = this.findViewById(R.id.change_connector);
590 if (bShowChangeConnector) {
591 v.setVisibility(View.VISIBLE);
592 } else {
593 v.setVisibility(View.GONE);
596 v = this.findViewById(R.id.emo);
597 if (bShowEmoticons) {
598 v.setVisibility(View.VISIBLE);
599 } else {
600 v.setVisibility(View.GONE);
603 v = this.findViewById(R.id.cancel);
604 if (bShowCancel) {
605 v.setVisibility(View.VISIBLE);
606 } else {
607 v.setVisibility(View.GONE);
611 prefsConnectorID = p.getString(PREFS_CONNECTOR_ID, "");
612 prefsConnectorSpec = getConnectorByID(prefsConnectorID);
613 if (prefsConnectorSpec != null
614 && prefsConnectorSpec.hasStatus(ConnectorSpec.STATUS_ENABLED)) {
615 prefsSubConnectorSpec = prefsConnectorSpec.getSubConnector(p
616 .getString(PREFS_SUBCONNECTOR_ID, ""));
617 if (prefsSubConnectorSpec == null) {
618 prefsSubConnectorSpec = prefsConnectorSpec.// .
619 getSubConnectors()[0];
621 } else {
622 ConnectorSpec[] connectors = getConnectors(
623 ConnectorSpec.CAPABILITIES_SEND,
624 ConnectorSpec.STATUS_ENABLED);
625 if (connectors.length == 1) {
626 prefsConnectorSpec = connectors[0];
627 prefsSubConnectorSpec = prefsConnectorSpec // .
628 .getSubConnectors()[0];
629 Toast.makeText(
630 this,
631 this.getString(R.string.connectors_switch) + " "
632 + prefsConnectorSpec.getName(),
633 Toast.LENGTH_LONG).show();
634 } else {
635 prefsConnectorSpec = null;
636 prefsSubConnectorSpec = null;
640 MobilePhoneAdapter.setMoileNubersObly(p.getBoolean(PREFS_MOBILES_ONLY,
641 false));
643 prefsNoAds = false;
644 String hash = Utils.md5(p.getString(PREFS_SENDER, ""));
645 for (String h : NO_AD_HASHS) {
646 if (hash.equals(h)) {
647 prefsNoAds = true;
648 break;
651 if (!prefsNoAds && this.getImeiHash() != null) {
652 for (String h : NO_AD_HASHS) {
653 if (imeiHash.equals(h)) {
654 prefsNoAds = true;
655 break;
660 this.setButtons();
664 * Show/hide, enable/disable send buttons.
666 private void setButtons() {
667 Button btn = (Button) this.findViewById(R.id.send_);
668 // show/hide buttons
669 btn.setEnabled(prefsConnectorSpec != null
670 && prefsSubConnectorSpec != null);
671 btn.setVisibility(View.VISIBLE);
673 if (prefsConnectorSpec != null && prefsSubConnectorSpec != null) {
674 final boolean sFlashsms = prefsSubConnectorSpec
675 .hasFeatures(SubConnectorSpec.FEATURE_FLASHSMS);
676 final boolean sCustomsender = prefsSubConnectorSpec
677 .hasFeatures(SubConnectorSpec.FEATURE_CUSTOMSENDER);
678 final boolean sSendLater = prefsSubConnectorSpec
679 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER);
680 if (sFlashsms || sCustomsender || sSendLater) {
681 this.findViewById(R.id.extras).setVisibility(View.VISIBLE);
682 } else {
683 this.findViewById(R.id.extras).setVisibility(View.GONE);
685 if (this.showExtras && sFlashsms) {
686 this.findViewById(R.id.flashsms).setVisibility(View.VISIBLE);
687 } else {
688 this.findViewById(R.id.flashsms).setVisibility(View.GONE);
690 if (this.showExtras && sCustomsender) {
691 this.findViewById(R.id.custom_sender).setVisibility(
692 View.VISIBLE);
693 } else {
694 this.findViewById(R.id.custom_sender).setVisibility(View.GONE);
696 if (this.showExtras && sSendLater) {
697 this.findViewById(R.id.send_later).setVisibility(View.VISIBLE);
698 } else {
699 this.findViewById(R.id.send_later).setVisibility(View.GONE);
702 String t = this.getString(R.string.app_name) + " - "
703 + prefsConnectorSpec.getName();
704 if (prefsSubConnectorSpec != null
705 && prefsConnectorSpec.getSubConnectorCount() > 1) {
706 t += " - " + prefsSubConnectorSpec.getName();
708 this.setTitle(t);
713 * Resets persistent store.
715 private void reset() {
716 this.etText.setText("");
717 this.etTo.setText("");
718 lastMsg = null;
719 lastTo = null;
720 lastCustomSender = null;
721 lastSendLater = -1;
722 // save user preferences
723 SharedPreferences.Editor editor = PreferenceManager
724 .getDefaultSharedPreferences(this).edit();
725 editor.putString(PREFS_TO, "");
726 editor.putString(PREFS_TEXT, "");
727 // commit changes
728 editor.commit();
731 /** Save prefs. */
732 final void savePreferences() {
733 if (prefsConnectorSpec != null) {
734 PreferenceManager.getDefaultSharedPreferences(this).edit()
735 .putString(PREFS_CONNECTOR_ID, prefsConnectorSpec.getID())
736 .commit();
741 * Run Connector.doUpdate().
743 private void updateFreecount() {
744 final SharedPreferences p = PreferenceManager
745 .getDefaultSharedPreferences(this);
746 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
747 final String defSender = p.getString(PREFS_SENDER, "");
748 final ConnectorSpec[] css = getConnectors(
749 ConnectorSpec.CAPABILITIES_UPDATE, // .
750 (short) (ConnectorSpec.STATUS_ENABLED | // .
751 ConnectorSpec.STATUS_READY));
752 for (ConnectorSpec cs : css) {
753 if (cs.isRunning()) {
754 // skip running connectors
755 continue;
757 runCommand(this, cs, ConnectorCommand.update(defPrefix, defSender));
762 * Send a command as broadcast.
764 * @param context
765 * WebSMS required for performance issues
766 * @param connector
767 * {@link ConnectorSpec}
768 * @param command
769 * {@link ConnectorCommand}
771 static final void runCommand(final WebSMS context,
772 final ConnectorSpec connector, final ConnectorCommand command) {
773 final Intent intent = command.setToIntent(null);
774 switch (command.getType()) {
775 case ConnectorCommand.TYPE_BOOTSTRAP:
776 intent.setAction(connector.getPackage()
777 + Connector.ACTION_RUN_BOOTSTRAP);
778 connector.addStatus(ConnectorSpec.STATUS_BOOTSTRAPPING);
779 break;
780 case ConnectorCommand.TYPE_SEND:
781 intent.setAction(connector.getPackage() // .
782 + Connector.ACTION_RUN_SEND);
783 connector.setToIntent(intent);
784 connector.addStatus(ConnectorSpec.STATUS_SENDING);
785 break;
786 case ConnectorCommand.TYPE_UPDATE:
787 intent.setAction(connector.getPackage()
788 + Connector.ACTION_RUN_UPDATE);
789 connector.addStatus(ConnectorSpec.STATUS_UPDATING);
790 break;
791 default:
792 break;
794 Log.d(TAG, "send broadcast: " + intent.getAction());
795 context.sendBroadcast(intent);
799 * {@inheritDoc}
801 public final void onClick(final View v) {
802 switch (v.getId()) {
803 case R.id.freecount:
804 this.updateFreecount();
805 return;
806 case R.id.send_:
807 this.send(prefsConnectorSpec, WebSMS.getSelectedSubConnectorID());
808 return;
809 case R.id.cancel:
810 this.reset();
811 return;
812 case R.id.change_connector:
813 case R.id.change_connector_u:
814 this.changeConnectorMenu();
815 return;
816 case R.id.extras:
817 this.showExtras = !this.showExtras;
818 this.setButtons();
819 return;
820 case R.id.custom_sender:
821 final CheckBox cs = (CheckBox) this
822 .findViewById(R.id.custom_sender);
823 if (cs.isChecked()) {
824 this.showDialog(DIALOG_CUSTOMSENDER);
825 } else {
826 lastCustomSender = null;
828 return;
829 case R.id.send_later:
830 final CheckBox sl = (CheckBox) this.findViewById(R.id.send_later);
831 if (sl.isChecked()) {
832 this.showDialog(DIALOG_SENDLATER_DATE);
833 } else {
834 lastSendLater = -1;
836 return;
837 case R.id.emo:
838 case R.id.emo_u:
839 this.showDialog(DIALOG_EMO);
840 return;
841 default:
842 return;
847 * {@inheritDoc}
849 @Override
850 public final boolean onCreateOptionsMenu(final Menu menu) {
851 MenuInflater inflater = this.getMenuInflater();
852 inflater.inflate(R.menu.menu, menu);
853 if (prefsNoAds) {
854 menu.removeItem(R.id.item_donate);
856 return true;
860 * Display "change connector" menu.
862 private void changeConnectorMenu() {
863 AlertDialog.Builder builder = new AlertDialog.Builder(this);
864 builder.setIcon(android.R.drawable.ic_menu_share);
865 builder.setTitle(R.string.change_connector_);
866 final ArrayList<String> items = new ArrayList<String>();
867 final ConnectorSpec[] css = getConnectors(
868 ConnectorSpec.CAPABILITIES_SEND, ConnectorSpec.STATUS_ENABLED);
869 for (ConnectorSpec cs : css) {
870 final SubConnectorSpec[] scs = cs.getSubConnectors();
871 if (scs.length <= 1) {
872 items.add(cs.getName());
873 } else {
874 final String n = cs.getName() + " - ";
875 for (SubConnectorSpec sc : scs) {
876 items.add(n + sc.getName());
881 builder.setItems(items.toArray(new String[0]),
882 new DialogInterface.OnClickListener() {
883 public void onClick(final DialogInterface d, // .
884 final int item) {
885 final SubConnectorSpec[] ret = ConnectorSpec
886 .getSubConnectorReturnArray();
887 prefsConnectorSpec = getConnectorByName(
888 items.get(item), ret);
889 prefsSubConnectorSpec = ret[0];
890 WebSMS.this.setButtons();
891 // save user preferences
892 final Editor e = PreferenceManager
893 .getDefaultSharedPreferences(WebSMS.this)
894 .edit();
895 e.putString(PREFS_CONNECTOR_ID, prefsConnectorSpec
896 .getID());
897 e.putString(PREFS_SUBCONNECTOR_ID,
898 prefsSubConnectorSpec.getID());
899 e.commit();
902 builder.create().show();
906 *{@inheritDoc}
908 @Override
909 public final boolean onOptionsItemSelected(final MenuItem item) {
910 switch (item.getItemId()) {
911 case R.id.item_about: // start about dialog
912 this.showDialog(DIALOG_ABOUT);
913 return true;
914 case R.id.item_settings: // start settings activity
915 this.startActivity(new Intent(this, Preferences.class));
916 return true;
917 case R.id.item_donate:
918 this.showDialog(DIALOG_PREDONATE);
919 return true;
920 case R.id.item_more:
921 try {
922 this.startActivity(new Intent(Intent.ACTION_VIEW, Uri
923 .parse("market://search?q=pub:\"Felix Bechstein\"")));
924 } catch (ActivityNotFoundException e) {
925 Log.e(TAG, "no market", e);
927 return true;
928 case R.id.item_connector:
929 this.changeConnectorMenu();
930 return true;
931 default:
932 return false;
937 * Create a Emoticons {@link Dialog}.
939 * @return Emoticons {@link Dialog}
941 private Dialog createEmoticonsDialog() {
942 final Dialog d = new Dialog(this);
943 d.setTitle(R.string.emo_);
944 d.setContentView(R.layout.emo);
945 d.setCancelable(true);
946 final GridView gridview = (GridView) d.findViewById(R.id.gridview);
947 gridview.setAdapter(new BaseAdapter() {
948 // references to our images
949 private Integer[] mThumbIds = { R.drawable.emo_im_angel,
950 R.drawable.emo_im_cool, R.drawable.emo_im_crying,
951 R.drawable.emo_im_foot_in_mouth, R.drawable.emo_im_happy,
952 R.drawable.emo_im_kissing, R.drawable.emo_im_laughing,
953 R.drawable.emo_im_lips_are_sealed,
954 R.drawable.emo_im_money_mouth, R.drawable.emo_im_sad,
955 R.drawable.emo_im_surprised,
956 R.drawable.emo_im_tongue_sticking_out,
957 R.drawable.emo_im_undecided, R.drawable.emo_im_winking,
958 R.drawable.emo_im_wtf, R.drawable.emo_im_yelling };
960 @Override
961 public long getItemId(final int position) {
962 return 0;
965 @Override
966 public Object getItem(final int position) {
967 return null;
970 @Override
971 public int getCount() {
972 return this.mThumbIds.length;
975 @Override
976 public View getView(final int position, final View convertView,
977 final ViewGroup parent) {
978 ImageView imageView;
979 if (convertView == null) { // if it's not recycled,
980 // initialize some attributes
981 imageView = new ImageView(WebSMS.this);
982 imageView.setLayoutParams(new GridView.LayoutParams(
983 EMOTICONS_SIZE, EMOTICONS_SIZE));
984 imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
985 // imageView.setPadding(0, 0, 0, 0);
986 } else {
987 imageView = (ImageView) convertView;
990 imageView.setImageResource(this.mThumbIds[position]);
991 return imageView;
994 gridview.setOnItemClickListener(new OnItemClickListener() {
995 /** Emoticon id: angel. */
996 private static final int EMO_ANGEL = 0;
997 /** Emoticon id: cool. */
998 private static final int EMO_COOL = 1;
999 /** Emoticon id: crying. */
1000 private static final int EMO_CRYING = 2;
1001 /** Emoticon id: foot in mouth. */
1002 private static final int EMO_FOOT_IN_MOUTH = 3;
1003 /** Emoticon id: happy. */
1004 private static final int EMO_HAPPY = 4;
1005 /** Emoticon id: kissing. */
1006 private static final int EMO_KISSING = 5;
1007 /** Emoticon id: laughing. */
1008 private static final int EMO_LAUGHING = 6;
1009 /** Emoticon id: lips are sealed. */
1010 private static final int EMO_LIPS_SEALED = 7;
1011 /** Emoticon id: money. */
1012 private static final int EMO_MONEY = 8;
1013 /** Emoticon id: sad. */
1014 private static final int EMO_SAD = 9;
1015 /** Emoticon id: suprised. */
1016 private static final int EMO_SUPRISED = 10;
1017 /** Emoticon id: tongue sticking out. */
1018 private static final int EMO_TONGUE = 11;
1019 /** Emoticon id: undecided. */
1020 private static final int EMO_UNDICIDED = 12;
1021 /** Emoticon id: winking. */
1022 private static final int EMO_WINKING = 13;
1023 /** Emoticon id: wtf. */
1024 private static final int EMO_WTF = 14;
1025 /** Emoticon id: yell. */
1026 private static final int EMO_YELL = 15;
1028 @Override
1029 public void onItemClick(final AdapterView<?> adapter, final View v,
1030 final int id, final long arg3) {
1031 EditText et = WebSMS.this.etText;
1032 String e = null;
1033 switch (id) {
1034 case EMO_ANGEL:
1035 e = "O:-)";
1036 break;
1037 case EMO_COOL:
1038 e = "8-)";
1039 break;
1040 case EMO_CRYING:
1041 e = ";-)";
1042 break;
1043 case EMO_FOOT_IN_MOUTH:
1044 e = ":-?";
1045 break;
1046 case EMO_HAPPY:
1047 e = ":-)";
1048 break;
1049 case EMO_KISSING:
1050 e = ":-*";
1051 break;
1052 case EMO_LAUGHING:
1053 e = ":-D";
1054 break;
1055 case EMO_LIPS_SEALED:
1056 e = ":-X";
1057 break;
1058 case EMO_MONEY:
1059 e = ":-$";
1060 break;
1061 case EMO_SAD:
1062 e = ":-(";
1063 break;
1064 case EMO_SUPRISED:
1065 e = ":o";
1066 break;
1067 case EMO_TONGUE:
1068 e = ":-P";
1069 break;
1070 case EMO_UNDICIDED:
1071 e = ":-\\";
1072 break;
1073 case EMO_WINKING:
1074 e = ";-)";
1075 break;
1076 case EMO_WTF:
1077 e = "o.O";
1078 break;
1079 case EMO_YELL:
1080 e = ":O";
1081 break;
1082 default:
1083 break;
1085 et.setText(et.getText() + e);
1086 d.dismiss();
1089 return d;
1093 * {@inheritDoc}
1095 @Override
1096 protected final Dialog onCreateDialog(final int id) {
1097 Dialog d;
1098 AlertDialog.Builder builder;
1099 switch (id) {
1100 case DIALOG_PREDONATE:
1101 builder = new AlertDialog.Builder(this);
1102 builder.setIcon(R.drawable.ic_menu_star);
1103 builder.setTitle(R.string.donate_);
1104 builder.setMessage(R.string.predonate);
1105 builder.setPositiveButton(R.string.donate_,
1106 new DialogInterface.OnClickListener() {
1107 public void onClick(final DialogInterface dialog,
1108 final int which) {
1109 try {
1110 WebSMS.this.startActivity(new Intent(
1111 Intent.ACTION_VIEW, Uri.parse(// .
1112 WebSMS.this.getString(// .
1113 R.string.donate_url))));
1114 } catch (ActivityNotFoundException e) {
1115 Log.e(TAG, "no browser", e);
1116 } finally {
1117 WebSMS.this.showDialog(DIALOG_POSTDONATE);
1121 builder.setNegativeButton(android.R.string.cancel, null);
1122 return builder.create();
1123 case DIALOG_POSTDONATE:
1124 builder = new AlertDialog.Builder(this);
1125 builder.setIcon(R.drawable.ic_menu_star);
1126 builder.setTitle(R.string.remove_ads_);
1127 builder.setMessage(R.string.postdonate);
1128 builder.setPositiveButton(R.string.send_,
1129 new DialogInterface.OnClickListener() {
1130 public void onClick(final DialogInterface dialog,
1131 final int which) {
1132 final Intent in = new Intent(Intent.ACTION_SEND);
1133 in.putExtra(Intent.EXTRA_EMAIL, new String[] {
1134 WebSMS.this.getString(// .
1135 R.string.donate_mail), "" });
1136 // FIXME: "" is a k9 hack. This is fixed in market
1137 // on 26.01.10. wait some more time..
1138 in.putExtra(Intent.EXTRA_TEXT, WebSMS.this
1139 .getImeiHash());
1140 in.putExtra(Intent.EXTRA_SUBJECT, WebSMS.this
1141 .getString(// .
1142 R.string.app_name)
1143 + " " + WebSMS.this.getString(// .
1144 R.string.donate_subject));
1145 in.setType("text/plain");
1146 WebSMS.this.startActivity(in);
1149 builder.setNegativeButton(android.R.string.cancel, null);
1150 return builder.create();
1151 case DIALOG_ABOUT:
1152 d = new Dialog(this);
1153 d.setContentView(R.layout.about);
1154 d.setTitle(this.getString(R.string.about_) + " v"
1155 + this.getString(R.string.app_version));
1156 StringBuffer authors = new StringBuffer();
1157 final ConnectorSpec[] css = getConnectors(
1158 ConnectorSpec.CAPABILITIES_NONE,
1159 ConnectorSpec.STATUS_INACTIVE);
1160 for (ConnectorSpec cs : css) {
1161 final String a = cs.getAuthor();
1162 if (a != null && a.length() > 0) {
1163 authors.append(cs.getName());
1164 authors.append(":\t");
1165 authors.append(a);
1166 authors.append("\n");
1169 ((TextView) d.findViewById(R.id.author_connectors)).setText(authors
1170 .toString().trim());
1171 return d;
1172 case DIALOG_UPDATE:
1173 builder = new AlertDialog.Builder(this);
1174 builder.setIcon(android.R.drawable.ic_dialog_info);
1175 builder.setTitle(R.string.changelog_);
1176 final String[] changes = this.getResources().getStringArray(
1177 R.array.updates);
1178 final StringBuilder buf = new StringBuilder(changes[0]);
1179 for (int i = 1; i < changes.length; i++) {
1180 buf.append("\n\n");
1181 buf.append(changes[i]);
1183 builder.setIcon(android.R.drawable.ic_menu_info_details);
1184 builder.setMessage(buf.toString());
1185 builder.setCancelable(true);
1186 builder.setPositiveButton(android.R.string.ok, null);
1187 return builder.create();
1188 case DIALOG_CUSTOMSENDER:
1189 builder = new AlertDialog.Builder(this);
1190 builder.setTitle(R.string.custom_sender);
1191 builder.setCancelable(true);
1192 final EditText et = new EditText(this);
1193 builder.setView(et);
1194 builder.setPositiveButton(android.R.string.ok,
1195 new DialogInterface.OnClickListener() {
1196 public void onClick(final DialogInterface dialog,
1197 final int id) {
1198 WebSMS.lastCustomSender = et.getText().toString();
1201 builder.setNegativeButton(android.R.string.cancel, null);
1202 return builder.create();
1203 case DIALOG_SENDLATER_DATE:
1204 Calendar c = Calendar.getInstance();
1205 return new DatePickerDialog(this, this, c.get(Calendar.YEAR), c
1206 .get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
1207 case DIALOG_SENDLATER_TIME:
1208 c = Calendar.getInstance();
1209 return new MyTimePickerDialog(this, this, c
1210 .get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true);
1211 case DIALOG_EMO:
1212 return this.createEmoticonsDialog();
1213 default:
1214 return null;
1219 * Log text.
1221 * @param text
1222 * text as resID
1224 public final void log(final int text) {
1225 this.log(this.getString(text));
1229 * Log text.
1231 * @param text
1232 * text
1234 public final void log(final String text) {
1235 try {
1236 Toast.makeText(this.getApplicationContext(), text,
1237 Toast.LENGTH_LONG).show();
1238 } catch (RuntimeException e) {
1239 Log.e(TAG, null, e);
1244 * Send text.
1246 * @param connector
1247 * which connector should be used.
1248 * @param subconnector
1249 * selected {@link SubConnectorSpec} ID
1251 private void send(final ConnectorSpec connector, // .
1252 final String subconnector) {
1253 // fetch text/recipient
1254 final String to = this.etTo.getText().toString();
1255 final String text = this.etText.getText().toString();
1256 if (to.length() == 0 || text.length() == 0) {
1257 return;
1260 if (!prefsNoAds) {
1261 // do not display any ads for donators
1262 // display ads
1263 ((AdView) WebSMS.this.findViewById(R.id.ad))
1264 .setVisibility(View.VISIBLE);
1267 CheckBox v = (CheckBox) this.findViewById(R.id.flashsms);
1268 final boolean flashSMS = (v.getVisibility() == View.VISIBLE)
1269 && v.isEnabled() && v.isChecked();
1270 final SharedPreferences p = PreferenceManager
1271 .getDefaultSharedPreferences(this);
1272 final String defPrefix = p.getString(PREFS_DEFPREFIX, "+49");
1273 final String defSender = p.getString(PREFS_SENDER, "");
1275 final ConnectorCommand command = ConnectorCommand.send(subconnector,
1276 defPrefix, defSender, to.split(","), text, flashSMS);
1277 command.setCustomSender(lastCustomSender);
1278 command.setSendLater(lastSendLater);
1280 try {
1281 runCommand(this, connector, command);
1282 } catch (Exception e) {
1283 Log.e(TAG, null, e);
1284 } finally {
1285 this.reset();
1286 if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
1287 PREFS_AUTOEXIT, false)) {
1288 try {
1289 Thread.sleep(SLEEP_BEFORE_EXIT);
1290 } catch (InterruptedException e) {
1291 Log.e(TAG, null, e);
1293 this.finish();
1299 * @return ID of selected {@link SubConnectorSpec}
1301 private static String getSelectedSubConnectorID() {
1302 if (prefsSubConnectorSpec == null) {
1303 return null;
1305 return prefsSubConnectorSpec.getID();
1309 * A Date was set.
1311 * @param view
1312 * DatePicker View
1313 * @param year
1314 * year set
1315 * @param monthOfYear
1316 * month set
1317 * @param dayOfMonth
1318 * day set
1320 public final void onDateSet(final DatePicker view, final int year,
1321 final int monthOfYear, final int dayOfMonth) {
1322 final Calendar c = Calendar.getInstance();
1323 if (lastSendLater > 0) {
1324 c.setTimeInMillis(lastSendLater);
1326 c.set(Calendar.YEAR, year);
1327 c.set(Calendar.MONTH, monthOfYear);
1328 c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
1329 lastSendLater = c.getTimeInMillis();
1331 MyTimePickerDialog.setOnlyQuaters(prefsSubConnectorSpec
1332 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER_QUARTERS));
1333 this.showDialog(DIALOG_SENDLATER_TIME);
1337 * A Time was set.
1339 * @param view
1340 * TimePicker View
1341 * @param hour
1342 * hour set
1343 * @param minutes
1344 * minutes set
1346 public final void onTimeSet(final TimePicker view, final int hour,
1347 final int minutes) {
1348 if (prefsSubConnectorSpec
1349 .hasFeatures(SubConnectorSpec.FEATURE_SENDLATER_QUARTERS)
1350 && minutes % 15 != 0) {
1351 Toast.makeText(this, R.string.error_sendlater_quater,
1352 Toast.LENGTH_LONG).show();
1353 return;
1356 final Calendar c = Calendar.getInstance();
1357 if (lastSendLater > 0) {
1358 c.setTimeInMillis(lastSendLater);
1360 c.set(Calendar.HOUR_OF_DAY, hour);
1361 c.set(Calendar.MINUTE, minutes);
1362 lastSendLater = c.getTimeInMillis();
1366 * Get MD5 hash of the IMEI (device id).
1368 * @return MD5 hash of IMEI
1370 private String getImeiHash() {
1371 if (imeiHash == null) {
1372 // get imei
1373 TelephonyManager mTelephonyMgr = (TelephonyManager) this
1374 .getSystemService(TELEPHONY_SERVICE);
1375 final String did = mTelephonyMgr.getDeviceId();
1376 if (did != null) {
1377 imeiHash = Utils.md5(did);
1380 return imeiHash;
1384 * Add or update a {@link ConnectorSpec}.
1386 * @param connector
1387 * connector
1389 static final void addConnector(final ConnectorSpec connector) {
1390 synchronized (CONNECTORS) {
1391 if (connector == null || connector.getID() == null
1392 || connector.getName() == null) {
1393 return;
1395 ConnectorSpec c = getConnectorByID(connector.getID());
1396 if (c != null) {
1397 c.update(connector);
1398 } else {
1399 final String name = connector.getName();
1400 if (connector.getSubConnectorCount() == 0 || name == null
1401 || connector.getID() == null) {
1402 Log.w(TAG, "skipped adding defect connector: " + name);
1403 return;
1405 Log.d(TAG, "add connector with id: " + connector.getID());
1406 Log.d(TAG, "add connector with name: " + name);
1407 boolean added = false;
1408 final int l = CONNECTORS.size();
1409 try {
1410 for (int i = 0; i < l; i++) {
1411 final ConnectorSpec cs = CONNECTORS.get(i);
1412 if (name.compareToIgnoreCase(cs.getName()) < 0) {
1413 CONNECTORS.add(i, connector);
1414 added = true;
1415 break;
1418 } catch (NullPointerException e) {
1419 Log.e(TAG, "error while sorting", e);
1421 if (!added) {
1422 CONNECTORS.add(connector);
1424 c = connector;
1426 final SharedPreferences p = PreferenceManager
1427 .getDefaultSharedPreferences(me);
1429 // update connectors balance if needed
1430 if (c.getBalance() == null && c.isReady() && !c.isRunning()
1431 && c.hasCapabilities(ConnectorSpec.CAPABILITIES_UPDATE)
1432 && p.getBoolean(PREFS_AUTOUPDATE, false)) {
1433 final String defPrefix = p
1434 .getString(PREFS_DEFPREFIX, "+49");
1435 final String defSender = p.getString(PREFS_SENDER, "");
1436 runCommand(me, c, ConnectorCommand.update(defPrefix,
1437 defSender));
1440 if (prefsConnectorSpec == null
1441 && prefsConnectorID.equals(connector.getID())) {
1442 prefsConnectorSpec = connector;
1444 prefsSubConnectorSpec = connector.getSubConnector(p
1445 .getString(PREFS_SUBCONNECTOR_ID, ""));
1446 me.setButtons();
1449 final String b = c.getBalance();
1450 final String ob = c.getOldBalance();
1451 if (b != null && (ob == null || !b.equals(ob))) {
1452 me.updateBalance();
1458 * Get {@link ConnectorSpec} by ID.
1460 * @param id
1461 * ID
1462 * @return {@link ConnectorSpec}
1464 private static ConnectorSpec getConnectorByID(final String id) {
1465 synchronized (CONNECTORS) {
1466 if (id == null) {
1467 return null;
1469 final int l = CONNECTORS.size();
1470 for (int i = 0; i < l; i++) {
1471 final ConnectorSpec c = CONNECTORS.get(i);
1472 if (id.equals(c.getID())) {
1473 return c;
1477 return null;
1481 * Get {@link ConnectorSpec} by name.
1483 * @param name
1484 * name
1485 * @param returnSelectedSubConnector
1486 * if not null, array[0] will be set to selected
1487 * {@link SubConnectorSpec}
1488 * @return {@link ConnectorSpec}
1490 private static ConnectorSpec getConnectorByName(final String name,
1491 final SubConnectorSpec[] returnSelectedSubConnector) {
1492 synchronized (CONNECTORS) {
1493 if (name == null) {
1494 return null;
1496 final int l = CONNECTORS.size();
1497 for (int i = 0; i < l; i++) {
1498 final ConnectorSpec c = CONNECTORS.get(i);
1499 final String n = c.getName();
1500 if (name.startsWith(n)) {
1501 if (name.length() == n.length()) {
1502 if (returnSelectedSubConnector != null) {
1503 returnSelectedSubConnector[0] = c
1504 .getSubConnectors()[0];
1506 return c;
1507 } else if (returnSelectedSubConnector != null) {
1509 final SubConnectorSpec[] scs = c.getSubConnectors();
1510 if (scs == null || scs.length == 0) {
1511 continue;
1513 for (SubConnectorSpec sc : scs) {
1514 if (name.endsWith(sc.getName())) {
1515 returnSelectedSubConnector[0] = sc;
1516 return c;
1523 return null;
1527 * Get {@link ConnectorSpec}s by capabilities and/or status.
1529 * @param capabilities
1530 * capabilities needed
1531 * @param status
1532 * status required {@link SubConnectorSpec}
1533 * @return {@link ConnectorSpec}s
1535 static final ConnectorSpec[] getConnectors(final short capabilities,
1536 final short status) {
1537 synchronized (CONNECTORS) {
1538 final ArrayList<ConnectorSpec> ret = new ArrayList<ConnectorSpec>(
1539 CONNECTORS.size());
1540 final int l = CONNECTORS.size();
1541 for (int i = 0; i < l; i++) {
1542 final ConnectorSpec c = CONNECTORS.get(i);
1543 if (c.hasCapabilities(capabilities) && c.hasStatus(status)) {
1544 ret.add(c);
1547 return ret.toArray(new ConnectorSpec[0]);