I am trying to perform some implementation against the phone call. by following this link. Everything works fine except the onReceive method from PhonecallReceiver doesn't get called on the first launch of the application, afterward, it works fine. Thanks for your time and help.
PhonecallReceiver
public class PhonecallReceiver extends BroadcastReceiver {
//The receiver will be recreated whenever android feels like it. We need a static variable to remember data between instantiations
private static int lastState = TelephonyManager.CALL_STATE_IDLE;
private static Date callStartTime;
private static boolean isIncoming;
private static String savedNumber;
#Override
public void onReceive(Context context, Intent intent) {
//We listen to two intents. The new outgoing call only tells us of an outgoing call. We use it to get the number.
String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
int state = 0;
if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)){
state = TelephonyManager.CALL_STATE_IDLE;
}
else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
state = TelephonyManager.CALL_STATE_OFFHOOK;
}
else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)){
state = TelephonyManager.CALL_STATE_RINGING;
}
onCallStateChanged(context, state, number);
}
//Derived classes should override these to respond to specific events of interest
protected void onIncomingCallReceived(Context ctx, String number, Date start){}
public void onCallStateChanged(Context context, int state, String number) {
if(lastState == state){
//No change, debounce extras
return;
}
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
isIncoming = true;
callStartTime = new Date();
savedNumber = number;
onIncomingCallReceived(context, number, callStartTime);
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
//Transition of ringing->offhook are pickups of incoming calls. Nothing done on them
break;
case TelephonyManager.CALL_STATE_IDLE:
//Went to idle- this is the end of a call. What type depends on previous state(s)
break;
}
lastState = state;
}
}
CallReceiver
public class CallReceiver extends PhonecallReceiver {
private static CallListener mCallListener;
public static void bindListener(CallListener callListener) {
mCallListener = callListener;
}
#Override
protected void onIncomingCallReceived(final Context ctx, final String number, Date start) {
try {
if (mCallListener != null) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
stopRinging(ctx);
mCallListener.displayNumber(number);
}
}, 2000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Manifest
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name="com.example.expphonecall.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="com.example.expphonecall.CallReceiver" android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
<!--<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>-->
</receiver>
</application>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
My English expression may not be very good, I hope you can understand
You can make it as `
PhoneStateReceiver phoneStateReceiver = new PhoneStateReceiver();
IntentFilter intentFilter = new IntentFilter();
for (String action : actions) {
intentFilter.addAction(action);
}
c.registerReceiver(phoneStateReceiver,intentFilter);
`
but not register as static,It well be work?
Or
If your application implement InCallService.
Use call.registerCallback(Call.Callback),the Callback worked No abnormality
Required permissions(READ_PHONE_STATE, CALL_PHONE) were being asked separately in the activity(not a better way)
if (Build.VERSION.SDK_INT >= 23) {
if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission has not been granted, therefore prompt the user to grant permission
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_PHONE_STATE},
//MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
11);
}
if (getApplicationContext().checkSelfPermission(Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission has not been granted, therefore prompt the user to grant permission
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CALL_PHONE},
//MY_PERMISSIONS_REQUEST_PROCESS_OUTGOING_CALLS);
12);
}
}
And as the request for these permissions is asked collectively then the receiver started to invoke on the first launch of the application
int PERMISSION_ALL = 1;
String[] PERMISSIONS = {
Manifest.permission.CALL_PHONE,
Manifest.permission.READ_PHONE_STATE,
};
private void checkPermissions(){
if (!hasPermissions(this, PERMISSIONS)) {
ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_ALL);
}
}
public static boolean hasPermissions(Context context, String... permissions) {
if (context != null && permissions != null) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
This question already has answers here:
Service crashes with nullpointerexception at onCreate
(2 answers)
Cant run background service
(3 answers)
Closed 4 years ago.
I'm trying to build a music app in Android, although I feel ridiculously newbie sometimes. I have researched a lot, tried my best at getting the hang of how MediaPlayer, MediaBrowserCompat / MediaBrowserServiceCompat, MediaController and Services work, tens of unfortunately old tutorials about how to build one.
My biggest issue was that most of them tended to use the IBinder functionality and intents to bind and start the musicPlaybackService, while google's documentation used these MediaBrowser and MediaBrowserService APIs, both approaches new and honestly quite hard and overwhelming for me.
I've learned a lot so far, but it was hard. The two slightly better tutorials I've found are
https://www.sitepoint.com/a-step-by-step-guide-to-building-an-android-audio-player-app/, and https://code.tutsplus.com/tutorials/background-audio-in-android-with-mediasessioncompat--cms-27030 and they use the first and the second approach. My version of the app is what I managed to make by putting together all the pieces I've learned.
A lot of bugs and issues I managed to figure out on my own, but I've encountered a NullPointer exception that I simply don't know how to solve. The debugger is weird as well on this one, it feels almost like every time the error comes from another place of the program; sometimes it stops at the breakpoints I put, and then running the debugger again with the exact same code and breakpoints, it skips them and goes straight into the Runtime error.
This is my Manifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ecebuc.gesmediaplayer">
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MediaPlaybackService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
</application>
Then this is my MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private final int REQUEST_CODE_GESPLAYER_EXTERNAL_STORAGE = 101;
private static final int STATE_PAUSED = 0;
private static final int STATE_PLAYING = 1;
private static int currentState;
private MediaBrowserCompat gesMediaBrowser;
private MediaControllerCompat gesMediaController;
public MediaControllerCompat.TransportControls gesPlaybackController;
private Button playPauseToggleButton;
private MediaBrowserCompat.ConnectionCallback mediaBrowserCallbacks = new MediaBrowserCompat.ConnectionCallback() {
#Override
public void onConnected() {
super.onConnected();
try {
//create the media controller and register the callbacks to stay in sync
gesMediaController = new MediaControllerCompat(MainActivity.this, gesMediaBrowser.getSessionToken());
gesMediaController.registerCallback(mediaControllerCallbacks);
//save the controller and define the easy access transport controls in the object
MediaControllerCompat.setMediaController(MainActivity.this, gesMediaController);
gesPlaybackController = gesMediaController.getTransportControls();
//Display initial state
MediaMetadataCompat metadata = gesMediaController.getMetadata();
PlaybackStateCompat pbState = gesMediaController.getPlaybackState();
} catch( RemoteException e ) {
}
}
#Override
public void onConnectionSuspended() {
// The Service has crashed. Disable transport controls until it automatically reconnects
}
#Override
public void onConnectionFailed() {
// The Service has refused our connection
Log.d("onConnectionFail: ", "the service hasn't been able to connect");
}
};
private MediaControllerCompat.Callback mediaControllerCallbacks = new MediaControllerCompat.Callback() {
#Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
super.onMetadataChanged(metadata);
}
#Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
super.onPlaybackStateChanged(state);
if( state == null ) {
Log.d("onPlaybackChange: ", "the state is null");
Toast.makeText(MainActivity.this,
"onPlaybackStateChange: the state is null",
Toast.LENGTH_SHORT)
.show();
return;
}
switch( state.getState() ) {
case PlaybackStateCompat.STATE_PLAYING: {
currentState = STATE_PLAYING;
break;
}
case PlaybackStateCompat.STATE_PAUSED: {
currentState = STATE_PAUSED;
break;
}
}
}
#Override
public void onSessionDestroyed(){
// Override to handle the session being destroyed
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//grab the buttons for media playback control
playPauseToggleButton = (Button)findViewById(R.id.playPause_btn);
//request permissions for external storage
if (ContextCompat.checkSelfPermission(this,
android.Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission have not been granted
ActivityCompat.requestPermissions(this,
new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_CODE_GESPLAYER_EXTERNAL_STORAGE);
}
else{
//permissions have already been granted
}
//initiate connection to the MediaPlaybackService through MediaBrowser
gesMediaBrowser = new MediaBrowserCompat(this,
new ComponentName(this, MediaPlaybackService.class),
mediaBrowserCallbacks, getIntent().getExtras());
gesMediaBrowser.connect();
//Attach listeners to them
playPauseToggleButton.setOnClickListener(this);
// space here for other buttons
// sapce here for other buttons
}
#Override
protected void onStart() {
super.onStart();
//gesMediaBrowser.connect();
}
/*protected void onStop() {
super.onStop();
// (see "stay in sync with the MediaSession")
if( gesMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) {
gesPlaybackController.pause();
}
gesMediaBrowser.disconnect();
}*/
#Override
protected void onDestroy() {
super.onDestroy();
/*if (gesMediaController != null) {
gesMediaController.unregisterCallback(mediaControllerCallbacks);
gesMediaController = null;
}
if(gesMediaBrowser != null && gesMediaBrowser.isConnected()) {
gesMediaBrowser.disconnect();
gesMediaBrowser = null;
}*/
if( gesMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) {
gesPlaybackController.pause();
}
gesMediaBrowser.disconnect();
}
#Override
public void onClick(View v) {
switch (v.getId()){
case R.id.playPause_btn:
//has to be dealt with accordingly, based on the current state of mediaplayer
int currentState = gesMediaController.getPlaybackState().getState();
if(currentState == PlaybackStateCompat.STATE_PLAYING) {
gesPlaybackController.pause();
} else {
//gesPlaybackController.play();
gesPlaybackController.playFromMediaId(String.valueOf(R.raw.warner_tautz_off_broadway), null);
}
break;
}
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_GESPLAYER_EXTERNAL_STORAGE: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "onRequestPermissionResult: granted", Toast.LENGTH_SHORT).show();
} else {
//close the app if permissions aren't granted
//Toast.makeText(this, "onRequestPermissionResult: denied", Toast.LENGTH_SHORT).show();
finish();
}
return;
}
// other 'case' lines to check for other
// permissions this app might request.
}
}
And then the playbackService.java
public class MediaPlaybackService extends MediaBrowserServiceCompat implements
MediaPlayer.OnCompletionListener,
AudioManager.OnAudioFocusChangeListener {
public static final String COMMAND_EXAMPLE = "command_example";
public static boolean isServiceStarted = false;
/*public int audioIndex;
public ArrayList<Audio> audioList;
public Audio activeAudio;*/
private MediaPlayer gesMediaPlayer;
private MediaSessionCompat gesMediaSession;
private int pausedPosition;
//-------------------------------------Lifecycle methods--------------------------------------//
#Override
public void onCreate() {
super.onCreate();
Log.d("onCreate: ", "Service created");
initMediaPlayer();
initMediaSession();
callStateListener();
registerNoisyReceiver();
// Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY |
PlaybackStateCompat.ACTION_PLAY_PAUSE);
gesMediaSession.setPlaybackState(playbackStateBuilder.build());
}
#Override
public void onDestroy() {
super.onDestroy();
if (gesMediaPlayer != null) {
gesMediaPlayer.stop();
gesMediaPlayer.release();
}
//Disable the PhoneStateListener
if (phoneStateListener != null) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
}
removeAudioFocus();
unregisterReceiver(becomingNoisyReceiver);
//clear cached playlist
//new StorageUtils(getApplicationContext()).clearCachedAudioPlaylist();
NotificationManagerCompat.from(this).cancel(1);
stopSelf();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("onStartCommand: ", "Service has been started");
MediaButtonReceiver.handleIntent(gesMediaSession, intent);
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
if( gesMediaPlayer != null ) {
gesMediaPlayer.release();
}
}
//----------------------------------------Initialisers----------------------------------------//
private void initMediaPlayer() {
gesMediaPlayer = new MediaPlayer();
gesMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
gesMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
gesMediaPlayer.setVolume(1.0f, 1.0f);
/*try {
//sets songPath as data source for media player
//gesMediaPlayer.setDataSource(songPath);
//sets current song as data source for media player
gesMediaPlayer.setDataSource(activeAudio.getData());
} catch (IOException e) {
e.printStackTrace();
stopSelf();
}
gesMediaPlayer.prepareAsync();*/
}
private void initMediaSession() {
ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
gesMediaSession = new MediaSessionCompat(getApplicationContext(), "GESMediaService",
mediaButtonReceiver, null);
gesMediaSession.setCallback(mediaSessionCallbacks);
gesMediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS );
//this is for pre-Lollipop media button handling on those devices
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setClass(this, MediaButtonReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
gesMediaSession.setMediaButtonReceiver(pendingIntent);
// Set the session's token so that client activities can communicate with it.
setSessionToken(gesMediaSession.getSessionToken());
}
private void registerNoisyReceiver() {
//Handles headphones coming unplugged. cannot be done through a manifest receiver
IntentFilter noisyFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(becomingNoisyReceiver, noisyFilter);
}
private void initMediaSessionMetadata() {
MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder();
//Notification icon in card
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
//lock screen icon for pre lollipop
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Display Title");
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Display Subtitle");
metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, 1);
metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, 1);
gesMediaSession.setMetadata(metadataBuilder.build());
}
//-----------------------------------Media Playback functions---------------------------------//
//TODO: read about the AssetFileDescriptor, and the ResultReceiver
private MediaSessionCompat.Callback mediaSessionCallbacks = new MediaSessionCompat.Callback() {
#Override
public void onPlay() {
super.onPlay();
if(!requestAudioFocus()) {
//failed to gain focus
return;
}
//check if service is started, not only bound
if(!isServiceStarted){
startService(new Intent(getApplicationContext(), MediaPlaybackService.class));
}
gesMediaSession.setActive(true);
setMediaPlaybackState(PlaybackStateCompat.STATE_PLAYING);
showPlayingNotification();
gesMediaPlayer.start();
}
#Override
public void onPause() {
super.onPause();
if( gesMediaPlayer.isPlaying() ) {
gesMediaPlayer.pause();
setMediaPlaybackState(PlaybackStateCompat.STATE_PAUSED);
showPausedNotification();
}
}
#Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
super.onPlayFromMediaId(mediaId, extras);
try {
AssetFileDescriptor afd = getResources().openRawResourceFd(Integer.valueOf(mediaId));
if( afd == null ) {
Log.d("afd: ", "afd in onPlayFromMediaId is null");
return;
}
try {
gesMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
} catch( IllegalStateException e ) {
gesMediaPlayer.release();
initMediaPlayer();
gesMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
}
afd.close();
initMediaSessionMetadata();
} catch (IOException e) {
e.printStackTrace();
return;
}
try {
gesMediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
Log.d("onPlayFromId: ", "mediaPlayer failed to prepare");
}
//Work with extras here if you want
}
#Override
public void onCommand(String command, Bundle extras, ResultReceiver cb) {
super.onCommand(command, extras, cb);
if( COMMAND_EXAMPLE.equalsIgnoreCase(command) ) {
//Custom command here
}
}
#Override
public void onSeekTo(long pos) {
super.onSeekTo(pos);
}
};
private void setMediaPlaybackState(int state) {
PlaybackStateCompat.Builder playbackstateBuilder = new PlaybackStateCompat.Builder();
if( state == PlaybackStateCompat.STATE_PLAYING ) {
playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_PAUSE);
} else {
playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_PLAY);
}
playbackstateBuilder.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0);
gesMediaSession.setPlaybackState(playbackstateBuilder.build());
}
//-------------------------------Audio Focus and Calls Handling-------------------------------//
//Handle incoming phone calls
private boolean ongoingCall = false;
private PhoneStateListener phoneStateListener;
private TelephonyManager telephonyManager;
private AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
#Override
public void onAudioFocusChange(int focusChange) {
switch( focusChange ) {
case AudioManager.AUDIOFOCUS_LOSS: {
// Lost focus for an unbounded amount of time:
// stop playback and release media player
if( gesMediaPlayer.isPlaying() ) {
gesMediaPlayer.stop();
}
/*gesMediaPlayer.release();
gesMediaPlayer = null;*/
break;
}
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: {
// Lost focus for a short time; Pause only and do not
// release the media player as playback is likely to resume
if (gesMediaPlayer.isPlaying()) {
gesMediaPlayer.pause();
}
break;
}
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
// Lost focus for a short time (ex. notification sound)
// but it's ok to keep playing at a temporarily attenuated level
if( gesMediaPlayer != null ) {
gesMediaPlayer.setVolume(0.2f, 0.2f);
}
break;
}
case AudioManager.AUDIOFOCUS_GAIN: {
//Invoked when the audio focus of the system is updated.
if( gesMediaPlayer != null ) {
if( !gesMediaPlayer.isPlaying() ) {
gesMediaPlayer.start();
}
gesMediaPlayer.setVolume(1.0f, 1.0f);
}
break;
}
}
}
private boolean requestAudioFocus() {
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
return result == AudioManager.AUDIOFOCUS_GAIN;
}
private boolean removeAudioFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
audioManager.abandonAudioFocus(this);
}
private void callStateListener() {
// Get the telephony manager
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
//Starting listening for PhoneState changes
phoneStateListener = new PhoneStateListener() {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
//if at least one call exists or the phone is ringing
//pause the MediaPlayer
case TelephonyManager.CALL_STATE_OFFHOOK:
case TelephonyManager.CALL_STATE_RINGING:
if (gesMediaPlayer != null && gesMediaPlayer.isPlaying()) {
gesMediaPlayer.pause();
pausedPosition = gesMediaPlayer.getCurrentPosition();
ongoingCall = true;
}
break;
case TelephonyManager.CALL_STATE_IDLE:
// Phone idle. Start/resume playing.
if (gesMediaPlayer != null) {
if (ongoingCall) {
ongoingCall = false;
gesMediaPlayer.seekTo(pausedPosition);
gesMediaPlayer.start();
}
}
break;
}
}
};
// Register the listener with the telephony manager
// Listen for changes to the device call state.
telephonyManager.listen(phoneStateListener,
PhoneStateListener.LISTEN_CALL_STATE);
}
private BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if( gesMediaPlayer != null && gesMediaPlayer.isPlaying() ) {
gesMediaPlayer.pause();
}
}
};
//------------------------------------Less important methods----------------------------------//
#Nullable
#Override
public BrowserRoot onGetRoot(#NonNull String clientPackageName, int clientUid, #Nullable Bundle rootHints) {
if(TextUtils.equals(clientPackageName, getPackageName())) {
return new BrowserRoot(getString(R.string.app_name), null);
}
return null;
}
//Not important for general audio service, required for class
#Override
public void onLoadChildren(#NonNull String parentId, #NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
result.sendResult(null);
}
}
I apologize for the whole code but at this point I do not know what am I doing wrong anymore or where to look. Especially since the app did work in a previous version. Any suggestions are well appreciated if you guys see anything specific in my code. I try to catch exceptions where I can, but at the same time I am never sure where should I be putting the try-catch constructs. I'm trying to learn
Thank you to anyone!
I'm trying to make an app like, when a call comes to the phone I want to detect the number. Below is what I tried, but it's not detecting incoming calls.
I want to run my MainActivity in background, how can I do that?
I had given the permission in manifest file.
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
Is there anything else should I provide in the manifest?
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_layout);
}
public class myPhoneStateChangeListener extends PhoneStateListener {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
if (state == TelephonyManager.CALL_STATE_RINGING) {
String phoneNumber = incomingNumber;
}
}
}
}
Here's what I use to do this:
Manifest:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<!--This part is inside the application-->
<receiver android:name=".CallReceiver" >
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
My base reusable call detector
package com.gabesechan.android.reusable.receivers;
import java.util.Date;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
public abstract class PhonecallReceiver extends BroadcastReceiver {
//The receiver will be recreated whenever android feels like it. We need a static variable to remember data between instantiations
private static int lastState = TelephonyManager.CALL_STATE_IDLE;
private static Date callStartTime;
private static boolean isIncoming;
private static String savedNumber; //because the passed incoming is only valid in ringing
#Override
public void onReceive(Context context, Intent intent) {
//We listen to two intents. The new outgoing call only tells us of an outgoing call. We use it to get the number.
if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
savedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
}
else{
String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
int state = 0;
if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)){
state = TelephonyManager.CALL_STATE_IDLE;
}
else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
state = TelephonyManager.CALL_STATE_OFFHOOK;
}
else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)){
state = TelephonyManager.CALL_STATE_RINGING;
}
onCallStateChanged(context, state, number);
}
}
//Derived classes should override these to respond to specific events of interest
protected abstract void onIncomingCallReceived(Context ctx, String number, Date start);
protected abstract void onIncomingCallAnswered(Context ctx, String number, Date start);
protected abstract void onIncomingCallEnded(Context ctx, String number, Date start, Date end);
protected abstract void onOutgoingCallStarted(Context ctx, String number, Date start);
protected abstract void onOutgoingCallEnded(Context ctx, String number, Date start, Date end);
protected abstract void onMissedCall(Context ctx, String number, Date start);
//Deals with actual events
//Incoming call- goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
//Outgoing call- goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
public void onCallStateChanged(Context context, int state, String number) {
if(lastState == state){
//No change, debounce extras
return;
}
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
isIncoming = true;
callStartTime = new Date();
savedNumber = number;
onIncomingCallReceived(context, number, callStartTime);
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
//Transition of ringing->offhook are pickups of incoming calls. Nothing done on them
if(lastState != TelephonyManager.CALL_STATE_RINGING){
isIncoming = false;
callStartTime = new Date();
onOutgoingCallStarted(context, savedNumber, callStartTime);
}
else
{
isIncoming = true;
callStartTime = new Date();
onIncomingCallAnswered(context, savedNumber, callStartTime);
}
break;
case TelephonyManager.CALL_STATE_IDLE:
//Went to idle- this is the end of a call. What type depends on previous state(s)
if(lastState == TelephonyManager.CALL_STATE_RINGING){
//Ring but no pickup- a miss
onMissedCall(context, savedNumber, callStartTime);
}
else if(isIncoming){
onIncomingCallEnded(context, savedNumber, callStartTime, new Date());
}
else{
onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());
}
break;
}
lastState = state;
}
}
Then to use it, simply derive a class from it and implement a few easy functions, whichever call types you care about:
public class CallReceiver extends PhonecallReceiver {
#Override
protected void onIncomingCallReceived(Context ctx, String number, Date start)
{
//
}
#Override
protected void onIncomingCallAnswered(Context ctx, String number, Date start)
{
//
}
#Override
protected void onIncomingCallEnded(Context ctx, String number, Date start, Date end)
{
//
}
#Override
protected void onOutgoingCallStarted(Context ctx, String number, Date start)
{
//
}
#Override
protected void onOutgoingCallEnded(Context ctx, String number, Date start, Date end)
{
//
}
#Override
protected void onMissedCall(Context ctx, String number, Date start)
{
//
}
}
In addition you can see a writeup I did on why the code is like it is on my blog. Gist link: https://gist.github.com/ftvs/e61ccb039f511eb288ee
EDIT: Updated to simpler code, as I've reworked the class for my own use
private MyPhoneStateListener phoneStateListener = new MyPhoneStateListener();
to register
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
and to unregister
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
With Android P - Api Level 28:
You need to get READ_CALL_LOG permission
Restricted access to call logs
Android P moves the CALL_LOG, READ_CALL_LOG, WRITE_CALL_LOG, and PROCESS_OUTGOING_CALLS permissions from the PHONE permission group to the new CALL_LOG permission group. This group gives users better control and visibility to apps that need access to sensitive information about phone calls, such as reading phone call records and identifying phone numbers.
To read numbers from the PHONE_STATE intent action, you need both the READ_CALL_LOG permission and the READ_PHONE_STATE permission.
To read numbers from onCallStateChanged(), you now need the READ_CALL_LOG permission only. You no longer need the READ_PHONE_STATE permission.
UPDATE: The really awesome code posted by Gabe Sechan no longer works unless you explicitly request the user to grant the necessary permissions. Here is some code that you can place in your main activity to request these permissions:
if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission has not been granted, therefore prompt the user to grant permission
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_PHONE_STATE},
MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
}
if (getApplicationContext().checkSelfPermission(Manifest.permission.PROCESS_OUTGOING_CALLS)
!= PackageManager.PERMISSION_GRANTED) {
// Permission has not been granted, therefore prompt the user to grant permission
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.PROCESS_OUTGOING_CALLS},
MY_PERMISSIONS_REQUEST_PROCESS_OUTGOING_CALLS);
}
ALSO: As someone mentioned in a comment below Gabe's post, you have to add a little snippet of code, android:enabled="true, to the receiver in order to detect incoming calls when the app is not currently running in the foreground:
<!--This part is inside the application-->
<receiver android:name=".CallReceiver" android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
Just to update Gabe Sechan's answer. If your manifest asks for permissions to READ_CALL_LOG and READ_PHONE_STATE, onReceive will called TWICE. One of which has EXTRA_INCOMING_NUMBER in it and the other doesn't. You have to test which has it and it can occur in any order.
https://developer.android.com/reference/android/telephony/TelephonyManager.html#ACTION_PHONE_STATE_CHANGED
this may helps you and also add require permision
public class PhoneListener extends PhoneStateListener
{
private Context context;
public static String getincomno;
public PhoneListener(Context c) {
Log.i("CallRecorder", "PhoneListener constructor");
context = c;
}
public void onCallStateChanged (int state, String incomingNumber)
{
if(!TextUtils.isEmpty(incomingNumber)){
// here for Outgoing number make null to get incoming number
CallBroadcastReceiver.numberToCall = null;
getincomno = incomingNumber;
}
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
break;
case TelephonyManager.CALL_STATE_RINGING:
Log.d("CallRecorder", "CALL_STATE_RINGING");
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
break;
}
}
}
Here is a simple method which can avoid the use of PhonestateListener and other complications.
So here we are receiving the 3 events from android such as RINGING,OFFHOOK and IDLE. And in order to get the all possible state of call,we need to define our own states like RINGING, OFFHOOK, IDLE, FIRST_CALL_RINGING, SECOND_CALL_RINGING.
It can handle every states in a phone call.
Please think in a way that we are receiving events from android and we will define our on call states. See the code.
public class CallListening extends BroadcastReceiver {
private static final String TAG ="broadcast_intent";
public static String incoming_number;
private String current_state,previus_state,event;
public static Boolean dialog= false;
private Context context;
private SharedPreferences sp,sp1;
private SharedPreferences.Editor spEditor,spEditor1;
public void onReceive(Context context, Intent intent) {
//Log.d("intent_log", "Intent" + intent);
dialog=true;
this.context = context;
event = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
incoming_number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
Log.d(TAG, "The received event : "+event+", incoming_number : " + incoming_number);
previus_state = getCallState(context);
current_state = "IDLE";
if(incoming_number!=null){
updateIncomingNumber(incoming_number,context);
}else {
incoming_number=getIncomingNumber(context);
}
switch (event) {
case "RINGING":
Log.d(TAG, "State : Ringing, incoming_number : " + incoming_number);
if((previus_state.equals("IDLE")) || (previus_state.equals("FIRST_CALL_RINGING"))){
current_state ="FIRST_CALL_RINGING";
}
if((previus_state.equals("OFFHOOK"))||(previus_state.equals("SECOND_CALL_RINGING"))){
current_state = "SECOND_CALL_RINGING";
}
break;
case "OFFHOOK":
Log.d(TAG, "State : offhook, incoming_number : " + incoming_number);
if((previus_state.equals("IDLE")) ||(previus_state.equals("FIRST_CALL_RINGING")) || previus_state.equals("OFFHOOK")){
current_state = "OFFHOOK";
}
if(previus_state.equals("SECOND_CALL_RINGING")){
current_state ="OFFHOOK";
startDialog(context);
}
break;
case "IDLE":
Log.d(TAG, "State : idle and incoming_number : " + incoming_number);
if((previus_state.equals("OFFHOOK")) || (previus_state.equals("SECOND_CALL_RINGING")) || (previus_state.equals("IDLE"))){
current_state="IDLE";
}
if(previus_state.equals("FIRST_CALL_RINGING")){
current_state = "IDLE";
startDialog(context);
}
updateIncomingNumber("no_number",context);
Log.d(TAG,"stored incoming number flushed");
break;
}
if(!current_state.equals(previus_state)){
Log.d(TAG, "Updating state from "+previus_state +" to "+current_state);
updateCallState(current_state,context);
}
}
public void startDialog(Context context) {
Log.d(TAG,"Starting Dialog box");
Intent intent1 = new Intent(context, NotifyHangup.class);
intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent1);
}
public void updateCallState(String state,Context context){
sp = PreferenceManager.getDefaultSharedPreferences(context);
spEditor = sp.edit();
spEditor.putString("call_state", state);
spEditor.commit();
Log.d(TAG, "state updated");
}
public void updateIncomingNumber(String inc_num,Context context){
sp = PreferenceManager.getDefaultSharedPreferences(context);
spEditor = sp.edit();
spEditor.putString("inc_num", inc_num);
spEditor.commit();
Log.d(TAG, "incoming number updated");
}
public String getCallState(Context context){
sp1 = PreferenceManager.getDefaultSharedPreferences(context);
String st =sp1.getString("call_state", "IDLE");
Log.d(TAG,"get previous state as :"+st);
return st;
}
public String getIncomingNumber(Context context){
sp1 = PreferenceManager.getDefaultSharedPreferences(context);
String st =sp1.getString("inc_num", "no_num");
Log.d(TAG,"get incoming number as :"+st);
return st;
}
}
I fixed Gabe Sechan answer, I used the following code and it worked properly.
I noticed when a receiver intent has the "incoming_number" key, I can get the phone number. So I filtered incoming intent and used EXTRA_INCOMING_NUMBER and EXTRA_PHONE_NUMBER to get the phone number.
public class ChangeCallStateListener extends BroadcastReceiver {
private static String lastState = TelephonyManager.EXTRA_STATE_IDLE;
private static Date callStartTime;
private static boolean isIncoming;
private static String savedNumber; //because the passed incoming is only valid in ringing
#Override
public void onReceive(Context context, Intent intent) {
Log.d("CallObserver", "CallReceiver is starting ....");
List<String> keyList = new ArrayList<>();
Bundle bundle = intent.getExtras();
if (bundle != null) {
keyList = new ArrayList<>(bundle.keySet());
Log.e("CallObserver", "keys : " + keyList);
}
if (keyList.contains("incoming_number")) {
String phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
String phoneIncomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
String phoneOutgoingNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
String phoneNumber = phoneOutgoingNumber != null ? phoneOutgoingNumber : (phoneIncomingNumber != null ? phoneIncomingNumber : "");
if (phoneState != null && phoneNumber != null) {
if (lastState.equals(phoneState)) {
//No change, debounce extras
return;
}
Log.e("CallObserver", "phoneState = " + phoneState);
if (TelephonyManager.EXTRA_STATE_RINGING.equals(phoneState)) {
isIncoming = true;
callStartTime = new Date();
//
lastState = TelephonyManager.EXTRA_STATE_RINGING;
if (phoneNumber != null) {
savedNumber = phoneNumber;
}
onIncomingCallStarted(context, savedNumber, callStartTime);
} else if (TelephonyManager.EXTRA_STATE_IDLE.equals(phoneState)) {
if (lastState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
//
lastState = TelephonyManager.EXTRA_STATE_IDLE;
onMissedCall(context, savedNumber, callStartTime);
} else {
if (isIncoming) {
//
lastState = TelephonyManager.EXTRA_STATE_IDLE;
onIncomingCallEnded(context, savedNumber, callStartTime, new Date());
} else {
//
lastState = TelephonyManager.EXTRA_STATE_IDLE;
Log.d("CallObserver", "onOutgoingCallEnded called !! : ");
onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());
}
}
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(phoneState)) {
if (lastState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
isIncoming = true;
} else {
isIncoming = false;
}
callStartTime = new Date();
savedNumber = phoneNumber;
//
lastState = TelephonyManager.EXTRA_STATE_OFFHOOK;
onOutgoingCallStarted(context, savedNumber, callStartTime);
}
}
}
}
protected void onIncomingCallStarted(Context ctx, String number, Date start) {
Log.d("CallObserver", "onIncomingCallStarted : " + " number is : " + number);
}
protected void onOutgoingCallStarted(Context ctx, String number, Date start) {
Log.d("CallObserver", "onOutgoingCallStarted : " + " number is : " + number);
}
protected void onIncomingCallEnded(Context context, String number, Date start, Date end) {
}
protected void onOutgoingCallEnded(Context context , String number, Date start, Date end) {
}
protected void onMissedCall(Context context, String number, Date start) {
}
}
Don't forget to get run time permission.
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
#Gabe Sechan, thanks for your code. It works fine except the onOutgoingCallEnded(). It is never executed. Testing phones are Samsung S5 & Trendy. There are 2 bugs I think.
1: a pair of brackets is missing.
case TelephonyManager.CALL_STATE_IDLE:
// Went to idle- this is the end of a call. What type depends on previous state(s)
if (lastState == TelephonyManager.CALL_STATE_RINGING) {
// Ring but no pickup- a miss
onMissedCall(context, savedNumber, callStartTime);
} else {
// this one is missing
if(isIncoming){
onIncomingCallEnded(context, savedNumber, callStartTime, new Date());
} else {
onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());
}
}
// this one is missing
break;
2: lastState is not updated by the state if it is at the end of the function. It should be replaced to the first line of this function by
public void onCallStateChanged(Context context, int state, String number) {
int lastStateTemp = lastState;
lastState = state;
// todo replace all the "lastState" by lastStateTemp from here.
if (lastStateTemp == state) {
//No change, debounce extras
return;
}
//....
}
Additional I've put lastState and savedNumber into shared preference as you suggested.
Just tested it with above changes. Bug fixed at least on my phones.
Please use the below code. It will help you to get the incoming number with other call details.
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<TextView
android:id="#+id/call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="#string/hello_world" />
</RelativeLayout>
MainActivity.java
public class MainActivity extends Activity {
private static final int MISSED_CALL_TYPE = 0;
private TextView txtcall;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtcall = (TextView) findViewById(R.id.call);
StringBuffer sb = new StringBuffer();
Cursor managedCursor = managedQuery(CallLog.Calls.CONTENT_URI, null,
null, null, null);
int number = managedCursor.getColumnIndex(CallLog.Calls.NUMBER);
int type = managedCursor.getColumnIndex(CallLog.Calls.TYPE);
int date = managedCursor.getColumnIndex(CallLog.Calls.DATE);
int duration = managedCursor.getColumnIndex(CallLog.Calls.DURATION);
sb.append("Call Details :");
while (managedCursor.moveToNext()) {
String phNumber = managedCursor.getString(number);
String callType = managedCursor.getString(type);
String callDate = managedCursor.getString(date);
Date callDayTime = new Date(Long.valueOf(callDate));
String callDuration = managedCursor.getString(duration);
String dir = null;
int dircode = Integer.parseInt(callType);
switch (dircode) {
case CallLog.Calls.OUTGOING_TYPE:
dir = "OUTGOING";
break;
case CallLog.Calls.INCOMING_TYPE:
dir = "INCOMING";
break;
case CallLog.Calls.MISSED_TYPE:
dir = "MISSED";
break;
}
sb.append("\nPhone Number:--- " + phNumber + " \nCall Type:--- "
+ dir + " \nCall Date:--- " + callDayTime
+ " \nCall duration in sec :--- " + callDuration);
sb.append("\n----------------------------------");
}
managedCursor.close();
txtcall.setText(sb);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
and in your manifest request for following permissions:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
You need a BroadcastReceiver for ACTION_PHONE_STATE_CHANGED This will call your received whenever the phone-state changes from idle, ringing, offhook so from the previous value and the new value you can detect if this is an incoming/outgoing call.
Required permission would be:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
But if you also want to receive the EXTRA_INCOMING_NUMBER in that broadcast, you'll need another permission: "android.permission.READ_CALL_LOG"
And the code something like this:
val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "onReceive")
}
}
override fun onResume() {
val filter = IntentFilter()
filter.addAction("android.intent.action.PHONE_STATE")
registerReceiver(receiver, filter)
super.onResume()
}
override fun onPause() {
unregisterReceiver(receiver)
super.onPause()
}
and in receiver class, we can get current state by reading intent like this:
intent.extras["state"]
the result of extras could be:
RINGING -> If your phone is ringing
OFFHOOK -> If you are talking with someone (Incoming or Outcoming
call)
IDLE -> if call ended (Incoming or Outcoming call)
With PHONE_STATE broadcast we don't need to use PROCESS_OUTGOING_CALLS permission or deprecated NEW_OUTGOING_CALL action.
Refer to the answer by Gabe Sechan. As mentioned, in the case of an Outgoing call, we have the following state change: IDLE -> OFFHOOK -> IDLE. In Gabe's original answer, savedNumber is only set if the phone state becomes RINGING which won't be true for an Outgoing call. A small fix to to also set savedNumber when the phone state becomes OFFHOOK:
case TelephonyManager.CALL_STATE_OFFHOOK:
if(lastState != TelephonyManager.CALL_STATE_RINGING){
//IDLE to OFFHOOK for example.
isIncoming = false;
callStartTime = new Date();
savedNumber = number;
onOutgoingCallStarted(context, savedNumber, callStartTime);
}
...
This fix allows the dialed number to be passed to Outgoing call methods in the same way that the incoming number is passed to Incoming call or Missed call methods.
Hack Alert :)
If you are just interested in the incoming call event, consider using the AudioManager and listening to focus changes.
Advantage - no permission is required.
Disadvantage - won't work in silent mode... in that case, we will increase the volume of the incoming call to the minimum to still get the Audio focus event.
/**
* Register a callback to [AudioManager] to identify an incoming call.
*/
private fun registerAudioFocusChangeListener(){
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val incomingCallVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING)
if(incomingCallVolume == 0) {
// Hack Alert :)
// The user has muted the phone calls, if we still want to intercept the event,
// we set the volume of the incoming call to Minumum otherwise we will not get
// Audio focus event...
try {
audioManager.adjustVolume(
AudioManager.ADJUST_UNMUTE,
AudioManager.FLAG_ALLOW_RINGER_MODES
)
audioManager.setStreamVolume(
AudioManager.STREAM_RING,
audioManager.getStreamMinVolume(AudioManager.STREAM_RING),
0
);
} catch (e : SecurityException){
// DND (Don't Disturb Mode) is probably ON, we are not allowed to set the volume
// in this scenario, But in this case no incoming call is possible anyway.
}
}
val requestBuilder = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
requestBuilder.setOnAudioFocusChangeListener { _ ->
Handler(Looper.getMainLooper()).postDelayed({
val mode = audioManager.mode
if(mode == AudioManager.MODE_RINGTONE || mode == AudioManager.MODE_IN_CALL){
//Ring Ring, do your thing
}
}, 100)
}
audioManager.requestAudioFocus(requestBuilder.build())
}
I'm using a NotificationListenerService in my android app that was developed from kpbird's example. The service runs in the background even after the app is destroyed. Is there a way to stop this service and start it when app is started?
NotificationListenerService can not be stopped, because after we start the service system will call bindService() . The service will keep a ServiceConnection, then it will not response to the stopService or stopSelf.
As my search result , we also can not remove ServiceConnection from a Service instance.
Create a broadcast receiver inside service which extends NotificationListenerService
like this
public class Block_All_Notification2 extends NotificationListenerService {
boolean check=false;
CancelNotificationReceiver mReceiver = new CancelNotificationReceiver();
public static final String Package_Name = "com.muhaiminurabir.notificationblocker";
#Override
public IBinder onBind(Intent intent) {
return super.onBind(intent);
}
#Override
public void onNotificationPosted(StatusBarNotification sbn){
// Implement what you want here
// Inform the notification manager about dismissal of all notifications.
Log.d("Msg", "Notification arrived");
start_blocking();
/* if (false){
cancelAllNotifications();
}*/
//Block_All_Notification2.this.cancelAllNotifications();
}
#Override
public void onNotificationRemoved(StatusBarNotification sbn){
// Implement what you want here
Log.d("Msg", "Notification Removed");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String block = intent.getStringExtra("block");
Log.d("checking service 1",block+"");
if (block.equals("yes")){
check=true;
}else if(block.equals("no")){
check=false;
}
Log.d("checking service 1",check+"");
return START_STICKY;
}
#Override
public void onCreate() {
super.onCreate();
IntentFilter filter = new IntentFilter();
filter.addAction(Package_Name);
registerReceiver(mReceiver, filter);
}
#Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
public void start_blocking(){
Log.d("checking service",check+"");
if (check==true){
cancelAllNotifications();
}
}
class CancelNotificationReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("received service","received");
String action;
if (intent != null && intent.getAction() != null) {
action = intent.getAction();
if (action.equals(Package_Name)) {
String block = intent.getStringExtra("block");
if (TextUtils.equals(block, "no")) {
Log.d("checking service 1",block+"");
if (block.equals("yes")){
check=true;
cancelAllNotifications();
}else if(block.equals("no")){
check=false;
}
Log.d("checking service 1",check+"");
} else if (TextUtils.equals(block, "yes")) {
Log.d("checking service 1",block+"");
if (block.equals("yes")){
check=true;
cancelAllNotifications();
}else if(block.equals("no")){
check=false;
}
Log.d("checking service 1",check+"");
}
}
}
}
}
}
and in your activity add this code to communicate with service which create above
Intent intent = new Intent();
intent.setAction(Block_All_Notification2.Package_Name);
intent.putExtra("block", "no");
context.sendBroadcast(intent);
Perhaps this problem has been solved.
However, I recently found a way to start or stop NotificationListenerService, and I would like to share it.
Of course, this is not the official method I think.
Therefore, I think you can just look at it for reference that there is also this way.
I got a hint from the fact that in order to run NotificationListenerService, users must be authorized manually.
When the Start Button is pressed, the NotificationListener permission window is displayed, and a Toast Message is displayed to guide the user to request permission.
On the contrary, when the Stop Button is pressed, the NotificationListener permission window is displayed, and at the same time, a Toast Message is displayed to guide the user to revoke permission.
My code is like belows.
main_start_service_btn.setOnClickListener {
if(isNotificationPermissionAllowed()) {
Toast.makeText(this, "Already Starting Service.", Toast.LENGTH_LONG).show()
}
else {
Toast.makeText(this, "Please allow permission in the next window", Toast.LENGTH_LONG).show()
startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
}
}
main_stop_service_btn.setOnClickListener {
if(isNotificationPermissionAllowed()) {
Toast.makeText(this, "Please deny permission in the next window", Toast.LENGTH_LONG).show()
startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
}
else {
Toast.makeText(this, "Already Stopped Service.", Toast.LENGTH_LONG).show()
}
}
The NotificationListenerService extends Service so yes you can stop a service by calling stopSelf() method in Service class.
Look at this: NotificationListenerService and this Services
Hope it Helps,