we are using Sinch voip api. There is a bound service which starts at app start , we initialize the sinch client in the service and it is always running in the background. I tried putting the code for notification in the call screen activity as this activity will always show to accept the call. My aim is to be able to click on the notification and reopen this call activity like in whatsapp.
public class CallScreenActivity extends BaseActivity {
static final String TAG = CallScreenActivity.class.getSimpleName();
private AudioPlayer mAudioPlayer;
private Timer mTimer;
private UpdateCallDurationTask mDurationTask;
private String mCallId;
String mCaller, mReceiver;
String otherusername, myname;
private long mCallStart = 0;
private TextView mCallDuration;
private TextView mCallState;
private TextView mCallerName;
private ImageView mCallImg;
private String mk, mTimestamp;
private String mProfpic;
Button endCallButton;
Notification notification;
NotificationManager notificationManager;
private class UpdateCallDurationTask extends TimerTask {
#Override
public void run() {
CallScreenActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
updateCallDuration();
}
});
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| +WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| +WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
setContentView(R.layout.activity_callscreen);
mAudioPlayer = new AudioPlayer(this);
mCallDuration = (TextView) findViewById(R.id.callDuration);
mCallerName = (TextView) findViewById(R.id.remoteUser);
mCallState = (TextView) findViewById(R.id.callState);
mCallImg = (ImageView) findViewById(R.id.imgotherusr);
endCallButton = (Button) findViewById(R.id.hangupButton);
endCallButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
endCall();
}
});
mCallStart = System.currentTimeMillis();
mCallId = getIntent().getStringExtra(SinchCallService.CALL_ID);
UserSession us = new UserSession(this);
mk = us.getUserKey();
myname = us.getUsername();
mCaller = getIntent().getStringExtra("calleruid");
mReceiver = getIntent().getStringExtra("receiveruid");
otherusername = getIntent().getStringExtra("otherusername");
mTimestamp = getIntent().getStringExtra("timestamp");
System.out.println(mCaller+"on create call screen activity ongoing call" + mReceiver + otherusername);
showNotification();
}
private void showNotification() {
System.out.println("show notification callscreenactivity");
Intent notificationIntent = new Intent(this, CallScreenActivity.class);
notificationIntent.setAction("ongoingcall");
notificationIntent.putExtra("calleruid", mCaller);
notificationIntent.putExtra("receiveruid", mReceiver);
notificationIntent.putExtra("otherusername", otherusername);
notificationIntent.putExtra("timestamp", mTimestamp);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent hangupintent = new Intent(this, CallScreenActivity.class);
hangupintent.setAction("hangupcall");
hangupintent.setAction("ongoingcall");
hangupintent.putExtra("calleruid", mCaller);
hangupintent.putExtra("receiveruid", mReceiver);
hangupintent.putExtra("otherusername", otherusername);
hangupintent.putExtra("timestamp", mTimestamp);
PendingIntent phangupintent = PendingIntent.getService(this, 0,
hangupintent, 0);
notification = new NotificationCompat.Builder(this)
.setContentTitle("In call with " + otherusername)
.setContentText("Duration " + VoiceCallHelper.formatTimespan(System.currentTimeMillis() - mCallStart))
.setSmallIcon(R.drawable.iconcall)
.setContentIntent(pendingIntent)
.addAction(R.drawable.ic_arrow_back, "Hangup",
phangupintent).build();
notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(111 /* ID of notification */, notification);
}
#Override
public void onServiceConnected() {
try {
doStuff();
} catch (NullPointerException e) {
//getSinchServiceInterface() in doStuff below throw null pointer error.
}
}
private void doStuff() {
final Call call = getSinchServiceInterface().getCall(mCallId);
if (call != null) {
call.addCallListener(new SinchCallListener());
mCallState.setText(call.getState().toString());
DBREF_USER_PROFILES.child(call.getRemoteUserId()).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
System.out.println("datasnapshot callscreenactivity otheruser" + dataSnapshot);
User u = User.parse(dataSnapshot);
mCallerName.setText(u.getName());
mProfpic = u.getProfpicurl();
Glide.with(CallScreenActivity.this).load(mProfpic).into(mCallImg);
} else {
mCallerName.setText(call.getHeaders().get("username"));
Glide.with(CallScreenActivity.this).load(R.drawable.whatsapplogo).into(mCallImg);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
} else {
Log.e(TAG, "Started with invalid callId, aborting.");
finish();
}
}
#Override
public void onPause() {
super.onPause();
mDurationTask.cancel();
mTimer.cancel();
}
#Override
public void onResume() {
super.onResume();
mTimer = new Timer();
mDurationTask = new UpdateCallDurationTask();
mTimer.schedule(mDurationTask, 0, 500);
if (getIntent() != null && getIntent().getAction() != null) {
switch (getIntent().getAction()) {
case "ongoingcall":
System.out.println("on resume call screen activity ongoing call" + mCaller + mReceiver + otherusername);
mCaller = getIntent().getStringExtra("calleruid");
mReceiver = getIntent().getStringExtra("receiveruid");
otherusername = getIntent().getStringExtra("otherusername");
mTimestamp = getIntent().getStringExtra("timestamp");
break;
case "hangupcall":
System.out.println("on resume call screen activity hangup call");
mCaller = getIntent().getStringExtra("calleruid");
mReceiver = getIntent().getStringExtra("receiveruid");
otherusername = getIntent().getStringExtra("otherusername");
mTimestamp = getIntent().getStringExtra("timestamp");
endCallButton.performClick();
break;
}
}
}
#Override
public void onBackPressed() {
startActivity(new Intent(CallScreenActivity.this, MainAct.class));
}
private void endCall() {
if (notification != null) {
System.out.println("cancelling notification in endCAll callscreenactivity");
notificationManager.cancel(111);
}
mAudioPlayer.stopProgressTone();
Call call = getSinchServiceInterface().getCall(mCallId);
if (call != null) {
call.hangup();
}
finish();
}
private void updateCallDuration() {
if (mCallStart > 0) {
mCallDuration.setText(VoiceCallHelper.formatTimespan(System.currentTimeMillis() - mCallStart));
showNotification();
}
}
private class SinchCallListener implements CallListener {
#Override
public void onCallEnded(Call call) {
CallEndCause cause = call.getDetails().getEndCause();
Log.d(TAG, "Call ended. Reason: " + cause.toString() + mk + mCaller);
if (mk != null && mCaller != null && mk.matches(mCaller)) {
mAudioPlayer.stopProgressTone();
setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
String endMsg = "Call ended: " + call.getDetails().toString();
Long gt = GetTimeStamp.Id();
Toast.makeText(CallScreenActivity.this, endMsg, Toast.LENGTH_LONG).show();
System.out.println(endMsg + "mtimestamp" + mTimestamp);
String cau;
String oth;
if (call.getDetails().getDuration() > 0)
cau = "completed";
else
cau = cause.toString();
CallDetails cd1 = new CallDetails(String.valueOf(call.getDetails().getDuration()), mCaller, mReceiver, cau, String.valueOf(gt), mTimestamp, mProfpic, mCallerName.getText().toString());
CallDetails cd2 = new CallDetails(String.valueOf(call.getDetails().getDuration()), mCaller, mReceiver, cau, String.valueOf(gt), mTimestamp, mProfpic, myname);
System.out.println(mCaller + "end msg callscreenactivity" + mReceiver + " " + String.valueOf(gt));
System.out.println("end msg callscreenactivity" + mReceiver + " " + DBREF.child("VoiceCalls").child(mCaller).child(String.valueOf(gt)));
//setting in mCaller mykey node at voicecalls node firebase
DBREF_CALLS.child(mCaller).child(String.valueOf(gt)).setValue(cd1);
//setting in mReceiver otheruserkey node at voicecalls node firebase
DBREF_CALLS.child(mReceiver).child(String.valueOf(gt)).setValue(cd2);
}
endCall();
}
#Override
public void onCallEstablished(Call call) {
Log.d(TAG, "Call established");
mAudioPlayer.stopProgressTone();
mCallState.setText(call.getState().toString());
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
mCallStart = System.currentTimeMillis();
mTimestamp = GetTimeStamp.timeStamp();
}
#Override
public void onCallProgressing(Call call) {
Log.d(TAG, "Call progressing");
mAudioPlayer.playProgressTone();
}
#Override
public void onShouldSendPushNotification(Call call, List<PushPair> pushPairs) {
// Send a push through your push provider here, e.g. GCM
}
}
}
My SinchCallService class is this:
public class SinchCallService extends Service {
private static final String APP_KEY = SINCH_APPLICATION_KEY;
private static final String APP_SECRET = SINCH_SECRET_KEY;
private static final String ENVIRONMENT = "sandbox.sinch.com";
public static final String LOCATION = "LOCATION";
public static final String CALL_ID = "CALL_ID";
static final String TAG = SinchCallService.class.getSimpleName();
private SinchServiceInterface mSinchServiceInterface = new SinchServiceInterface();
private SinchClient mSinchClient;
private String mUserId;
private StartFailedListener mListener;
#Override
public void onCreate() {
super.onCreate();
UserSession us = new UserSession(this);
System.out.println("From sinchcall oncreate" + us.getUserKey());
if (!isStarted()) {
System.out.println("sinch not started callservice oncreate " + us.getUserKey());
start(us.getUserKey());
}
}
#Override
public void onDestroy() {
if (mSinchClient != null && mSinchClient.isStarted()) {
mSinchClient.terminate();
}
super.onDestroy();
}
public void start(String userName) {
System.out.println("sinch call service start " + userName);
if (mSinchClient == null) {
mUserId = userName;
mSinchClient = Sinch.getSinchClientBuilder().context(getApplicationContext()).userId(userName)
.applicationKey(APP_KEY)
.applicationSecret(APP_SECRET)
.environmentHost(ENVIRONMENT).build();
mSinchClient.setSupportCalling(true);
mSinchClient.startListeningOnActiveConnection();
mSinchClient.addSinchClientListener(new MySinchClientListener());
mSinchClient.getCallClient().addCallClientListener(new SinchCallClientListener());
mSinchClient.start();
System.out.println(" sinch client started");
}
}
private void stop() {
if (mSinchClient != null) {
mSinchClient.terminate();
mSinchClient = null;
}
}
private boolean isStarted() {
return (mSinchClient != null && mSinchClient.isStarted());
}
#Override
public IBinder onBind(Intent intent) {
return mSinchServiceInterface;
}
public class SinchServiceInterface extends Binder {
public SinchCallService getService() {
return SinchCallService.this;
}
public Call callPhoneNumber(String phoneNumber) {
return mSinchClient.getCallClient().callPhoneNumber(phoneNumber);
}
public Call callUser(String userId) {
return mSinchClient.getCallClient().callUser(userId);
}
public Call callUser(String userId, Map<String, String> headers) {
if(!isStarted()){
UserSession us = new UserSession(getApplicationContext());
startClient(us.getUserKey());
}
return mSinchClient.getCallClient().callUser(userId, headers);
}
public String getUserName() {
return mUserId;
}
public boolean isStarted() {
return SinchCallService.this.isStarted();
}
public void startClient(String userName) {
System.out.println("startClient called sinchcallservice" + userName);
if (!isStarted()) {
System.out.println("startClient not started callservice " + userName);
start(userName);
}
}
public void stopClient() {
stop();
}
public void setStartListener(StartFailedListener listener) {
mListener = listener;
}
public Call getCall(String callId) {
return mSinchClient.getCallClient().getCall(callId);
}
}
public interface StartFailedListener {
void onStartFailed(SinchError error);
void onStarted();
}
private class MySinchClientListener implements SinchClientListener {
#Override
public void onClientFailed(SinchClient client, SinchError error) {
if (mListener != null) {
mListener.onStartFailed(error);
}
mSinchClient.terminate();
mSinchClient = null;
}
#Override
public void onClientStarted(SinchClient client) {
Log.d(TAG, "SinchClient started");
if (mListener != null) {
mListener.onStarted();
}
}
#Override
public void onClientStopped(SinchClient client) {
Log.d(TAG, "SinchClient stopped");
}
#Override
public void onLogMessage(int level, String area, String message) {
switch (level) {
case Log.DEBUG:
Log.d(area, message);
break;
case Log.ERROR:
Log.e(area, message);
break;
case Log.INFO:
Log.i(area, message);
break;
case Log.VERBOSE:
Log.v(area, message);
break;
case Log.WARN:
Log.w(area, message);
break;
}
}
#Override
public void onRegistrationCredentialsRequired(SinchClient client,
ClientRegistration clientRegistration) {
}
}
private class SinchCallClientListener implements CallClientListener {
#Override
public void onIncomingCall(CallClient callClient, Call call) {
Log.e(TAG, "Incoming call");
Intent intent = new Intent(SinchCallService.this, IncomingCallScreenActivity.class);
intent.putExtra(CALL_ID, call.getCallId());
intent.putExtra(LOCATION, call.getHeaders().get("location"));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
SinchCallService.this.startActivity(intent);
}
}
}
Following is my BaseActivity.java :
public abstract class BaseActivity extends FragmentActivity implements ServiceConnection {
private SinchCallService.SinchServiceInterface mSinchServiceInterface;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getApplicationContext().bindService(new Intent(this, SinchCallService.class), this,
BIND_AUTO_CREATE);
}
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
if (SinchCallService.class.getName().equals(componentName.getClassName())) {
mSinchServiceInterface = (SinchCallService.SinchServiceInterface) iBinder;
onServiceConnected();
}
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
if (SinchCallService.class.getName().equals(componentName.getClassName())) {
mSinchServiceInterface = null;
onServiceDisconnected();
}
}
protected void onServiceConnected() {
// for subclasses
}
protected void onServiceDisconnected() {
// for subclasses
}
protected SinchCallService.SinchServiceInterface getSinchServiceInterface() {
return mSinchServiceInterface;
}
}
I have tried things like setting
android:launchMode="singleTop"
If I backpress on the CallScreenActivity and click on notification, it opens the MainAct not the CallScreenActivity. How to make it open the CallScreenActivity?
I tried a solution with creating a stackbuilder and passing it to pending intent as follows:
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(CallScreenActivity.class);
stackBuilder.addNextIntent(notificationIntent);
PendingIntent pendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
And also changed the manifest as follows:
<activity
android:launchMode="singleTop"
android:name=".activity.calls.CallScreenActivity"
android:screenOrientation="portrait"
android:parentActivityName=".activity.main.MainAct">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.main.MainAct"/>
</activity>
But the above change leads to app crash with following error:
07-06 23:38:52.353 9182-9182/com.app E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.NullPointerException: Attempt to invoke interface method 'com.sinch.android.rtc.calling.CallClient com.sinch.android.rtc.SinchClient.getCallClient()' on a null object reference
at com.app.services.SinchCallService$SinchServiceInterface.getCall(SinchCallService.java:150)
at com.app.activity.calls.CallScreenActivity.endCall(CallScreenActivity.java:265)
at com.app.activity.calls.CallScreenActivity.access$100(CallScreenActivity.java:51)
at com.app.activity.calls.CallScreenActivity$1.onClick(CallScreenActivity.java:110)
at android.view.View.performClick(View.java:5207)
at android.view.View$PerformClick.run(View.java:21177)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5438)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)
So If i am using the code without the stackbuilder and i press home button, then press the notification my CallSCreenACtivity opens, but if i press back button inside the CAllSCreenACtivity and then press home button then i press notification, it opens the main acitivty instead of the CallSCreenACtivity. My call is still actvity even after pressing the backbutton on callscreenactivty.
Also I have noticed that the duration of the call being shown in the notification has stopped refreshing after back press on the CallScreenActivity but the call is still active.
Flow of the app:
For Caller:-
When app starts, MainAct starts,from where a user clicks on someone's profile and is taken to ProfilesAct where he presses the call button to call that person and is taken to the CallScreenActivity.
MainAct->ProfilesACt->CallScreenACt
For Receiver:-
App is in background, the call comes, the SinchCallService makes the IncomingCAllAct to show, when he accepts the call he is taken to the CallScreenActivity.
IncomingCallAct->CallSCreeenACtivity
or could be suppose receiver is already using the app, then
any activity(could be chat activity, main acitivty etc)->IncomingCallAct->CallSCreeenACtivity
Manifest of the app:-
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.app.t">
<application
android:name=".TApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/Theme.AppCompat.Light.NoActionBar">
<activity
android:name=".SplashAct"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activity.userprofile.UserProfileAct"
android:screenOrientation="portrait" />
<activity
android:name=".activity.videocalls.VideoCallScreenActivity"
android:screenOrientation="portrait"
android:launchMode="singleTop"/>
<activity
android:name=".activity.videocalls.IncomingVideoCallScreenActivity"
android:screenOrientation="portrait"
android:noHistory="true"/>
<activity
android:name=".activity.main.MainAct"
android:launchMode="singleTop"
android:screenOrientation="portrait"></activity>
<activity android:name=".activity.chat.ChatActivity" />
<service android:name=".services.MyFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name=".services.MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<activity android:name=".activity.settings.SettingsmainActivity" />
<service android:name=".services.SinchCallService"></service>
<activity
android:name=".activity.calls.CallScreenActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name=".activity.calls.IncomingCallScreenActivity"
android:noHistory="true"
android:screenOrientation="portrait" />
<activity android:name=".activity.chat.NewChatActivity" />
<activity android:name=".activity.chat.ChatActivity1"/>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
</application>
</manifest>
There is no need to make the CallScreenActivity in the mainfest as singletask or singletop, just store the call parameters like callid(which helps Sinch api in recognizing the specific call), call start time, etc in sharedpreferences or sqlite db, make the notification intent like following :
Intent notificationIntent = new Intent(getApplicationContext(), CallScreenActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0,
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new NotificationCompat.Builder(getApplicationContext())
.setContentTitle("In call with " + otherusername)
.setSmallIcon(R.drawable.iconcall)
.setContentIntent(pendingIntent).build();
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(111 /* ID of notification */, notification);
As long as we pass the right callid we will be able to resume call by clicking the notification.
Related
I created a service that extends MediaBrowserServiceCompat. This service holds a reference to my player and creates a new MediaSession with a callback. Everytime the player changes state, I update the MediaSession's playback state and create a MediaStyle notification. The notification is showing when I start to play something in my player, but the buttons in the notification are not triggering the MediaSession callback, they don't do anything. I'm setting the right flags in the MediaSession, I'm setting the session as active, I'm setting the correct actions in the playback state, I'm passing the session token to the notification but still not getting any callbacks from it. I really don't know what I'm doing wrong. All this code is inside a module imported by my app.
My NotificationHelper class:
private final MusicService mService;
private final NotificationCompat.Action mPlayAction;
private final NotificationCompat.Action mPauseAction;
private final NotificationCompat.Action mNextAction;
private final NotificationCompat.Action mPrevAction;
private final NotificationManager mNotificationManager;
public MediaNotificationManager(MusicService service) {
mService = service;
mNotificationManager =
(NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
mPlayAction =
new NotificationCompat.Action(
R.drawable.exo_icon_play,
"Play",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_PLAY));
mPauseAction =
new NotificationCompat.Action(
R.drawable.exo_icon_pause,
"Pause",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_PAUSE));
mNextAction =
new NotificationCompat.Action(
R.drawable.exo_icon_next,
"Next",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_SKIP_TO_NEXT));
mPrevAction =
new NotificationCompat.Action(
R.drawable.exo_icon_previous,
"Previous",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS));
// Cancel all notifications to handle the case where the Service was killed and
// restarted by the system.
mNotificationManager.cancelAll();
}
public Notification getNotification(MediaMetadataCompat metadata,
#NonNull PlaybackStateCompat state,
MediaSessionCompat.Token token) {
boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_PLAYING;
MediaDescriptionCompat description = metadata.getDescription();
NotificationCompat.Builder builder =
buildNotification(state, token, isPlaying, description);
return builder.build();
}
private NotificationCompat.Builder buildNotification(#NonNull PlaybackStateCompat state,
MediaSessionCompat.Token token,
boolean isPlaying,
MediaDescriptionCompat description) {
// Create the (mandatory) notification channel when running on Android Oreo.
if (isAndroidOOrHigher()) {
createChannel();
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(mService, CHANNEL_ID)
.setSmallIcon(R.drawable.exo_notification_small_icon)
.setContentTitle("Track title")
.setContentText("Artist - Album")
.setLargeIcon(BitmapFactory.decodeResource(mService.getResources(), R.drawable.exo_notification_small_icon))
.setStyle(new MediaStyle().setShowActionsInCompactView(0).setMediaSession(token));
builder.addAction(mPrevAction);
builder.addAction(isPlaying ? mPauseAction : mPlayAction);
builder.addAction(mNextAction);
return builder;
}
// Does nothing on versions of Android earlier than O.
#RequiresApi(Build.VERSION_CODES.O)
private void createChannel() {
if (mNotificationManager.getNotificationChannel(CHANNEL_ID) == null) {
// The user-visible name of the channel.
CharSequence name = "MediaSession";
// The user-visible description of the channel.
String description = "MediaSession and MediaPlayer";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
// Configure the notification channel.
mChannel.setDescription(description);
mChannel.enableLights(true);
// Sets the notification light color for notifications posted to this
// channel, if the device supports this feature.
mChannel.setLightColor(Color.RED);
mChannel.enableVibration(true);
mChannel.setVibrationPattern(
new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
mNotificationManager.createNotificationChannel(mChannel);
Log.d(TAG, "createChannel: New channel created");
} else {
Log.d(TAG, "createChannel: Existing channel reused");
}
}
private boolean isAndroidOOrHigher() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
My Service class:
public class MusicService extends MediaBrowserServiceCompat {
private static final String TAG = MusicService.class.getSimpleName();
private MediaSessionCompat mSession;
private PlayerManager playerManager;
private MediaSessionCallback mCallback;
private MediaNotificationManager mediaNotificationManager;
#Override
public void onCreate() {
super.onCreate();
playerManager = PlayerManager.getInstance(this);
playerManager.addListener(new PlayerManagerServiceListener());
mediaNotificationManager = new MediaNotificationManager(this);
// Create a new MediaSession.
mSession = new MediaSessionCompat(this, "MusicService");
mCallback = new MediaSessionCallback();
mSession.setCallback(mCallback);
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
setSessionToken(mSession.getSessionToken());
mSession.setActive(true);
}
#Override
public void onDestroy() {
mSession.release();
Log.d(TAG, "onDestroy: MediaPlayerAdapter stopped, and MediaSession released");
}
#Override
public BrowserRoot onGetRoot(#NonNull String clientPackageName,
int clientUid,
Bundle rootHints) {
return new BrowserRoot("root", null);
}
#Override
public void onLoadChildren(
#NonNull final String parentMediaId,
#NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
result.sendResult(null);
}
// MediaSession Callback: Transport Controls -> MediaPlayerAdapter
public class MediaSessionCallback extends MediaSessionCompat.Callback {
#Override
public void onPlay() {
playerManager.play();
}
#Override
public void onPause() {
playerManager.pause();
}
#Override
public void onStop() {
playerManager.stop();
}
#Override
public void onSkipToNext() {
playerManager.next();
}
#Override
public void onSkipToPrevious() {
playerManager.previous();
}
#Override
public void onSeekTo(long pos) {
playerManager.seekTo(pos);
}
}
public class PlayerManagerServiceListener implements PlayerManager.PlayerManagerListener {
#Override
public void onError(#Nullable Exception error) {
}
#Override
public void onProgress(long duration, long position) {
}
#Override
public void onPlayerChange(int change) {
}
#Override
public void onTrackChange(TrackVO track) {
}
#Override
public void onListChange(List tracks) {
}
#Override
public void onPlaybackStateChange(int playbackState) {
PlaybackStateCompat.Builder playbackstateBuilder = new PlaybackStateCompat.Builder();
int playbackStateCompat = -1;
switch(playbackState) {
case PlaybackStateListener.STATE_PLAYING:
playbackStateCompat = PlaybackStateCompat.STATE_PLAYING;
//playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PAUSE);
break;
case PlaybackStateListener.STATE_PAUSED:
playbackStateCompat = PlaybackStateCompat.STATE_PAUSED;
//playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY);
break;
}
if (playbackStateCompat == -1) {
return;
}
mSession.setActive(true);
playbackstateBuilder.setActions(
PlaybackStateCompat.ACTION_PLAY |
PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_SKIP_TO_NEXT |
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
playbackstateBuilder.setState(playbackStateCompat, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0);
PlaybackStateCompat state = playbackstateBuilder.build();
MediaMetadataCompat mediaMetadata = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, playerManager.getCurrenTrack().getName())
.build();
mSession.setMetadata(mediaMetadata);
mSession.setPlaybackState(state);
Notification notification = mediaNotificationManager.getNotification(
mediaMetadata,
state,
getSessionToken()
);
Intent intent = new Intent(MusicService.this, MusicService.class);
ContextCompat.startForegroundService(MusicService.this, intent);
startForeground(417, notification);
}
}
}
MediaBrowserHelper to initialize the service:
public class MediaBrowserHelper {
private static final String TAG = MediaBrowserHelper.class.getSimpleName();
private final Context mContext;
private final Class<? extends MediaBrowserServiceCompat> mMediaBrowserServiceClass;
private final List<Callback> mCallbackList = new ArrayList<>();
private final MediaBrowserConnectionCallback mMediaBrowserConnectionCallback;
private final MediaControllerCallback mMediaControllerCallback;
private final MediaBrowserSubscriptionCallback mMediaBrowserSubscriptionCallback;
private MediaBrowserCompat mMediaBrowser;
#Nullable
private MediaControllerCompat mMediaController;
public MediaBrowserHelper(Context context,
Class<? extends MediaBrowserServiceCompat> serviceClass) {
mContext = context;
mMediaBrowserServiceClass = serviceClass;
mMediaBrowserConnectionCallback = new MediaBrowserConnectionCallback();
mMediaControllerCallback = new MediaControllerCallback();
mMediaBrowserSubscriptionCallback = new MediaBrowserSubscriptionCallback();
}
public void onStart() {
if (mMediaBrowser == null) {
mMediaBrowser =
new MediaBrowserCompat(
mContext,
new ComponentName(mContext, mMediaBrowserServiceClass),
mMediaBrowserConnectionCallback,
null);
mMediaBrowser.connect();
}
Log.d(TAG, "onStart: Creating MediaBrowser, and connecting");
}
public void onStop() {
if (mMediaController != null) {
mMediaController.unregisterCallback(mMediaControllerCallback);
mMediaController = null;
}
if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
mMediaBrowser.disconnect();
mMediaBrowser = null;
}
resetState();
Log.d(TAG, "onStop: Releasing MediaController, Disconnecting from MediaBrowser");
}
/**
* Called after connecting with a {#link MediaBrowserServiceCompat}.
* <p>
* Override to perform processing after a connection is established.
*
* #param mediaController {#link MediaControllerCompat} associated with the connected
* MediaSession.
*/
protected void onConnected(#NonNull MediaControllerCompat mediaController) {
}
/**
* Called after loading a browsable {#link MediaBrowserCompat.MediaItem}
*
* #param parentId The media ID of the parent item.
* #param children List (possibly empty) of child items.
*/
protected void onChildrenLoaded(#NonNull String parentId,
#NonNull List<MediaBrowserCompat.MediaItem> children) {
}
/**
* Called when the {#link MediaBrowserServiceCompat} connection is lost.
*/
protected void onDisconnected() {
}
#NonNull
protected final MediaControllerCompat getMediaController() {
if (mMediaController == null) {
throw new IllegalStateException("MediaController is null!");
}
return mMediaController;
}
/**
* The internal state of the app needs to revert to what it looks like when it started before
* any connections to the {#link MusicService} happens via the {#link MediaSessionCompat}.
*/
private void resetState() {
performOnAllCallbacks(new CallbackCommand() {
#Override
public void perform(#NonNull Callback callback) {
callback.onPlaybackStateChanged(null);
}
});
Log.d(TAG, "resetState: ");
}
public MediaControllerCompat.TransportControls getTransportControls() {
if (mMediaController == null) {
Log.d(TAG, "getTransportControls: MediaController is null!");
throw new IllegalStateException("MediaController is null!");
}
return mMediaController.getTransportControls();
}
public void registerCallback(Callback callback) {
if (callback != null) {
mCallbackList.add(callback);
// Update with the latest metadata/playback state.
if (mMediaController != null) {
final MediaMetadataCompat metadata = mMediaController.getMetadata();
if (metadata != null) {
callback.onMetadataChanged(metadata);
}
final PlaybackStateCompat playbackState = mMediaController.getPlaybackState();
if (playbackState != null) {
callback.onPlaybackStateChanged(playbackState);
}
}
}
}
private void performOnAllCallbacks(#NonNull CallbackCommand command) {
for (Callback callback : mCallbackList) {
if (callback != null) {
command.perform(callback);
}
}
}
/**
* Helper for more easily performing operations on all listening clients.
*/
private interface CallbackCommand {
void perform(#NonNull Callback callback);
}
// Receives callbacks from the MediaBrowser when it has successfully connected to the
// MediaBrowserService (MusicService).
private class MediaBrowserConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
// Happens as a result of onStart().
#Override
public void onConnected() {
try {
// Get a MediaController for the MediaSession.
mMediaController =
new MediaControllerCompat(mContext, mMediaBrowser.getSessionToken());
mMediaController.registerCallback(mMediaControllerCallback);
// Sync existing MediaSession state to the UI.
mMediaControllerCallback.onMetadataChanged(mMediaController.getMetadata());
mMediaControllerCallback.onPlaybackStateChanged(
mMediaController.getPlaybackState());
MediaBrowserHelper.this.onConnected(mMediaController);
} catch (RemoteException e) {
Log.d(TAG, String.format("onConnected: Problem: %s", e.toString()));
throw new RuntimeException(e);
}
mMediaBrowser.subscribe(mMediaBrowser.getRoot(), mMediaBrowserSubscriptionCallback);
}
}
// Receives callbacks from the MediaBrowser when the MediaBrowserService has loaded new media
// that is ready for playback.
public class MediaBrowserSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
#Override
public void onChildrenLoaded(#NonNull String parentId,
#NonNull List<MediaBrowserCompat.MediaItem> children) {
MediaBrowserHelper.this.onChildrenLoaded(parentId, children);
}
}
// Receives callbacks from the MediaController and updates the UI state,
// i.e.: Which is the current item, whether it's playing or paused, etc.
private class MediaControllerCallback extends MediaControllerCompat.Callback {
#Override
public void onMetadataChanged(final MediaMetadataCompat metadata) {
performOnAllCallbacks(new CallbackCommand() {
#Override
public void perform(#NonNull Callback callback) {
callback.onMetadataChanged(metadata);
}
});
}
#Override
public void onPlaybackStateChanged(#Nullable final PlaybackStateCompat state) {
performOnAllCallbacks(new CallbackCommand() {
#Override
public void perform(#NonNull Callback callback) {
callback.onPlaybackStateChanged(state);
}
});
}
// This might happen if the MusicService is killed while the Activity is in the
// foreground and onStart() has been called (but not onStop()).
#Override
public void onSessionDestroyed() {
resetState();
onPlaybackStateChanged(null);
MediaBrowserHelper.this.onDisconnected();
}
}
}
Manifest:
<service android:name="com.amco.playermanager.MusicService">
<intent-filter>
<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"/>
</intent-filter>
</receiver>
It turns out that the whole problem was caused by having another BroadcastReceiver handling MEDIA_BUTTON declared in my app's Manifest. By removing that receiver everything works now.
I am building a simple downloader app, with a pause/resume button. When I click on pause button and kill the app the download progress isn't shown anymore, after I open the app again. I want to save the download progress state even if the app is killed. Can anyone help me with this? I'm building the app using download manager pro library from github.
here's the code:
Activity Class:
public class MainActivity extends AppCompatActivity {
String[] permissions = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
public static final int REQUEST_ID_MULTIPLE_PERMISSIONS = 100;
EditText edt_url;
Button btn_download;
TextView fileNametv, ProgressbarTv;
private MyDownloadService mService;
ProgressBar progressBar;
String filename, url;
/* UrlFileNameListener listener;*/
Integer progress;
MyReceiver receiver;
public static MainActivity instance;
/* RecyclerView recyclerView;
List<Model> downloadList;
LinearLayoutManager manager;
DownloadAdapter adapter;
// ImageView pausebtn;
int position;*/
File myDirectory;
Boolean mBound = false;
Button pause_btn;
int tasktoken;
/* Model model;
Parcelable mListState;
private final String KEY_RECYCLER_STATE = "recycler_state";
private final String KEY_LIST_STATE = "list_state";*/
private ServiceConnection mServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mBound = true;
MyDownloadService.LocalBinder localBinder = (MyDownloadService.LocalBinder) iBinder;
mService = localBinder.getService();
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
mBound = false;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* if(savedInstanceState!=null){
mListState = savedInstanceState.getParcelable(KEY_RECYCLER_STATE);
downloadList =savedInstanceState.getParcelableArrayList(KEY_LIST_STATE);
} */
setContentView(R.layout.activity_main);
edt_url = findViewById(R.id.edt_url);
btn_download = findViewById(R.id.btn_download);
pause_btn = findViewById(R.id.pause_resume);
/*recyclerView = findViewById(R.id.recycler_view);
downloadList = new ArrayList<>();
manager = new LinearLayoutManager(this);
adapter = new DownloadAdapter(downloadList, this);
recyclerView.setLayoutManager(manager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
manager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration);
recyclerView.setAdapter(adapter);*/
fileNametv = findViewById(R.id.download_file_name);
ProgressbarTv = findViewById(R.id.progress_tv);
progressBar = findViewById(R.id.download_progress);
// pausebtn = findViewById(R.id.pause_resume);
instance = this;
/* if (progress!= null && filename != null) {
savedInstanceState.putInt("progress", progress);
savedInstanceState.putString("filename",filename);
// savedInstanceState.putString("filename", model.getFileName());
// savedInstanceState.putParcelable("list",mListState);
}*/
receiver = new MyReceiver();
btn_download.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getDownloadProcess();
}
});
pause_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mService.pauseDownload(tasktoken);
}
});
LocalBroadcastManager.getInstance(this)
.registerReceiver(receiver,
new IntentFilter("download"));
if (checkAndRequestPermissions()) {
myDirectory = new File(Environment.getExternalStorageDirectory() + "/" + "RITSDownloads2");
if (!myDirectory.exists()) {
myDirectory.mkdir();
}
}
}
#Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this,MyDownloadService.class);
bindService(intent,mServiceConnection,Context.BIND_AUTO_CREATE);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("progress", progress);
outState.putString("filename",filename);
/* mListState = manager.onSaveInstanceState();
outState.putParcelable(KEY_RECYCLER_STATE,mListState);
outState.putParcelableArrayList(KEY_LIST_STATE, (ArrayList<? extends Parcelable>) adapter.getDownloadList());*/
}
#Override
protected void onPause() {
super.onPause();
//mListState = manager.onSaveInstanceState();
}
#Override
protected void onResume() {
super.onResume();
// manager.onRestoreInstanceState(mListState);
}
#Override
public void onBackPressed() {
progress = progressBar.getProgress();
super.onBackPressed();
}
/* #Override
public void onRestoreInstanceState(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onRestoreInstanceState(savedInstanceState, persistentState);
manager.onRestoreInstanceState(mListState);
savedInstanceState.getParcelable(KEY_RECYCLER_STATE);
savedInstanceState.getParcelableArrayList(KEY_LIST_STATE);
}
*/
private void getDownloadProcess() {
url = edt_url.getText().toString();
filename = URLUtil.guessFileName(url, null, null);
//listener.setUrl(url,filename);
edt_url.setText("");
/* model = new Model();
model.setFileName(filename);
downloadList.add(model);
adapter.notifyDataSetChanged();*/
fileNametv.setText(filename);
Intent intent = new Intent(MainActivity.this, MyDownloadService.class);
intent.putExtra("filename", filename);
intent.putExtra("url", url);
intent.setAction(DownloadActions.ACTION.Download_ACTION);
startService(intent);
}
public class MyReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
progress = intent.getIntExtra("progress", 1);
ReportStructure reportStructure = MyDownloadService.downloadManagerPro.singleDownloadStatus(intent.getIntExtra("tasktoken",1));
tasktoken = intent.getIntExtra("tasktoken",1);
// model.setProgress(progress);
/*int position = downloadList.indexOf(model);
DownloadAdapter.DownloadHolder holder = getDownloadHolder(position);
holder.progressBar.setProgress(progress);*/
progressBar.setProgress(progress);
}
}
/* public DownloadAdapter.DownloadHolder getDownloadHolder(int position) {
return (DownloadAdapter.DownloadHolder) recyclerView.findViewHolderForLayoutPosition(position);
}
*/
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
savedInstanceState.getInt("progress");
savedInstanceState.getString("filename");
/* Log.d("savedInstancestate",savedInstanceState.toString());
//savedInstanceState.getInt("position");
if(savedInstanceState!=null){
List<Model> downloadList = savedInstanceState.getParcelableArrayList(KEY_LIST_STATE);
adapter = new DownloadAdapter(downloadList,this);
}
*/
}
private boolean checkAndRequestPermissions() {
if (ContextCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, permissions[1]) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, REQUEST_ID_MULTIPLE_PERMISSIONS);
return false;
} else {
return true;
}
}
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String permissions[], #NonNull int[] grantResults) {
String TAG = "LOG_PERMISSION";
Log.d(TAG, "Permission callback called-------");
switch (requestCode) {
case REQUEST_ID_MULTIPLE_PERMISSIONS: {
Map<String, Integer> perms = new HashMap<>();
// Initialize the map with both permissions
perms.put(this.permissions[0], PackageManager.PERMISSION_GRANTED);
perms.put(this.permissions[1], PackageManager.PERMISSION_GRANTED);
// Fill with actual results from user
if (grantResults.length > 0) {
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
// Check for both permissions
if (perms.get(this.permissions[0]) == PackageManager.PERMISSION_GRANTED
&& perms.get(this.permissions[1]) == PackageManager.PERMISSION_GRANTED
) {
Log.d(TAG, "Phone state and storage permissions granted");
// process the normal flow
//else any one or both the permissions are not granted
//TODO Do your stuff here after permissions granted
} else {
Log.d(TAG, "Some permissions are not granted ask again ");
//permissions is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permissions
// //shouldShowRequestPermissionRationale will return true
//show the dialog or snackbar saying its necessary and try again otherwise proceed with setup.
if (ActivityCompat.shouldShowRequestPermissionRationale(this, this.permissions[0]) ||
ActivityCompat.shouldShowRequestPermissionRationale(this, this.permissions[1])) {
showDialogOK("Phone state and storage permissions required for this app",
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
checkAndRequestPermissions();
break;
case DialogInterface.BUTTON_NEGATIVE:
// proceed with logic by disabling the related features or quit the app.
break;
}
}
});
}
//permissions is denied (and never ask again is checked)
//shouldShowRequestPermissionRationale will return false
else {
Toast.makeText(this, "Go to settings and enable permissions", Toast.LENGTH_LONG)
.show();
//proceed with logic by disabling the related features or quit the app.
}
}
}
}
}
}
private void showDialogOK(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", okListener)
.create()
.show();
}
}
Service Class:
public class MyDownloadService extends Service implements DownloadManagerListener {
private static final String LOG_TAG = "tag";
public static DownloadManagerPro downloadManagerPro;
File myDirectory;
int taskToken;
String name;
Intent notificationIntent;
Notification notification;
PendingIntent pendingIntent;
private IBinder binder = new LocalBinder();
#Nullable
#Override
public IBinder onBind(Intent intent) {
return binder;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getAction().equals(DownloadActions.ACTION.Download_ACTION)) {
Log.d(LOG_TAG, "Received Start Foreground Intent");
}
name = intent.getStringExtra("filename");
final String url = intent.getStringExtra("url");
Log.d(LOG_TAG, name);
Log.d(LOG_TAG, url);
downloadManagerPro = new DownloadManagerPro(this.getApplicationContext());
downloadManagerPro.init("RITSDownloads2/", 16, this);
myDirectory = new File(Environment.getExternalStorageDirectory() + "/" + "RITSDownloads2");
if (!myDirectory.exists()) {
myDirectory.mkdir();
}
taskToken = downloadManagerPro.addTask(name, url, 16, true, true);
Log.d(LOG_TAG, String.valueOf(taskToken));
try {
downloadManagerPro.startDownload(taskToken);
notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setAction(DownloadActions.ACTION.Download_ACTION);
pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
notification = new NotificationCompat.Builder(this)
.setContentTitle("Downloading")
.setTicker("Rits Download")
.setContentText(name)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentIntent(pendingIntent)
.setOngoing(true)
.build();
startForeground(DownloadActions.NOTIFICATION_ID.FOREGROUND_SERVICE,
notification);
// stopForeground(true);
// stopSelf();
} catch (IOException e) {
e.printStackTrace();
}
return START_STICKY;
}
#Override
public void OnDownloadStarted(long taskId) {
Log.d(LOG_TAG, "DownloadStarted");
}
#Override
public void OnDownloadPaused(long taskId) {
}
#Override
public void onDownloadProcess(long taskId, double percent, long downloadedLength) {
final int progress = (int) percent;
final int taskToken = (int) taskId;
// int position = positions.get(taskToken);
notification = new NotificationCompat.Builder(this)
.setContentTitle("Downloading")
.setTicker("Rits Download")
.setContentText(name)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setColor(ContextCompat.getColor(this, R.color.colorPrimary))
.setProgress(100, progress, false)
.build();
startForeground(DownloadActions.NOTIFICATION_ID.FOREGROUND_SERVICE,
notification);
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("progress", progress);
intent.setAction("download");
intent.putExtra("tasktoken",taskToken);
ReportStructure structure = downloadManagerPro.singleDownloadStatus(taskToken);
String name =structure.name;
intent.putExtra("name",name);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
}
#Override
public void OnDownloadFinished(long taskId) {
}
#Override
public void OnDownloadRebuildStart(long taskId) {
}
#Override
public void OnDownloadRebuildFinished(long taskId) {
}
public void pauseDownload(int taskToken){
ReportStructure reportStructure = downloadManagerPro.singleDownloadStatus(taskToken);
if(reportStructure.state == TaskStates.DOWNLOADING){
downloadManagerPro.pauseDownload(taskToken);
} else if(reportStructure.state == TaskStates.PAUSED){
try {
downloadManagerPro.startDownload(taskToken);
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void OnDownloadCompleted(long taskId) {
Log.d(LOG_TAG, "Download Complete");
/* MainActivity.instance.pausebtn.post(new Runnable() {
#Override
public void run() {
MainActivity.instance.pausebtn.setImageDrawable(getResources().getDrawable(R.drawable.ic_action_done));
}
});*/
notification = new NotificationCompat.Builder(this)
.setContentTitle("Download Complete")
.setTicker("Rits Download")
.setContentText(name)
.setSmallIcon(R.drawable.ic_action_done)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setColor(ContextCompat.getColor(this, R.color.colorPrimary))
.build();
startForeground(DownloadActions.NOTIFICATION_ID.FOREGROUND_SERVICE,
notification);
}
#Override
public void connectionLost(long taskId) {
}
#Override
public void onTaskRemoved(Intent rootIntent) {
/* Intent restartService = new Intent(getApplicationContext(),this.getClass());
restartService.setPackage(getPackageName());
PendingIntent restartPendingIntent =PendingIntent.getService(getApplicationContext(), 1,restartService, PendingIntent.FLAG_ONE_SHOT);
AlarmManager myAlarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
myAlarmService.set(
AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + 1000,
restartPendingIntent);*/
}
public class LocalBinder extends Binder{
public MyDownloadService getService(){
return MyDownloadService.this;
}
}
}
I don't know how you want to save the state but I do know that an activity has a onStop() method that you can override.
The onStop() method runs when the app is killed so I would imagine you would want to do your "saving" in this method.
As for saving something I believe you would want to use the SharedPreferences class. You can find more information about that here.
You can find the lifecycle of an activity here
hope this helps
Save the progress on SharedPreferences onDestroy() and retrieve it from there when activity is created.
Cache:
#Override
protected void onDestroy() {
super.onDestroy();
SharedPreferences sharedPreferences = getSharedPreferences("space_name", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt("progress", progress).apply();
}
And retrieve:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences sharedPreferences = getSharedPreferences("space_name", MODE_PRIVATE);
progress = sharedPreferences.getInt("progress", 0); // 0 default value in case is empty
}
I use quickblox for create video call
Start call work, but incoming service not work for opponent user
VideoActivity
public class VideoActivity extends E implements QBRTCClientSessionCallbacks,QBRTCClientVideoTracksCallbacks {
private int userid;
private Boolean isOutgoing,micE=true,vidE=true;
private QBRTCSurfaceView surfaceView,remoteview;
private MediaPlayer mp;
private QBRTCSession currentsession;
private QBMediaStreamManager mediaStreamManager;
private ImageView mic,video;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video);
userid=getIntent().getIntExtra("user", 1);
userid = 39753771;
isOutgoing=getIntent().getBooleanExtra("isoutgoing",false);
isOutgoing = true;
ProcessCalls();
InitSignalling();
if (isOutgoing) {
//CallUser();
SetCallerName(userid);
}
Log.i("errorCheck", "Nz: " + userid);
Log.i("errorCheck", "Na: " + isOutgoing);
if(getIntent().getBooleanExtra("service",false)){
Log.i("errorCheck", "x");
}
mic=(ImageView)findViewById(R.id.mic);
mic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (micE) {
micE = false;
AudioManage();
} else {
micE = true;
AudioManage();
}
}
});
video=(ImageView)findViewById(R.id.video);
video.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (vidE) {
vidE = false;
VideoManage();
} else {
vidE = true;
VideoManage();
}
}
});
surfaceView =(QBRTCSurfaceView)findViewById(R.id.localView);
surfaceView.setMirror(true);
surfaceView.requestLayout();
remoteview=(QBRTCSurfaceView)findViewById(R.id.opponentView);
remoteview.requestLayout();
}
private void SetCallerName(Integer callerID) {
TextView callerName=(TextView)findViewById(R.id.callername);
TextView callertime=(TextView)findViewById(R.id.callTime);
callerName.setText(callerID + " , You:");
if(isOutgoing){
callertime.setText("Calling...");
}
}
private void InitSignalling() {
final QBChatService chatService = QBChatService.getInstance();
Log.i("errorCheck", "ERR1: " + G.userQB);
chatService.login(G.userQB, new QBEntityCallback() {
#Override
public void onSuccess(Object result, Bundle params) {
Log.i("errorCheck", "R0: " + result);
Log.i("errorCheck", "R1: " + params);
chatService.getVideoChatWebRTCSignalingManager().addSignalingManagerListener(new QBVideoChatSignalingManagerListener() {
#Override
public void signalingCreated(QBSignaling qbSignaling, boolean createdLocally) {
Log.i("errorCheck", "Q1: " + qbSignaling);
Log.i("errorCheck", "Q2: " + createdLocally);
if (!createdLocally) {
QBRTCClient.getInstance(VideoActivity.this).addSignaling((QBWebRTCSignaling) qbSignaling);
}
}
});
QBRTCClient.getInstance(G.currentActivity).addSessionCallbacksListener((QBRTCSessionEventsCallback) G.currentActivity);
QBRTCConfig.setDebugEnabled(true);
QBRTCClient.getInstance(getApplicationContext()).prepareToProcessCalls();
Log.i("errorCheck", "chatService1: " + chatService);
if(G.userMobile.equals("09139479548")) {
CallUser();
}
}
#Override
public void onError(QBResponseException errors) {
Log.i("errorCheck", "E1: " + errors);
}
});
}
private void ProcessCalls() {
QBRTCClient.getInstance(this).prepareToProcessCalls();
}
private void CallUser() {
List<Integer> opponents = new ArrayList<Integer>();
opponents.add(userid);
Map<String, String> userInfo = new HashMap<>();
userInfo.put("key", "value");
QBRTCTypes.QBConferenceType qbConferenceType = QBRTCTypes.QBConferenceType.QB_CONFERENCE_TYPE_VIDEO;
QBRTCSession session = QBRTCClient.getInstance(G.currentActivity).createNewSessionWithOpponents(opponents, qbConferenceType);
session.addVideoTrackCallbacksListener(this);
session.startCall(userInfo);
Log.i("errorCheck", "QR1: " + session);
SetCallButtonsDialing(session,userInfo);
StartDialRinging();
}
#Override
public void onReceiveNewSession(QBRTCSession qbrtcSession) {
Log.i("errorCheck","x");
qbrtcSession.addVideoTrackCallbacksListener(this);
Map<String,String> userInfo = qbrtcSession.getUserInfo();
SetLayoutForReceiveCall(qbrtcSession,userInfo);
}
private void SetLayoutForReceiveCall(final QBRTCSession qbrtcSession,final Map<String, String> userInfo) {
final FrameLayout receive=(FrameLayout)findViewById(R.id.answerlayout);
receive.setVisibility(View.VISIBLE);
qbrtcSession.addVideoTrackCallbacksListener(this);
final ImageView calll=(ImageView)findViewById(R.id.answerCall);
calll.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Accept incoming call
qbrtcSession.acceptCall(userInfo);
receive.setVisibility(View.GONE);
SetCallButtonsDialing(qbrtcSession, userInfo);
StartTimer();
if (mp != null && mp.isPlaying()) {
mp.stop();
}
}
});
findViewById(R.id.rejectcall).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
qbrtcSession.rejectCall(userInfo);
VideoActivity.this.finish();
if(mp!=null&&mp.isPlaying())
{
mp.stop();
}
}
});
}
#Override
public void onUserNoActions(QBRTCSession qbrtcSession, Integer integer) {
Toast.makeText(this, "no action by user", Toast.LENGTH_SHORT).show();
if(mp!=null&&mp.isPlaying())
{
mp.stop();
}
}
#Override
public void onSessionStartClose(QBRTCSession qbrtcSession) {
qbrtcSession.addVideoTrackCallbacksListener(this);
try {
qbrtcSession.getMediaStreamManager().setVideoCapturer(new QBRTCCameraVideoCapturer(this, null));
mediaStreamManager = qbrtcSession.getMediaStreamManager();
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onUserNotAnswer(QBRTCSession qbrtcSession, Integer integer) {
Toast.makeText(this, "No answer", Toast.LENGTH_SHORT).show();
if(mp!=null&&mp.isPlaying())
{
mp.stop();
}
finish();
}
#Override
public void onCallRejectByUser(QBRTCSession qbrtcSession, Integer integer, Map<String, String> map) {
Toast.makeText(this, "Call rejected", Toast.LENGTH_SHORT).show();
if(mp!=null&&mp.isPlaying())
{
mp.stop();
}
finish();
}
#Override
public void onCallAcceptByUser(QBRTCSession qbrtcSession, Integer integer, Map<String, String> map) {
qbrtcSession.addVideoTrackCallbacksListener(this);
if(mp!=null&&mp.isPlaying())
{
mp.stop();
}
StartTimer();
}
#Override
public void onReceiveHangUpFromUser(QBRTCSession qbrtcSession, Integer integer, Map<String, String> map) {
if(mp!=null&&mp.isPlaying())
{
mp.stop();
}
Toast.makeText(this, "Call ended by user", Toast.LENGTH_SHORT).show();
finish();
}
#Override
public void onSessionClosed(QBRTCSession qbrtcSession) {
if(mp!=null&&mp.isPlaying())
{
mp.stop();
}
}
#Override
public void onLocalVideoTrackReceive(QBRTCSession qbrtcSession, QBRTCVideoTrack qbrtcVideoTrack) {
Log.i("errorCheck", "WW1: " + qbrtcSession);
qbrtcVideoTrack.addRenderer(new VideoRenderer(surfaceView));
surfaceView.setMirror(true);
surfaceView.requestLayout();
}
#Override
public void onRemoteVideoTrackReceive(QBRTCSession qbrtcSession, QBRTCVideoTrack qbrtcVideoTrack, Integer integer) {
Log.i("errorCheck", "WW2: " + qbrtcSession);
qbrtcVideoTrack.addRenderer(new VideoRenderer(remoteview));
mediaStreamManager = qbrtcSession.getMediaStreamManager();
remoteview.requestLayout();
}
public void StartDialRinging(){
try {
mp = MediaPlayer.create(getApplicationContext(), R.raw.beep);
mp.setLooping(true);
mp.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
VideoCallService
public class VideoCallService extends Service implements QBRTCClientSessionCallbacks {
private Timer mTimer = null;
private Handler mHandler = new Handler();
public static final int notify = 300000;
public VideoCallService() {
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
Log.wtf("service", "start");
if (mTimer != null) {
Log.wtf("service", "All ready started");
} else {
mTimer = new Timer(); //recreate new
mTimer.scheduleAtFixedRate(new TimeDisplay(), 0, notify);
LoginChatService();
ProcessCalls();
InitSignalling();
QBRTCClient.getInstance(this).addSessionCallbacksListener(this);
}
}catch (Exception e){
Log.wtf("ex",""+e);
}
return START_NOT_STICKY;
}
private void InitSignalling() {
QBChatService.getInstance().getVideoChatWebRTCSignalingManager()
.addSignalingManagerListener(new QBVideoChatSignalingManagerListener() {
#Override
public void signalingCreated(QBSignaling qbSignaling, boolean createdLocally) {
if (!createdLocally) {
QBRTCClient.getInstance(VideoCallService.this).addSignaling((QBWebRTCSignaling) qbSignaling);
}
}
});
}
private void ProcessCalls() {
QBRTCClient.getInstance(this).prepareToProcessCalls();
}
#Override
public void onReceiveNewSession(QBRTCSession qbrtcSession) {
//DataHolder.getInstance().setServiceData(qbrtcSession,qbrtcSession.getUserInfo());
startActivity(new Intent(this,VideoActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra("service",true));
}
}
Manifest
<service
android:name=".MyFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<service
android:name=".MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
<service android:name=".VideoCallService" />
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.quickblox.sample.groupchatwebrtc" />
</intent-filter>
</receiver>
<service
android:name=".GcmPushListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
<service
android:name="com.quickblox.messages.services.gcm.QBGcmPushInstanceIDService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID" />
</intent-filter>
</service>
<meta-data android:name="com.quickblox.messages.TYPE" android:value="GCM" />
<meta-data android:name="com.quickblox.messages.SENDER_ID" android:value="1" />
<meta-data android:name="com.quickblox.messages.QB_ENVIRONMENT" android:value="DEVELOPMENT" />
After start Call, session created for opponent
This my log for user caller
I/org.webrtc.Logging: SurfaceViewRenderer: localView: onMeasure(). New size: 200x200
I/org.webrtc.Logging: EglRenderer: localView: setLayoutAspectRatio: 1.0
I/org.webrtc.Logging: EglRenderer: opponentView: setLayoutAspectRatio: 0.58536583
D/QBASDK: register network listener
D/QBASDK: Connected. Login to chat, currentUser JID: 39753009-65649, resource: android_ffffffff-ac04-0c2d-ffff-ffffd173e4c0
I/errorCheck: R0: null
I/errorCheck: R1: Bundle[{}]
D/RTCClient.: Added session CALLBACK listenerapp.x.y.VideoActivity#4288e7f8
I/errorCheck: chatService1: com.quickblox.chat.QBChatService#42f6b478
D/RTCClient.: Call createNewSessionWithOpponents[39753771]conference type=QBConferenceType{value='1'}
D/RTCClient.QBRTCSession: Create new session
I/errorCheck: Q1: com.quickblox.chat.QBWebRTCSignaling#43156ce0
I/errorCheck: Q2: true
D/RTCClient.: New signalling was added for participant39753771
D/RTCClient.QBRTCSession: Make new channel for oppoennt:39753771com.quickblox.videochat.webrtc.QBPeerChannel#42f23c00
D/RTCClient.QBRTCSession: isInitiator=true
D/RTCClient.QBRTCSession: ADD VideoTrackCallbacksListener app.x.y.VideoActivity#4288e7f8
D/RTCClient.QBRTCSession: startCall
D/RTCClient.PeerFactoryManager: Creating Peer connection factory
D/RTCClient.QBRTCSession.SessionWaitingTimers: Stop WaitTimer
But for opponent not set log ( not incoming notification or ... )
How can I solve this problem?
You may refer with this thread. Check the video-sample. There is implementation of opening IncomingCallFragment by receiving push in this sample. Also, if you have successful subscription, but do not receive pushes, you always can check the following:
Check certificates for pushes: Admin panel ->Push Notification ->Settings -> APNS, GCM etc.
Send push from Admin Panel - > Push notifications - > Send (link)
Also you can check Push Notifications Queue tab in Admin panel, there should be a log of your push. It will help you to understand what's going on with your devices.
Hope this helps!
I have a connection with SIP server(Asterisk), but micro and dynamics doesn't work. Asterisk kicks client in 31 seconds(Empty RTP thread). Documentation by Google says:
Android provides an API that supports the Session Initiation Protocol (SIP). This lets you add SIP-based internet telephony features to your applications. Android includes a full SIP protocol stack and integrated call management services that let applications easily set up outgoing and incoming voice calls, without having to manage sessions, transport-level communication, or audio record or playback directly.
Activity + Receiver:
public class MainActivity extends AppCompatActivity {
public String domain = "192.168.10.37";
public String name = "111";
public String password = "123456";
public String sipAddress = "100#192.168.10.37";
public IncomingCallReceiver receiver;
public SipManager sipManager;
public SipProfile sipProfile;
public SipAudioCall call;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.USE_SIP)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.USE_SIP)) {
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.USE_SIP},
0);
}
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("zlotnikov.SIPexample.INCOMING_CALL");
receiver = new IncomingCallReceiver();
this.registerReceiver(receiver, intentFilter);
initManager();
}
private void initManager() {
if (sipManager == null) {
sipManager = SipManager.newInstance(this);
}
}
private void initializeLocalProfile() {
if (sipProfile != null) {
closeLocalProfile();
}
try {
SipProfile.Builder builder = new SipProfile.Builder(name, domain);
builder.setPassword(password);
builder.setSendKeepAlive(true);
builder.setAutoRegistration(true);
sipProfile = builder.build();
Intent intent = new Intent();
intent.setAction("zlotnikov.SIPexample.INCOMING_CALL");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
SipRegistrationListener listener = new SipRegistrationListener() {
#Override
public void onRegistering(String s) {
System.out.println("voip onRegistering " + s);
}
#Override
public void onRegistrationDone(String s, long l) {
System.out.println("voip onRegistrationDone " + s + " " + l);
//initCall();
}
#Override
public void onRegistrationFailed(String s, int i, String s1) {
System.out.println("voip onRegistrationFailed " + s);
}
};
sipManager.open(sipProfile, pendingIntent, null);
//sipManager.register(sipProfile, 40, listener);
sipManager.setRegistrationListener(sipProfile.getUriString(), listener);
} catch (ParseException e) {
e.printStackTrace();
} catch (SipException e) {
e.printStackTrace();
}
}
#Override
protected void onStart() {
super.onStart();
initializeLocalProfile();
}
#Override
protected void onStop() {
super.onStop();
closeLocalProfile();
}
private void closeLocalProfile() {
try {
if (sipProfile != null) {
sipManager.close(sipProfile.getUriString());
}
} catch (Exception ee) {
ee.printStackTrace();
}
}
private void initCall() {
try {
SipAudioCall.Listener listener = new SipAudioCall.Listener() {
#Override
public void onCallEstablished(SipAudioCall call) {
super.onCallEstablished(call);
System.out.println("voip onCallEstablished");
call.startAudio();
call.setSpeakerMode(true);
}
#Override
public void onCallEnded(SipAudioCall call) {
super.onCallEnded(call);
System.out.println("voip onCallEnded");
}
};
call = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, listener, 30);
} catch (SipException e) {
closeLocalProfile();
call.close();
e.printStackTrace();
System.out.println("voip MainActivity Конец соединения");
}
}
public class IncomingCallReceiver extends BroadcastReceiver {
private MediaPlayer mediaPlayer;
#Override
public void onReceive(Context context, Intent intent) {
SipAudioCall incomingCall = null;
System.out.println("voip Пришел звонок " + intent.toString());
try {
SipAudioCall.Listener listener = new SipAudioCall.Listener() {
#Override
public void onRinging(SipAudioCall call, SipProfile caller) {
System.out.println("voip onRinging()");
try {
startRinging();
} catch (Exception e) {
stopRinging();
System.out.println("voip onRinging exception");
e.printStackTrace();
}
}
#Override
public void onCallEstablished(SipAudioCall call) {
super.onCallEstablished(call);
System.out.println("voip onCallEstablished()");
stopRinging();
}
#Override
public void onCallEnded(SipAudioCall call) {
super.onCallEnded(call);
System.out.println("voip onCallEnded()");
}
};
incomingCall = sipManager.takeAudioCall(intent, listener);
incomingCall.startAudio();
incomingCall.setSpeakerMode(true);
/*if (incomingCall.isMuted()) {
incomingCall.toggleMute();
}*/
//call = incomingCall;
incomingCall.answerCall(30);
} catch (Exception e) {
if (incomingCall != null) {
incomingCall.close();
System.out.println("voip IncomingCallReceiver конец соединения");
}
}
}
private synchronized void startRinging() {
long[] pattern = {0, 1000, 1000};
((Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE)).vibrate(pattern, 0);
Uri defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(), RingtoneManager.TYPE_RINGTONE);
mediaPlayer = MediaPlayer.create(getApplicationContext(), defaultRingtoneUri);
mediaPlayer.setLooping(true);
mediaPlayer.start();
}
private synchronized void stopRinging() {
((Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE))
.cancel();
if (mediaPlayer != null || mediaPlayer.isPlaying()) mediaPlayer.stop();
}
}
}
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="zlotnikov.sipexample">
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.sip.voip" />
<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"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".MainActivity$IncomingCallReceiver" android:label="Call Receiver" />
</application>
I am trying to send a message from my mobile app to a service in the wearable app.
The message needs to be send from a BroadcastReceiver. In the BroadcastReceiver the message is sent successfully, at least that is what is said in the log, but the method onMessageReceive is not triggered.
I am not quite sure what the problem might be, I have the same applicationId in both build.gradle files, but packages names are different.
In mobile manifest the receiver has the following declaration:
<receiver android:name="com.app.mobile.NewAlarm"
android:exported="false">
<intent-filter>
<action android:name="com.example.android.support.wearable.notifications.ACTION_EXAMPLE" />
</intent-filter>
</receiver>
the BroadcastReceiver class:
public class TimeAlarm extends BroadcastReceiver {
public static final String TAP_ACTION_PATH = "/tap";
private static final String TAG = "NotificationReceiver";
public void onReceive(Context context, Intent paramIntent) {
mGoogleApiClient = new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
}
#Override
public void onConnectionSuspended(int cause) {
}
}).build();
mGoogleApiClient.connect();
sendMessage();
if (mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}
private void getNodes() {
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback(
new ResultCallback<NodeApi.GetConnectedNodesResult>() {
#Override
public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
HashSet<String> results = new HashSet<String>();
for (Node node : getConnectedNodesResult.getNodes()) {
results.add(node.getId());
Log.d(TAG,node.getId().toString());
}
Log.d(TAG,results.toString());
sendMessageApi(results);
}
}
);
}
private void sendMessageApi(Collection<String> nodes) {
for (String node : nodes) {
Wearable.MessageApi.sendMessage(
mGoogleApiClient, node, TAP_ACTION_PATH, null).setResultCallback(
new ResultCallback<MessageApi.SendMessageResult>() {
#Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
if (!sendMessageResult.getStatus().isSuccess()) {
Log.e(TAG, "ERROR: failed to send Message: " + sendMessageResult.getStatus());
} else {
Log.e(TAG, "Success!");
}
}
}
);
}
}
private void sendMessage() {
getNodes();
}
}
The WearableListenerService is declared this way in wear app manifest:
<service android:name=".DataLayerListenerService" >
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
and finally the DataLayerListenerService:
public class DataLayerListenerService extends WearableListenerService {
public static final String START_ACTIVITY_PATH = "/notice";
public static final String TAP_ACTION_PATH = "/tap";
private static final String TAG = "DataLayerListenerService";
#Override
public void onMessageReceived(MessageEvent messageEvent) {
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
Log.d(TAG, "Message Received !!");
if (START_ACTIVITY_PATH.equals(messageEvent.getPath())) {
Log.d(TAG, "Message Received !!");
int vibeTime = 5000;
String strVibeTime = new String(messageEvent.getData());
if (!strVibeTime.equals("") && !strVibeTime.equals("0")) {
vibeTime = (Integer.parseInt(strVibeTime)) * 1000;
}
sleep(1000);
vibrator.vibrate(vibeTime);
} else if (TAP_ACTION_PATH.equals(messageEvent.getPath())) {
Log.d(TAG, "Tapping Received !!");
vibrator.cancel();
}
}
public synchronized void sleep(long msec) {
try
{
wait(msec);
}catch(InterruptedException e){
Log.e(TAG, e.getMessage());
}
}
}
You're trying to send the message before the client is connected, so getConnectedNodesResult.getNodes() is returning an empty list.
Try this instead:
public void onReceive(Context context, Intent paramIntent) {
mGoogleApiClient = new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
sendMessage();
}
#Override
public void onConnectionSuspended(int cause) {
}
}).build();
mGoogleApiClient.connect();
}