package net.everythingandroid.smspopup.service;
import java.util.List;
import net.everythingandroid.smspopup.BuildConfig;
import net.everythingandroid.smspopup.R;
import net.everythingandroid.smspopup.provider.SmsMmsMessage;
import net.everythingandroid.smspopup.provider.SmsPopupContract.ContactNotifications;
import net.everythingandroid.smspopup.util.Log;
import net.everythingandroid.smspopup.util.ManageKeyguard;
import net.everythingandroid.smspopup.util.ManageNotification;
import net.everythingandroid.smspopup.util.ManagePreferences;
import net.everythingandroid.smspopup.util.ManagePreferences.Defaults;
import net.everythingandroid.smspopup.util.ManageWakeLock;
import net.everythingandroid.smspopup.util.SmsMessageSender;
import net.everythingandroid.smspopup.util.SmsPopupUtils;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.telephony.SmsMessage.MessageClass;
import android.telephony.TelephonyManager;
import android.widget.Toast;
import com.commonsware.cwac.wakeful.WakefulIntentService;
public class SmsReceiverService extends WakefulIntentService {
private static final String TAG = SmsReceiverService.class.getName();
private static final String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
private static final String ACTION_MMS_RECEIVED =
"android.provider.Telephony.WAP_PUSH_RECEIVED";
private static final String ACTION_MESSAGE_RECEIVED =
"net.everythingandroid.smspopup.MESSAGE_RECEIVED";
private static final String MMS_DATA_TYPE = "application/vnd.wap.mms-message";
// http://android.git.kernel.org/?p=platform/packages/apps/Mms.git;a=blob;f=src/com/android/mms/transaction/SmsReceiverService.java
public static final String MESSAGE_SENT_ACTION = "com.android.mms.transaction.MESSAGE_SENT";
/*
* This is the number of retries and pause between retries that we will keep checking the system
* message database for the latest incoming message
*/
private static final int MESSAGE_RETRY = 8;
private static final int MESSAGE_RETRY_PAUSE = 1000;
private Context context;
private int mResultCode;
private static final int TOAST_HANDLER_MESSAGE_SENT = 0;
private static final int TOAST_HANDLER_MESSAGE_SEND_LATER = 1;
private static final int TOAST_HANDLER_MESSAGE_FAILED = 2;
public SmsReceiverService() {
super(TAG);
}
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
@Override
protected void doWakefulWork(Intent intent) {
if (BuildConfig.DEBUG)
Log.v("SMSReceiverService: doWakefulWork()");
mResultCode = 0;
if (intent != null) {
mResultCode = intent.getIntExtra("result", 0);
final String action = intent.getAction();
final String dataType = intent.getType();
if (ACTION_SMS_RECEIVED.equals(action)) {
handleSmsReceived(intent);
} else if (ACTION_MMS_RECEIVED.equals(action) && MMS_DATA_TYPE.equals(dataType)) {
handleMmsReceived(intent);
} else if (MESSAGE_SENT_ACTION.equals(action)) {
handleSmsSent(intent);
} else if (ACTION_MESSAGE_RECEIVED.equals(action)) {
handleMessageReceived(intent);
}
}
}
/**
* Handle receiving a SMS message
*/
private void handleSmsReceived(Intent intent) {
if (BuildConfig.DEBUG)
Log.v("SMSReceiver: Intercept SMS");
Bundle bundle = intent.getExtras();
if (bundle != null) {
SmsMessage[] messages = SmsPopupUtils.getMessagesFromIntent(intent);
if (messages != null) {
notifyMessageReceived(new SmsMmsMessage(
context, messages, System.currentTimeMillis()));
}
}
}
private void notifyMessageReceived(SmsMmsMessage message) {
// Class 0 SMS, let the system handle this
if (message.isSms() && message.getMessageClass() == MessageClass.CLASS_0) {
return;
}
if (message.isSprintVisualVoicemail()) {
return;
}
// Unknown sender and empty body, ignore
if (context.getString(android.R.string.unknownName).equals(message.getContactName())
&& "".equals(message.getMessageBody())) {
return;
}
// Fetch preferences
ManagePreferences mPrefs = new ManagePreferences(
context, message.getContactId(), message.getContactLookupKey());
// Whether or not the popup should only show when keyguard is on
boolean onlyShowOnKeyguard =
mPrefs.getBoolean(R.string.pref_onlyShowOnKeyguard_key,
Defaults.PREFS_ONLY_SHOW_ON_KEYGUARD);
// check if popup is enabled for this contact
boolean showPopup =
mPrefs.getBoolean(R.string.pref_popup_enabled_key,
Defaults.PREFS_SHOW_POPUP,
ContactNotifications.POPUP_ENABLED);
// check if notifications are on for this contact
boolean notifEnabled =
mPrefs.getBoolean(R.string.pref_notif_enabled_key,
Defaults.PREFS_NOTIF_ENABLED,
ContactNotifications.ENABLED);
// get docked state of phone
boolean docked = mPrefs.getInt(R.string.pref_docked_key, Intent.EXTRA_DOCK_STATE_UNDOCKED)
!= Intent.EXTRA_DOCK_STATE_UNDOCKED;
mPrefs.close();
// Fetch call state, if the user is in a call or the phone is ringing we don't want
// to show the popup
TelephonyManager mTM =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
boolean callStateIdle = mTM.getCallState() == TelephonyManager.CALL_STATE_IDLE;
// Init keyguard manager
ManageKeyguard.initialize(context);
/*
* If popup is enabled for this user -AND- the user is not in a call --AND- phone is
* not docked -AND- (screen is locked -OR- (setting is OFF to only show on keyguard -AND-
* user is not in messaging app: then show the popup activity, otherwise check if
* notifications are on and just use the standard notification))
*/
if (showPopup && callStateIdle && !docked
&& (ManageKeyguard.inKeyguardRestrictedInputMode() ||
(!onlyShowOnKeyguard && !SmsPopupUtils.inMessagingApp(context)))) {
if (BuildConfig.DEBUG)
Log.v("^^^^^^Showing SMS Popup");
ManageWakeLock.acquirePartial(context);
context.startActivity(message.getPopupIntent());
} else if (notifEnabled) {
if (BuildConfig.DEBUG)
Log.v("^^^^^^Not showing SMS Popup, using notifications");
ManageNotification.show(context, message, message == null ? 0 : message.getUnreadCount());
ReminderService.scheduleReminder(context, message);
}
}
/**
* Handle receiving a MMS message
*/
private void handleMmsReceived(Intent intent) {
if (BuildConfig.DEBUG)
Log.v("MMS received!");
SmsMmsMessage mmsMessage = null;
int count = 0;
// Ok this is super hacky, but fixes the case where this code
// runs before the system MMS transaction service (that stores
// the MMS details in the database). This should really be
// a content listener that waits for a while then gives up...
while (mmsMessage == null && count < MESSAGE_RETRY) {
mmsMessage = SmsPopupUtils.getMmsDetails(context);
if (mmsMessage != null) {
if (BuildConfig.DEBUG)
Log.v("MMS found in content provider");
notifyMessageReceived(mmsMessage);
} else {
if (BuildConfig.DEBUG)
Log.v("MMS not found, sleeping (count is " + count + ")");
count++;
try {
Thread.sleep(MESSAGE_RETRY_PAUSE);
} catch (InterruptedException e) {
// e.printStackTrace();
}
}
}
}
/**
* Handle receiving an arbitrary message (potentially coming from a 3rd party app)
*/
private void handleMessageReceived(Intent intent) {
if (BuildConfig.DEBUG)
Log.v("SMSReceiver: Intercept Message");
Bundle bundle = intent.getExtras();
/*
* FROM: ContactURI -or- display name and display address -or- display address MESSAGE BODY:
* message body TIMESTAMP: optional (will use system timestamp)
*
* QUICK REPLY INTENT: REPLY INTENT: DELETE INTENT:
*/
if (bundle != null) {
// notifySmsReceived(new SmsMmsMessage(context, messages, System.currentTimeMillis()));
}
}
/*
* Handler to deal with showing Toast messages for message sent status
*/
public Handler mToastHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg != null) {
switch (msg.what) {
case TOAST_HANDLER_MESSAGE_SENT:
Toast.makeText(SmsReceiverService.this,
SmsReceiverService.this.getString(R.string.quickreply_sent_toast),
Toast.LENGTH_SHORT).show();
break;
case TOAST_HANDLER_MESSAGE_SEND_LATER:
Toast.makeText(
SmsReceiverService.this,
SmsReceiverService.this
.getString(R.string.quickreply_failed_send_later),
Toast.LENGTH_SHORT).show();
break;
case TOAST_HANDLER_MESSAGE_FAILED:
Toast.makeText(SmsReceiverService.this,
SmsReceiverService.this.getString(R.string.quickreply_failed),
Toast.LENGTH_SHORT).show();
break;
}
}
}
};
/*
* Handle the result of a sms being sent
*/
private void handleSmsSent(Intent intent) {
if (BuildConfig.DEBUG)
Log.v("SMSReceiver: Handle SMS sent");
PackageManager pm = getPackageManager();
Intent sysIntent = null;
Intent tempIntent;
List<ResolveInfo> receiverList;
boolean forwardToSystemApp = true;
// Search for system messaging app that will receive our "message sent complete" type intent
tempIntent = intent.setClassName(
SmsMessageSender.MESSAGING_PACKAGE_NAME,
SmsMessageSender.MESSAGING_RECEIVER_CLASS_NAME);
tempIntent.setAction(SmsReceiverService.MESSAGE_SENT_ACTION);
receiverList = pm.queryBroadcastReceivers(tempIntent, 0);
if (receiverList.size() > 0) {
if (BuildConfig.DEBUG)
Log.v("SMSReceiver: Found system messaging app - " + receiverList.get(0).toString());
sysIntent = tempIntent;
}
/*
* No system messaging app was found to forward this intent to, therefore we will need to do
* the final piece of this ourselves which is basically moving the message to the correct
* folder depending on the result.
*/
if (sysIntent == null) {
forwardToSystemApp = false;
if (BuildConfig.DEBUG)
Log.v("SMSReceiver: Did not find system messaging app, moving messages directly");
Uri uri = intent.getData();
if (mResultCode == Activity.RESULT_OK) {
SmsMessageSender.moveMessageToFolder(this, uri, SmsMessageSender.MESSAGE_TYPE_SENT);
} else if ((mResultCode == SmsManager.RESULT_ERROR_RADIO_OFF) ||
(mResultCode == SmsManager.RESULT_ERROR_NO_SERVICE)) {
SmsMessageSender.moveMessageToFolder(this, uri,
SmsMessageSender.MESSAGE_TYPE_QUEUED);
} else {
SmsMessageSender.moveMessageToFolder(this, uri,
SmsMessageSender.MESSAGE_TYPE_FAILED);
}
}
// Check the result and notify the user using a toast
if (mResultCode == Activity.RESULT_OK) {
if (BuildConfig.DEBUG)
Log.v("SMSReceiver: Message was sent");
mToastHandler.sendEmptyMessage(TOAST_HANDLER_MESSAGE_SENT);
} else if ((mResultCode == SmsManager.RESULT_ERROR_RADIO_OFF) ||
(mResultCode == SmsManager.RESULT_ERROR_NO_SERVICE)) {
if (BuildConfig.DEBUG)
Log.v("SMSReceiver: Error sending message (will send later)");
// The system shows a Toast here so no need to show one
// mToastHandler.sendEmptyMessage(TOAST_HANDLER_MESSAGE_SEND_LATER);
} else {
if (BuildConfig.DEBUG)
Log.v("SMSReceiver: Error sending message");
// ManageNotification.notifySendFailed(this);
mToastHandler.sendEmptyMessage(TOAST_HANDLER_MESSAGE_FAILED);
}
/*
* Start the broadcast via PendingIntent so result code is passed over correctly
*/
if (forwardToSystemApp) {
try {
Log.v("SMSReceiver: Broadcasting send complete to system messaging app");
PendingIntent.getBroadcast(this, 0, sysIntent, 0).send(mResultCode);
} catch (CanceledException e) {
e.printStackTrace();
}
}
}
}