/* * Copyright (C) 2007-2008 Esmertec AG. * Copyright (C) 2007-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.mms.transaction; import static android.provider.Telephony.Sms.Intents.SMSCB_RECEIVED_ACTION; import com.android.mms.ui.SmsCBClassZeroActivity; import android.database.sqlite.SqliteWrapper; import android.app.Service; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; import android.util.Log; import com.android.internal.telephony.TelephonyIntents; import com.android.mms.LogTag; import com.android.internal.telephony.gsm.SmsCBMessage.SmsCBPage; import java.io.ByteArrayInputStream; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import com.android.mms.R; import com.android.mms.ui.CellBroadcastSmsActivity; import android.preference.PreferenceManager; import android.content.SharedPreferences; import com.android.mms.ui.MessagingPreferenceActivity; import android.text.TextUtils; import android.media.AudioManager; /** * This service essentially plays the role of a "worker thread", allowing us to * store incoming messages to the database, update notifications, etc. without * blocking the main thread that SmsReceiver runs on. */ public class SmsCBReceiverService extends Service { private static final String TAG = "SmsCBReceiverService"; private static final Uri CBSMS_URI_T = Uri.parse("content://sms/cbsms"); private ServiceHandler mServiceHandler; private Looper mServiceLooper; private String COLUMN_ID = "_id"; private String COLUMN_ADDRESS = "address"; private String COLUMN_BODY = "body"; private String COLUMN_DATE = "date"; private String COLUMN_READ = "read"; private String COLUMN_SEEN = "seen"; private String COLUMN_ICONID = "iconId"; private String COLUMN_LANGID = "langId"; private int DEFAULT_ICON = R.drawable.unread_cbsms; private int READ_ICON = R.drawable.read_cbsms; private int mResultCode; @Override public void onCreate() { // Temporarily removed for this duplicate message track down. // if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { // Log.v(TAG, "onCreate"); // } Log.i(TAG, "onCreate"); HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // Temporarily removed for this duplicate message track down. // if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { // Log.v(TAG, "onStart: #" + startId + ": " + intent.getExtras()); // } mResultCode = intent != null ? intent.getIntExtra("result", 0) : 0; Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); return Service.START_NOT_STICKY; } @Override public void onDestroy() { // Temporarily removed for this duplicate message track down. // if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { // Log.v(TAG, "onDestroy"); // } mServiceLooper.quit(); } @Override public IBinder onBind(Intent intent) { return null; } private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } /** * Handle incoming transaction requests. The incoming requests are * initiated by the MMSC Server or by the MMS Client itself. */ @Override public void handleMessage(Message msg) { Log.i(TAG, "handleMessage"); int serviceId = msg.arg1; Intent intent = (Intent) msg.obj; if (intent != null) { String action = intent.getAction(); int error = intent.getIntExtra("errorCode", 0); if (SMSCB_RECEIVED_ACTION.equals(action)) { handleSmsCBReceived(intent, error); } // NOTE: We MUST not call stopSelf() directly, since we need to // make sure the wake lock acquired by AlertReceiver is // released. SmsCBReceiver.finishStartingService(SmsCBReceiverService.this, serviceId); } } } private static SmsCBPage parsePage(byte[] bytePage) { ByteArrayInputStream inStream = new ByteArrayInputStream(bytePage); SmsCBPage page = new SmsCBPage(); page.gs = inStream.read(); page.messageCode = inStream.read(); page.updateNum = inStream.read(); int high = inStream.read(); int low = inStream.read(); Log.i(TAG, "parsePage high " + high + "low :" + low); page.msgId = (high << 0x8) | low; page.dcs = inStream.read(); page.sequenceNum = inStream.read(); page.totalNum = inStream.read(); high = inStream.read(); low = inStream.read(); Log.i(TAG, "parsePage high " + high + "low :" + low); page.langId = (high << 0x8) | low; int length; length = inStream.read(); byte[] byteContent1 = new byte[length]; Log.i(TAG, "parsePage input " + "length of bytes array :" + length + "page.msgId " + page.msgId); inStream.read(byteContent1, 0, length); page.content = new String(byteContent1); Log.i(TAG, "parsePage input " + page.content); return page; } private void handleSmsCBReceived(Intent intent, int error) { Object[] messages = (Object[]) intent.getSerializableExtra("pages"); byte[][] bytePages = new byte[messages.length][]; Log.i(TAG, "handleSmsCBReceived length" + messages.length); SmsCBPage[] pages = new SmsCBPage[messages.length]; for (int i = 0; i < messages.length; i++) { bytePages[i] = (byte[]) messages[i]; } for (int i = 0; i < messages.length; i++) { pages[i] = new SmsCBPage(); pages[i] = parsePage(bytePages[i]); Log.i(TAG, "handleSmsCBReceived content " + pages[i].content); } Uri messageUri = insertMessage(this, pages, error); if (messageUri != null) { // Called off of the UI thread so ok to block. String string = "SMS CB !"; showNotification(string, null, null, R.drawable.stat_notify_sms, R.drawable.stat_notify_sms, intent); if (CellBroadcastSmsActivity.IschangeList()) { notifyListChanged(); } } } /** * If the message is a class-zero message, display it immediately and return * null. Otherwise, store it using the <code>ContentResolver</code> and * return the <code>Uri</code> of the thread containing this message so that * we can use it for notification. */ private Uri insertMessage(Context context, SmsCBPage[] msgs, int error) { // Build the helper classes to parse the messages. Log.i(TAG, "insertMessage gs" + msgs[0].gs); if (msgs[0].gs == 0) { displayClassZeroMessage(context, msgs); return null; } // else if (sms.isReplace()) { // return replaceMessage(context, msgs, error); // } else { return storeMessage(context, msgs, error); } } private Uri storeMessage(Context context, SmsCBPage[] msgs, int error) { SmsCBPage msg = new SmsCBPage(); msg = msgs[0]; int pageCount = msgs.length; Log.i(TAG, "storeMessage : "); // Store the message in the content provider. ContentValues values = extractContentValues(msg); if (pageCount == 1) { // There is only one part, so grab the body directly. values.put(COLUMN_BODY, msg.content); } else { // Build up the body from the parts. StringBuilder body = new StringBuilder(); for (int i = 0; i < pageCount; i++) { body.append(msgs[i].content); } values.put(COLUMN_BODY, body.toString()); } Log.i(TAG, "storeMessage : msgId " + msg.msgId); values.put(COLUMN_ADDRESS, msg.msgId); values.put(COLUMN_ICONID, DEFAULT_ICON); values.put(COLUMN_LANGID, msg.langId); // values.put(COLUMN_DATE, enable); ContentResolver resolver = context.getContentResolver(); Uri insertedUri = SqliteWrapper.insert(context, resolver, CBSMS_URI_T, values); // Now make sure we're not over the limit in stored messages // Recycler.getSmsRecycler().deleteOldMessagesByThreadId(getApplicationContext(), // threadId); return insertedUri; } /** * Extract all the content values except the body from an SMS message. */ private ContentValues extractContentValues(SmsCBPage msg) { // Store the message in the content provider. ContentValues values = new ContentValues(); values.put(COLUMN_READ, 0); values.put(COLUMN_SEEN, 0); return values; } /** * Displays a class-zero message immediately in a pop-up window with the * number from where it received the Notification with the body of the * message * */ private void displayClassZeroMessage(Context context, SmsCBPage[] msgs) { // Using NEW_TASK here is necessary because we're calling // startActivity from outside an activity. Intent smscbDialogIntent = new Intent(context, SmsCBClassZeroActivity.class) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); Log.i(TAG, "displayClassZeroMessage length :" + msgs.length); smscbDialogIntent.putExtra("length", msgs.length); for (int i = 0; i < msgs.length; i++) { String gsS = "gs" + i; smscbDialogIntent.putExtra(gsS, msgs[i].gs); String messageCodeS = "messageCode" + i; smscbDialogIntent.putExtra(messageCodeS, msgs[i].messageCode); String updateNumS = "updateNum" + i; smscbDialogIntent.putExtra(updateNumS, msgs[i].updateNum); String msgIdS = "msgId" + i; smscbDialogIntent.putExtra(msgIdS, msgs[i].msgId); Log.i(TAG, "storeMessage : msgId " + msgs[i].msgId); String dcsS = "dcs" + i; smscbDialogIntent.putExtra(dcsS, msgs[i].dcs); String sequenceNumS = "sequenceNum" + i; smscbDialogIntent.putExtra(sequenceNumS, msgs[i].gs); String totalNumS = "totalNum" + i; smscbDialogIntent.putExtra(totalNumS, msgs[i].totalNum); String langIdS = "langId" + i; smscbDialogIntent.putExtra(langIdS, msgs[i].langId); Log.i(TAG, "onCreate langId :" + msgs[i].langId); String contentS = "content" + i; smscbDialogIntent.putExtra(contentS, msgs[i].content); Log.i(TAG, "displayClassZeroMessage content :" + msgs[i].content); } context.startActivity(smscbDialogIntent); } private void registerForServiceStateChanges() { Context context = getApplicationContext(); unRegisterForServiceStateChanges(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED); if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { Log.v(TAG, "registerForServiceStateChanges"); } context.registerReceiver(SmsReceiver.getInstance(), intentFilter); } private void unRegisterForServiceStateChanges() { if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { Log.v(TAG, "unRegisterForServiceStateChanges"); } try { Context context = getApplicationContext(); context.unregisterReceiver(SmsReceiver.getInstance()); } catch (IllegalArgumentException e) { // Allow un-matched register-unregister calls } } private void showNotification(String tickerText, String contentTitle, String contentText, int id, int resId, Intent intent) { SharedPreferences sp = PreferenceManager .getDefaultSharedPreferences(this); Log.i(TAG, "showNotification content :" + tickerText); NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); Notification notification = new Notification(resId, tickerText, System.currentTimeMillis()); String vibrateWhen; if (sp.contains(MessagingPreferenceActivity.NOTIFICATION_VIBRATE_WHEN)) { vibrateWhen = sp .getString( MessagingPreferenceActivity.NOTIFICATION_VIBRATE_WHEN, null); } else if (sp .contains(MessagingPreferenceActivity.NOTIFICATION_VIBRATE)) { vibrateWhen = sp.getBoolean( MessagingPreferenceActivity.NOTIFICATION_VIBRATE, false) ? getString(R.string.prefDefault_vibrate_true) : getString(R.string.prefDefault_vibrate_false); } else { vibrateWhen = getString(R.string.prefDefault_vibrateWhen); } boolean vibrateAlways = vibrateWhen.equals("always"); boolean vibrateSilent = vibrateWhen.equals("silent"); AudioManager audioManager = (AudioManager) this .getSystemService(Context.AUDIO_SERVICE); boolean nowSilent = audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE; if (vibrateAlways || vibrateSilent && nowSilent) { notification.defaults |= Notification.DEFAULT_VIBRATE; } String ringtoneStr = sp.getString( MessagingPreferenceActivity.NOTIFICATION_RINGTONE, null); notification.sound = TextUtils.isEmpty(ringtoneStr) ? null : Uri .parse(ringtoneStr); notification.flags |= Notification.FLAG_SHOW_LIGHTS; notification.defaults |= Notification.DEFAULT_LIGHTS; Log.i(TAG, "showNotification content :" + tickerText); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0); notification.setLatestEventInfo(this, contentTitle, contentText, contentIntent); nm.notify(id, notification); } private void notifyListChanged() { Log.i(TAG, "notifyListChanged : "); Context context = getApplicationContext(); Intent intent = new Intent(context, CellBroadcastSmsActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } }