I am trying to have an Android Service listen for an incoming phone call and when one does occur, grab the incoming number and text it a message.
In my service I have made a PhoneStateListener:
TelephonyManager tManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
PhoneStateListener listener = new PhoneStateListener()
{
#Override
public void onCallStateChanged(int state, String incomingNumber)
{
switch(state)
{
case TelephonyManager.CALL_STATE_IDLE:
Log.d(TAG, "Phone: Idle");
break;
case TelephonyManager.CALL_STATE_RINGING:
Log.d(TAG, "Phone: Ringing.");
Log.i(TAG, "Incoming call from: " + incomingNumber);
sendSms();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
Log.d(TAG, "Phone: Off Hook");
break;
}
}
};
tManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
My sendSms() function is as follows:
private void sendSms()
{
SmsManager manager = SmsManager.getDefault();
wakeLock.acquire();
Log.d(TAG, "Wake Lock Acquired!");
if (getMessageContent(getInformStatus()).length() > 160)
{
ArrayList<String> messagelist = manager.divideMessage(getMessageContent(getInformStatus()));
manager.sendMultipartTextMessage(getReturnAddress(), null, messagelist, null, null);
Log.i(TAG, "Multipart Text Message Sent!");
}
else
{
manager.sendTextMessage(getReturnAddress(), null, getMessageContent(getInformStatus()), sentPI, null);
Log.i(TAG, "Text Message Sent!");
}
wakeLock.release();
Log.d(TAG, "Wake Lock Released!");
}
I even have an SMS_SENT Broadcast Receiver to check if the text message sent out correctly that will recall the sendSms() function if it did not sent for whatever reason:
resend = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
switch (getResultCode())
{
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
Log.e(TAG, "Text did NOT send (GENERIC_FAILURE)");
Log.i(TAG, "Attempting to resend");
sendSms();
break;
case SmsManager.RESULT_ERROR_NO_SERVICE:
Log.e(TAG, "Text did NOT send (NO_SERVICE)");
Log.i(TAG, "Attempting to resend");
sendSms();
break;
case SmsManager.RESULT_ERROR_RADIO_OFF:
Log.e(TAG, "Text did NOT send (RADIO_OFF)");
Log.i(TAG, "Attempting to resend");
sendSms();
break;
}
}
};
registerReceiver(resend, new IntentFilter("android.provider.Telephony.SMS_SENT"));
This works on my phone fine, however on other people's phones it likes to sometimes toggle back and forth between RINGING and IDLE in the PhoneStateListener a few times before "sending".. The phone actually says that the application is trying to send multiple text messages and asks the user if that is okay. When they click yes, it just asks again.
To make it better, it never actually sends the text message either..
On another phone, it doesn't do this at all.. The other phone will go through the motions and the logcat is identical to mine.. It actually says it sends, but the recipient never gets the SMS..
It is suppose to be an auto-response service of sorts and I wanted to try and make it work on at least Froyo, Gingerbread, and Ice Cream Sandwich..
By the way, it works 100% on my:
Galaxy Nexus (toro) (Android 4.0.3)
Droid 1 (sholes) (Android 2.3.7)
HTC Thunderbolt (mecha) (Android 2.3.7)
However, if it's my friend's:
Nexus S 4G (crespo4g) (Android 4.0.3)
or my other friend's
Galaxy Nexus (toro) (Android 4.0.3)
It does not work..
What I really don't understand is that it doesn't work with my friend with the exact same phone as me..
I feel like this code implementation is a hit or miss and was wondering if anyone had some insight to help out.
Appreciate it!
I think I figured it out. What I ended up doing is putting a Handler that called a Runnable 30 seconds after the phone call was initially received.. This gave the phone time to finish the phone call (assuming it was in your pocket) and then cleanly send the text message..
I hope this is the only issues I was having, but it seems to work on my friends Galaxy Nexus (toro) now..
Hope this helps anyone else wondering about this..
Cheers!
Related
The Problem:
What I want the app to do is checking a phone's current call state in the background. Now, for making an app that works in background we need a service(source codes in the next section), but the only way I have managed to do this is by using a receiver. The issue with the receiver is that it is not always running if you kill the app in the background. Now this seems a fairly simple question, but I am new to JAVA.
Source Codes
This is my main service's OnStartCommand file:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID).setContentTitle("asdaff").setContentText("asdas").setSmallIcon(R.drawable.ic_android_black_24dp).build();
startForeground(1, notification);
TelephonyManager telephonyManager =
(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
PhoneStateListener callStateListener;
callStateListener = new PhoneStateListener() {
public void onCallStateChanged(int state, String incomingNumber) {
String stateString = "N/A";
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
stateString = "Idle";
Toast toast = Toast.makeText(getApplicationContext(), stateString, Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0,0);
toast.show();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
stateString = "Off Hook";
Toast toast1 = Toast.makeText(getApplicationContext(), stateString, Toast.LENGTH_LONG);
toast1.setGravity(Gravity.CENTER, 0,0);
toast1.show();
break;
case TelephonyManager.CALL_STATE_RINGING:
stateString = "Ringing";
Toast toast2 = Toast.makeText(getApplicationContext(), stateString, Toast.LENGTH_LONG);
toast2.setGravity(Gravity.CENTER, 0,0);
toast2.show();
break;
}
}
};
return super.onStartCommand(intent, flags, startId);
}
Edit
Used startForegroundservice() and it still does not work. I have the phone permission. Please tell if you need more of the source code to tell the problem.
So I figured it out. The issue is in the onStartCommand method itself. When using a phone state listener, you need to have the listener registered to the telephony manager.
telephonyManager.listen(callStateListener, PhoneStateListener.LISTEN_CALL_STATE);
Now, I thought this is one of those "Google-able" Things, that have a proper beginner mistake warning that may help anyone who is going to commit such a stupid mistake(i.e, me).
I am Writing this answer so that anyone who comes across this may no what not to do.
Cheers.
I am trying to implement Android In App Update API using AppUpdateManager. I first tried following the documentation, which if you are here, then you know that doesn't work.
So then I've read about a dozen tutorials and blog articles on how to get this to work. I'm about 90% there. For me after the user accepts the update, the update is downloaded, my listener detects that the download is complete, and I display my own message with a call back (most of the tutorials use a Toast at this point, but that shouldn't matter). If my user taps on "Restart" which is the label I gave the OK side of the message that I'm displaying, then I call appUpdateManager.completeUpdate().
At this point I get a screen from Google Play being displayed with some nice animation, and an Installing progress bar when that finishes, the Google screen disappears, and then my app is restarted.
Problem, is that it didn't get installed, I can tell from my app version that it is still the previous version, and since it is still the previous version, the appUpdateManager is recreated and when it checks UpdateAvailability.UPDATE_AVAILABLE, it finds (obviously) that there is an update available, and goes back through the whole processing, downloading it again and trying to restart the app again.
I have added some log messages and checked to see if I am getting RESULT_IN_APP_UPDATE_FAILED in the onActivityResult, but it is coming back fine.
Any help or suggestions anyone has, would be great.
I create a handle to appUpdateManager at the beginning of MainActivity, like this:
private AppUpdateManager appUpdateManager;
I also create a listener to monitor the install state as I am working towards a FLEXABLE AppUpdateType like:
private InstallStateUpdatedListener UPDATE_LISTENER = installState -> {
if (installState.installStatus() == DOWNLOADED) {
Log.d(TAG, "~UPDATE_LISTENER - installStatus is DOWNLOADED");
Utility.displayAlert(UPDATE_READY);
}
Log.d(TAG, "~Error code is: " + installState.installErrorCode());
Log.d(TAG, "~Package name is: " + installState.packageName());
};
In my onCreate, I create the appUpdateManager and register the download listener as well as add the addOnSuccessListener like so:
Log.d(TAG, "~updateAppIfAvailable");
// Creates instance of the manager.
appUpdateManager = AppUpdateManagerFactory.create(this);
Log.d(TAG, "~registerListener");
appUpdateManager.registerListener(UPDATE_LISTENER);
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
// Checks that the platform will allow the specified type of update.
Log.d(TAG, "~updateAvailability is " + appUpdateInfo.updateAvailability());
if ((appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE)
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
// Request the update.
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
this,
MY_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
My onActivityResult looks like this:
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "~onActivityResult");
if (requestCode == MY_REQUEST_CODE) {
switch (resultCode) {
case Activity.RESULT_OK:
Log.d(TAG, "~User approved update");
break;
case Activity.RESULT_CANCELED:
Log.d(TAG, "~User rejected update");
break;
case RESULT_IN_APP_UPDATE_FAILED:
Log.d(TAG, "~Update failed");
break;
}
}
}
onResume is:
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
if (appUpdateInfo.updateAvailability()
== UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
try {
Log.d(TAG, "~onResume appUpdateManager is calling startUpdateFlowForResult");
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
this,
MY_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
Finally when I display my alert (this could be a Toast if you wanted), I have both a Cancel and a Restart option with a message that says "Update download is complete."
If the user presses the "Restart" option, then it calls this updateAndRestart() method:
private void updateAndRestart() {
Log.d(TAG, "~updateAndRestart");
Log.d(TAG, "~Completing app update.");
if (appUpdateManager != null) {
appUpdateManager.completeUpdate();
Log.d(TAG, "~unregisterListener");
appUpdateManager.unregisterListener(UPDATE_LISTENER);
}
}
Ok, it's been a week, apparently I've stumped the entire SO community. :) Seriously though, I've been reading Android docs and tutorials for the past week and I have no other answers, so I'm going to just say the Google Update API just isn't ready for production.
I found an alternate solution. It is a combination of
Using the AppUpdater library as mentioned in the answer to this question.
If I get a callback that the user tapped Update, then I take them to my Play Store App listing, using this intent. Make sure you replace "com.example.android" with your own app id. This google doc page shows you how to do it.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://play.google.com/store/apps/details?id=com.example.android"));
intent.setPackage("com.android.vending");
startActivity(intent);
I am facing this error while disabling Bluetooth. Googled it but did not find solution. Here is my broadcast receiver which invokes when bluetooth state is changed.
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
Log.e(LOG_TAG, "Broadcast receiver - onReceive");
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
h.removeCallbacks(runnable);
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
switch (state) {
case BluetoothAdapter.STATE_OFF:
Log.i(LOG_TAG, "***** BLE-Bluetooth is disabled");
Toast.makeText(getApplicationContext(), "Bluetooth is disabled", Toast.LENGTH_LONG).show();
Log.i(TAG, "***** mBeaconScanner "+ mBeaconScanner);
if(null != mBeaconScanner)
mBeaconScanner.scanLeDevice(false);
break;
case BluetoothAdapter.STATE_ON:
Log.i(LOG_TAG, "***** BLE-Bluetooth is enabled");
Toast.makeText(getApplicationContext(), "Bluetooth is enabled", Toast.LENGTH_LONG).show();
if(null != mBeaconScanner)
mBeaconScanner.scanLeDevice(true);
break;
}
}
}
};
In the above code I am trying to stop ble scanning while disabling the Bluetooth. In the above code method "mBeaconScanner.scanLeDevice(false);" redirects to :
Log.i(TAG, "***** Stopping BLE Scan for Android version " + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (bluetoothLeScanner != null)
{
bluetoothLeScanner.stopScan(mScanCallback);
}
} else {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
In the above snippet I am getting Exception when calling bluetoothLeScanner.stopScan(mScanCallback) method.
NOTE: this issue is not reproducible in all the devices but nexus and its not very frequently reproducible as well for me.
Please suggest me if there is any solution for this.. Thanks in advance
The only solution I can suggest here is comment this code:-
//mBeaconScanner.scanLeDevice(false);
The way BLE scan works is pretty complicated at the radio level. There are tons of factors (like power, efficiency etc..) that are taken into consideration while arriving at the right scan window sleep time etc..
For you in this particular case when the BT adapter itself is turned OFF, trying to stop scan is an invalid operation as far as the radio is concerned. Am glad that Android FW throws this exception !! Just get rid of this code and may be do some internal app specific state transition (if you have to) or else just log and be done in case BluetoothAdapter.STATE_OFF:
In my application I am copying all the Call Logs into a Database in an Async Task. But If a call comes in between my application can get terminated leading to incomplete data. So first of all I want to stop my application from getting terminated in such scenario, so what should I do? Also I want to keep track of cursor such that when application is opened again it will continue from the exact same place where it stopped and complete the job.
This is the code that helped me save my game state during incoming call.. Hope it helps you too...
TelephonyManager tm;
private PhoneStateListener mPhoneListener = new PhoneStateListener() {
public void onCallStateChanged(int state, String incomingNumber) {
try {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
Toast.makeText(GameActivity.this, "CALL_STATE_RINGING", Toast.LENGTH_SHORT).show();
//Your function to save state right here...
stopTimer();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
Toast.makeText(GameActivity.this, "CALL_STATE_OFFHOOK", Toast.LENGTH_SHORT).show();
break;
case TelephonyManager.CALL_STATE_IDLE:
Toast.makeText(GameActivity.this, "CALL_STATE_IDLE", Toast.LENGTH_SHORT).show();
break;
default:
Toast.makeText(GameActivity.this, "default", Toast.LENGTH_SHORT).show();
Log.i("Default", "Unknown phone state=" + state);
}
} catch (Exception e) {
Log.i("Exception", "PhoneStateListener() e = " + e);
}
}
};
I have a program that sends pre-defined text messages to a group of people at a push of a button. I have it working well but the problem I have is that when it sends the messages, it pops up with 2 toasts per message sent. Code:
package com.mfd.alerter;
//imports
public class homeScreen extends Activity {
//buttons
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//vars
// Grab the time
final Date anotherCurDate = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("km");
final String formattedTime = formatter.format(anotherCurDate);
// Contacts
final String[] numbers = getResources().getStringArray(R.array.numbers);
// Start messages. Only 1 is given to shorten post
callStructureFire.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String msgText = "MFD PAGE OUT:\nStructure Fire\nTimeout:"+formattedTime;
for (int i = 0; i < numbers.length; i++) {
sendSMS(numbers[i], msgText);
}
}
});
//more call types. not important.
}
//---sends a SMS message to another device---
private void sendSMS(String numbers, String message)
{
String SENT = "SMS_SENT";
PendingIntent sentPI = PendingIntent.getBroadcast(this, 0,
new Intent(SENT), 0);
//---when the SMS has been sent---
registerReceiver(new BroadcastReceiver(){
#Override
public void onReceive(Context arg0, Intent arg1) {
switch (getResultCode())
{
case Activity.RESULT_OK:
Toast.makeText(getBaseContext(), "SMS sent",
Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
Toast.makeText(getBaseContext(), "Generic failure",
Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_NO_SERVICE:
Toast.makeText(getBaseContext(), "No service",
Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_NULL_PDU:
Toast.makeText(getBaseContext(), "Null PDU",
Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_RADIO_OFF:
Toast.makeText(getBaseContext(), "Radio off",
Toast.LENGTH_SHORT).show();
break;
}
}
}, new IntentFilter(SENT));
SmsManager sms = SmsManager.getDefault();
sms.sendTextMessage(numbers, null, message, sentPI, null);
}
//action bar stuff. not important.
}
More in detail: Lets say I send the text to 3 people, 6 toast messages will pop up saying "SMS Sent". How do I make it so only 3 will show up?
Also, Is there a way to maybe add a counter of the messages sent? Ex: "Message 1/10 sent", "Message 2/10 sent", etc?
I didn't really look at your code or asked myself why this happens but here's a trick to stop toasts show up twice:
Create a Toast instance using makeToast(), before showing it you call cancel(), set your text and then call show(). This will dismiss the previous toast. You won't even notice that a toast is displayed twice.
That's a stupid workaround, but it works for me ;-)
Aren't you supposed to register the receiver only once and not every time you call sendSMS.
You get 6 Toasts with three sms messages because you have 3 BroadCastReceivers. So in the first run you get 1 Toast. In the second run you get 2 Toasts (the receiver that was registered in the first run is called, and the one in the second). In the third run all three receivers are called, so you get three more toasts. All in sum - 6 Toasts...
so I guess, you have to register only one receiver before the for loop where you call sendSMS, or if you want the registration in sendSMS, then you have to unregister at the end of the method.
I hope this helps,
cheers!