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
.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
;
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;
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;
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;
228 private TextView etTextLabel
;
231 private boolean showExtras
= false;
233 /** TextWatcher updating char count on writing. */
234 private TextWatcher textWatcher
= new TextWatcher() {
238 public void afterTextChanged(final Editable s
) {
239 int[] l
= SmsMessage
.calculateLength(s
, false);
240 WebSMS
.this.etTextLabel
.setText(l
[0] + "/" + l
[2]);
244 public void beforeTextChanged(final CharSequence s
, final int start
,
245 final int count
, final int after
) {
249 public void onTextChanged(final CharSequence s
, final int start
,
250 final int before
, final int count
) {
255 * Parse data pushed by {@link Intent}.
260 private void parseIntent(final Intent intent
) {
261 final String action
= intent
.getAction();
262 if (action
== null) {
266 // launched by clicking a sms: link, target number is in URI.
267 final Uri uri
= intent
.getData();
269 final String scheme
= uri
.getScheme();
270 if (scheme
.equals("sms") || scheme
.equals("smsto")) {
271 String s
= uri
.getSchemeSpecificPart();
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
280 if (helperAPI5c
!= null) {
282 n
= helperAPI5c
.getNameForNumber(this, s
);
283 } catch (NoClassDefFoundError e
) {
287 if (helperAPI5c
== null) {
288 Cursor c
= this.managedQuery(Phones
.CONTENT_URI
,
289 new String
[] { PhonesColumns
.NUMBER
,
292 PhonesColumns
.NUMBER
+ " = '" + s
+ "'",
294 if (c
.moveToFirst()) {
295 n
= c
.getString(c
.getColumnIndex(// .
296 PeopleColumns
.DISPLAY_NAME
));
300 s
= n
+ " <" + s
+ ">, ";
303 ((EditText
) this.findViewById(R
.id
.to
)).setText(s
);
306 final Bundle extras
= intent
.getExtras();
307 if (extras
!= null) {
308 s
= extras
.getCharSequence(Intent
.EXTRA_TEXT
).toString();
310 ((EditText
) this.findViewById(R
.id
.text
)).setText(s
);
313 s
= extras
.getString(EXTRA_ERRORMESSAGE
);
315 Toast
.makeText(this, s
, Toast
.LENGTH_LONG
).show();
319 // do not display any ads for donators
321 ((AdView
) WebSMS
.this.findViewById(R
.id
.ad
))
322 .setVisibility(View
.VISIBLE
);
331 @SuppressWarnings("unchecked")
333 public final void onCreate(final Bundle savedInstanceState
) {
334 super.onCreate(savedInstanceState
);
335 this.requestWindowFeature(Window
.FEATURE_INDETERMINATE_PROGRESS
);
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);
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
);
366 this.showDialog(DIALOG_UPDATE
);
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
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
);
388 Log
.d(TAG
, "loaded connectors: " + CONNECTORS
.size());
392 lastTo
= p
.getString(PREFS_TO
, "");
393 lastMsg
= p
.getString(PREFS_TEXT
, "");
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
);
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
);
442 protected final void onResume() {
444 // set accounts' balance to gui
445 this.updateBalance();
447 // if coming from prefs..
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
,
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();
472 Log
.d(TAG
, "clear connector cache (" + s1
+ "/" + s2
+ ")");
474 this.updateConnectors();
480 // reload text/recipient from local store
481 if (lastMsg
!= null) {
482 this.etText
.setText(lastMsg
);
484 this.etText
.setText("");
486 if (lastTo
!= null) {
487 this.etTo
.setText(lastTo
);
489 this.etTo
.setText("");
492 if (lastTo
!= null && lastTo
.length() > 0) {
493 this.etText
.requestFocus();
495 this.etTo
.requestFocus();
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) {
512 if (buf
.length() > 0) {
515 buf
.append(cs
.getName());
520 this.tvBalances
.setText(this.getString(R
.string
.free_
) + " "
521 + buf
.toString() + " "
522 + this.getString(R
.string
.click_for_update
));
529 protected final void 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(
539 editor
.putString(PREFS_TO
, lastTo
);
540 editor
.putString(PREFS_TEXT
, lastMsg
);
544 this.savePreferences();
548 protected final void onDestroy() {
550 final Editor editor
= PreferenceManager
.getDefaultSharedPreferences(
553 final ByteArrayOutputStream out
= new ByteArrayOutputStream();
554 ObjectOutputStream objOut
= new ObjectOutputStream(
555 new BufferedOutputStream(out
, BUFSIZE
));
556 objOut
.writeObject(CONNECTORS
);
558 final String s
= String
.valueOf(Base64Coder
.encode(out
561 editor
.putString(PREFS_CONNECTORS
, s
);
562 } catch (IOException e
) {
563 editor
.remove(PREFS_CONNECTORS
);
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
,
579 final boolean bShowCancel
= !p
.getBoolean(PREFS_HIDE_CANCEL_BUTTON
,
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
);
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
);
593 v
.setVisibility(View
.GONE
);
596 v
= this.findViewById(R
.id
.emo
);
597 if (bShowEmoticons
) {
598 v
.setVisibility(View
.VISIBLE
);
600 v
.setVisibility(View
.GONE
);
603 v
= this.findViewById(R
.id
.cancel
);
605 v
.setVisibility(View
.VISIBLE
);
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];
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];
631 this.getString(R
.string
.connectors_switch
) + " "
632 + prefsConnectorSpec
.getName(),
633 Toast
.LENGTH_LONG
).show();
635 prefsConnectorSpec
= null;
636 prefsSubConnectorSpec
= null;
640 MobilePhoneAdapter
.setMoileNubersObly(p
.getBoolean(PREFS_MOBILES_ONLY
,
644 String hash
= Utils
.md5(p
.getString(PREFS_SENDER
, ""));
645 for (String h
: NO_AD_HASHS
) {
646 if (hash
.equals(h
)) {
651 if (!prefsNoAds
&& this.getImeiHash() != null) {
652 for (String h
: NO_AD_HASHS
) {
653 if (imeiHash
.equals(h
)) {
664 * Show/hide, enable/disable send buttons.
666 private void setButtons() {
667 Button btn
= (Button
) this.findViewById(R
.id
.send_
);
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
);
683 this.findViewById(R
.id
.extras
).setVisibility(View
.GONE
);
685 if (this.showExtras
&& sFlashsms
) {
686 this.findViewById(R
.id
.flashsms
).setVisibility(View
.VISIBLE
);
688 this.findViewById(R
.id
.flashsms
).setVisibility(View
.GONE
);
690 if (this.showExtras
&& sCustomsender
) {
691 this.findViewById(R
.id
.custom_sender
).setVisibility(
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
);
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();
713 * Resets persistent store.
715 private void reset() {
716 this.etText
.setText("");
717 this.etTo
.setText("");
720 lastCustomSender
= null;
722 // save user preferences
723 SharedPreferences
.Editor editor
= PreferenceManager
724 .getDefaultSharedPreferences(this).edit();
725 editor
.putString(PREFS_TO
, "");
726 editor
.putString(PREFS_TEXT
, "");
732 final void savePreferences() {
733 if (prefsConnectorSpec
!= null) {
734 PreferenceManager
.getDefaultSharedPreferences(this).edit()
735 .putString(PREFS_CONNECTOR_ID
, prefsConnectorSpec
.getID())
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
757 runCommand(this, cs
, ConnectorCommand
.update(defPrefix
, defSender
));
762 * Send a command as broadcast.
765 * WebSMS required for performance issues
767 * {@link ConnectorSpec}
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
);
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
);
786 case ConnectorCommand
.TYPE_UPDATE
:
787 intent
.setAction(connector
.getPackage()
788 + Connector
.ACTION_RUN_UPDATE
);
789 connector
.addStatus(ConnectorSpec
.STATUS_UPDATING
);
794 Log
.d(TAG
, "send broadcast: " + intent
.getAction());
795 context
.sendBroadcast(intent
);
801 public final void onClick(final View v
) {
804 this.updateFreecount();
807 this.send(prefsConnectorSpec
, WebSMS
.getSelectedSubConnectorID());
812 case R
.id
.change_connector
:
813 case R
.id
.change_connector_u
:
814 this.changeConnectorMenu();
817 this.showExtras
= !this.showExtras
;
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
);
826 lastCustomSender
= null;
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
);
839 this.showDialog(DIALOG_EMO
);
850 public final boolean onCreateOptionsMenu(final Menu menu
) {
851 MenuInflater inflater
= this.getMenuInflater();
852 inflater
.inflate(R
.menu
.menu
, menu
);
854 menu
.removeItem(R
.id
.item_donate
);
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());
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
, // .
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)
895 e
.putString(PREFS_CONNECTOR_ID
, prefsConnectorSpec
897 e
.putString(PREFS_SUBCONNECTOR_ID
,
898 prefsSubConnectorSpec
.getID());
902 builder
.create().show();
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
);
914 case R
.id
.item_settings
: // start settings activity
915 this.startActivity(new Intent(this, Preferences
.class));
917 case R
.id
.item_donate
:
918 this.showDialog(DIALOG_PREDONATE
);
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
);
928 case R
.id
.item_connector
:
929 this.changeConnectorMenu();
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
};
961 public long getItemId(final int position
) {
966 public Object
getItem(final int position
) {
971 public int getCount() {
972 return this.mThumbIds
.length
;
976 public View
getView(final int position
, final View convertView
,
977 final ViewGroup parent
) {
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);
987 imageView
= (ImageView
) convertView
;
990 imageView
.setImageResource(this.mThumbIds
[position
]);
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;
1029 public void onItemClick(final AdapterView
<?
> adapter
, final View v
,
1030 final int id
, final long arg3
) {
1031 EditText et
= WebSMS
.this.etText
;
1043 case EMO_FOOT_IN_MOUTH
:
1055 case EMO_LIPS_SEALED
:
1085 et
.setText(et
.getText() + e
);
1096 protected final Dialog
onCreateDialog(final int id
) {
1098 AlertDialog
.Builder builder
;
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
,
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
);
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
,
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
1140 in
.putExtra(Intent
.EXTRA_SUBJECT
, WebSMS
.this
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();
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");
1166 authors
.append("\n");
1169 ((TextView
) d
.findViewById(R
.id
.author_connectors
)).setText(authors
1170 .toString().trim());
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(
1178 final StringBuilder buf
= new StringBuilder(changes
[0]);
1179 for (int i
= 1; i
< changes
.length
; i
++) {
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
,
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);
1212 return this.createEmoticonsDialog();
1224 public final void log(final int text
) {
1225 this.log(this.getString(text
));
1234 public final void log(final String text
) {
1236 Toast
.makeText(this.getApplicationContext(), text
,
1237 Toast
.LENGTH_LONG
).show();
1238 } catch (RuntimeException e
) {
1239 Log
.e(TAG
, null, e
);
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) {
1261 // do not display any ads for donators
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
);
1281 runCommand(this, connector
, command
);
1282 } catch (Exception e
) {
1283 Log
.e(TAG
, null, e
);
1286 if (PreferenceManager
.getDefaultSharedPreferences(this).getBoolean(
1287 PREFS_AUTOEXIT
, false)) {
1289 Thread
.sleep(SLEEP_BEFORE_EXIT
);
1290 } catch (InterruptedException e
) {
1291 Log
.e(TAG
, null, e
);
1299 * @return ID of selected {@link SubConnectorSpec}
1301 private static String
getSelectedSubConnectorID() {
1302 if (prefsSubConnectorSpec
== null) {
1305 return prefsSubConnectorSpec
.getID();
1315 * @param monthOfYear
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
);
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();
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) {
1373 TelephonyManager mTelephonyMgr
= (TelephonyManager
) this
1374 .getSystemService(TELEPHONY_SERVICE
);
1375 final String did
= mTelephonyMgr
.getDeviceId();
1377 imeiHash
= Utils
.md5(did
);
1384 * Add or update a {@link ConnectorSpec}.
1389 static final void addConnector(final ConnectorSpec connector
) {
1390 synchronized (CONNECTORS
) {
1391 if (connector
== null || connector
.getID() == null
1392 || connector
.getName() == null) {
1395 ConnectorSpec c
= getConnectorByID(connector
.getID());
1397 c
.update(connector
);
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
);
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();
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
);
1418 } catch (NullPointerException e
) {
1419 Log
.e(TAG
, "error while sorting", e
);
1422 CONNECTORS
.add(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
,
1440 if (prefsConnectorSpec
== null
1441 && prefsConnectorID
.equals(connector
.getID())) {
1442 prefsConnectorSpec
= connector
;
1444 prefsSubConnectorSpec
= connector
.getSubConnector(p
1445 .getString(PREFS_SUBCONNECTOR_ID
, ""));
1449 final String b
= c
.getBalance();
1450 final String ob
= c
.getOldBalance();
1451 if (b
!= null && (ob
== null || !b
.equals(ob
))) {
1458 * Get {@link ConnectorSpec} by ID.
1462 * @return {@link ConnectorSpec}
1464 private static ConnectorSpec
getConnectorByID(final String id
) {
1465 synchronized (CONNECTORS
) {
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())) {
1481 * Get {@link ConnectorSpec} by 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
) {
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];
1507 } else if (returnSelectedSubConnector
!= null) {
1509 final SubConnectorSpec
[] scs
= c
.getSubConnectors();
1510 if (scs
== null || scs
.length
== 0) {
1513 for (SubConnectorSpec sc
: scs
) {
1514 if (name
.endsWith(sc
.getName())) {
1515 returnSelectedSubConnector
[0] = sc
;
1527 * Get {@link ConnectorSpec}s by capabilities and/or status.
1529 * @param capabilities
1530 * capabilities needed
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
>(
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
)) {
1547 return ret
.toArray(new ConnectorSpec
[0]);