/* * Copyright (C) 2008 Esmertec AG. * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.im.service; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.provider.Telephony; import static android.provider.Telephony.Sms.Intents.DATA_SMS_RECEIVED_ACTION; import android.telephony.PhoneNumberUtils; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.util.Log; import com.android.im.engine.SmsService; public class AndroidSmsService implements SmsService { private static final String TAG = RemoteImService.TAG; private static final String SMS_STATUS_RECEIVED_ACTION = "com.android.im.SmsService.SMS_STATUS_RECEIVED"; private static final int sMaxSmsLength = SmsMessage.MAX_USER_DATA_BYTES - 7/* UDH size */; private Context mContext; private SmsReceiver mSmsReceiver; private IntentFilter mIntentFilter; private boolean mStarted; /*package*/HashMap<Integer, ListenerList> mListeners; /*package*/HashMap<Long, SmsSendFailureCallback> mFailureCallbacks; public AndroidSmsService(Context context) { mContext = context; mSmsReceiver = new SmsReceiver(); mIntentFilter = new IntentFilter( Telephony.Sms.Intents.DATA_SMS_RECEIVED_ACTION); mIntentFilter.addDataScheme("sms"); mListeners = new HashMap<Integer, ListenerList>(); mFailureCallbacks = new HashMap<Long, SmsSendFailureCallback>(); } public int getMaxSmsLength() { return sMaxSmsLength; } public void sendSms(String dest, int port, byte[] data) { sendSms(dest, port, data, null); } public void sendSms(String dest, int port, byte[] data, SmsSendFailureCallback callback) { if (Log.isLoggable(TAG, Log.DEBUG)) { try { log(dest + ":" + port + " >>> " + new String(data, "UTF-8")); } catch (UnsupportedEncodingException e) { } } if (data.length > sMaxSmsLength) { Log.e(TAG, "SMS data message can only contain " + sMaxSmsLength + " bytes"); return; } SmsManager smsManager = SmsManager.getDefault(); PendingIntent sentIntent; if (callback == null) { sentIntent = null; } else { long msgId = genMsgId(); mFailureCallbacks.put(msgId, callback); sentIntent = PendingIntent.getBroadcast(mContext, 0, new Intent( SMS_STATUS_RECEIVED_ACTION, Uri.parse("content://sms/" + msgId), /*uri*/ mContext, SmsReceiver.class), 0); } smsManager.sendDataMessage(dest, null/*use the default SMSC*/, (short) port, data, sentIntent, null/*do not require delivery report*/); } public void addSmsListener(String from, int port, SmsListener listener) { ListenerList l = mListeners.get(port); if (l == null) { l = new ListenerList(port); mListeners.put(port, l); if (Log.isLoggable(TAG, Log.DEBUG)) { log("Register SMS receiver on port " + port); } // We didn't listen on the port yet, register the receiver with the // additional port. mIntentFilter.addDataAuthority("*", String.valueOf(port)); mContext.registerReceiver(mSmsReceiver, mIntentFilter); synchronized (this) { mStarted = true; } } l.addListener(from, listener); } public void removeSmsListener(SmsListener listener) { Iterator<ListenerList> iter = mListeners.values().iterator(); while (iter.hasNext()) { ListenerList l = iter.next(); l.removeListener(listener); if (l.isEmpty()) { iter.remove(); } } } public synchronized void stop() { if (mStarted) { mContext.unregisterReceiver(mSmsReceiver); mStarted = false; } } private static long sNextMsgId = 0; private static synchronized long genMsgId() { return sNextMsgId++; } private static void log(String msg) { Log.d(TAG, "[SmsService]" + msg); } private final class SmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (SMS_STATUS_RECEIVED_ACTION.equals(intent.getAction())) { if (Log.isLoggable(TAG, Log.DEBUG)) { log("send status received"); } long id = ContentUris.parseId(intent.getData()); SmsSendFailureCallback callback = mFailureCallbacks.get(id); if (callback == null) { return; } int resultCode = getResultCode(); if (resultCode == SmsManager.RESULT_ERROR_GENERIC_FAILURE) { callback.onFailure(SmsSendFailureCallback.ERROR_GENERIC_FAILURE); } else if (resultCode == SmsManager.RESULT_ERROR_RADIO_OFF) { callback.onFailure(SmsSendFailureCallback.ERROR_RADIO_OFF); } mFailureCallbacks.remove(id); } else if (DATA_SMS_RECEIVED_ACTION.equals(intent.getAction())){ Uri uri = intent.getData(); int port = uri.getPort(); if (Log.isLoggable(TAG, Log.DEBUG)) { log("Received sms on port:" + port); } ListenerList listeners = mListeners.get(port); if (listeners == null) { if (Log.isLoggable(TAG, Log.DEBUG)) { log("No listener on port " + port + ", ignore"); } return; } SmsMessage[] receivedSms = Telephony.Sms.Intents.getMessagesFromIntent(intent); for (SmsMessage msg : receivedSms) { String from = msg.getOriginatingAddress(); byte[] data = msg.getUserData(); if (Log.isLoggable(TAG, Log.DEBUG)) { try { log(from + ":" + port + " <<< " + new String(data, "UTF-8")); } catch (UnsupportedEncodingException e) { } } listeners.notifySms(from, data); } } } } private final static class ListenerList { private int mPort; private ArrayList<String> mAddrList; private ArrayList<SmsListener> mListenerList; public ListenerList(int port) { mPort = port; mAddrList = new ArrayList<String>(); mListenerList = new ArrayList<SmsListener>(); } public synchronized void addListener(String addr, SmsListener listener) { mAddrList.add(addr); mListenerList.add(listener); } public synchronized void removeListener(SmsListener listener) { int index = -1; while ((index = mListenerList.indexOf(listener)) != -1) { mAddrList.remove(index); mListenerList.remove(index); } } public void notifySms(String addr, byte[] data) { int N = mListenerList.size(); for (int i = 0; i < N; i++) { String listenAddr = mAddrList.get(i); if (ANY_ADDRESS.equals(listenAddr) || addr.equals(listenAddr) || PhoneNumberUtils.compare(addr, listenAddr)) { mListenerList.get(i).onIncomingSms(data); } } } public boolean isEmpty() { return mListenerList.isEmpty(); } public int getPort() { return mPort; } } }