恢复 activity 的通知已在后台使用 backpress

Notification to resume activity which has gone in background using backpress

我们正在使用 Sinch voip api。有一个在 app start 启动的绑定服务,我们在服务中初始化 sinch 客户端,它在后台总是 运行 。我尝试将通知代码放在呼叫屏幕 activity 中,因为此 activity 将始终显示接受呼叫。我的目标是能够像在 whatsapp 中一样单击通知并重新打开此调用 activity。

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
        }
    }
}

我的 SinchCallService class 是这样的:

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);
        }
    }
}

以下是我的 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;
    }

}

我试过设置

android:launchMode="singleTop"

如果我按下 CallScreenActivity 并单击通知,它会打开 MainAct 而不是 CallScreenActivity。如何让它打开CallScreenActivity?

我尝试了一种解决方案,即创建一个 stackbuilder 并将其传递给待定意图,如下所示:

TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(CallScreenActivity.class);
stackBuilder.addNextIntent(notificationIntent);
PendingIntent pendingIntent =
        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

并且还更改了清单如下:

<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>

但上述更改导致应用程序崩溃并出现以下错误:

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.access0(CallScreenActivity.java:51)
at com.app.activity.calls.CallScreenActivity.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)

因此,如果我使用没有 stackbuilder 的代码,然后按下主页按钮,然后按下通知,我的 CallSCreenACtivity 打开,但是如果我按下 CAllSCreenACtivity 内的后退按钮,然后按下主页按钮,然后按下通知,它打开主要活动而不是 CallSCreenACtivity。即使按下 callscreenactivty 上的后退按钮,我的通话仍然活跃。

我还注意到通知中显示的通话时长在 CallScreenActivity 上按下后停止刷新,但通话仍然有效。

应用程序流程:

来电者:- 当应用程序启动时,MainAct 启动,用户从那里点击某人的个人资料,然后被带到 ProfilesAct,在那里他按下呼叫按钮呼叫那个人,然后被带到 CallScreenActivity。

MainAct->ProfilesACt->CallScreenACt

对于接收者:- 应用程序在后台,来电,SinchCallService 使 IncomingCAllAct 显示,当他接受呼叫时,他被带到 CallScreenActivity。

IncomingCallAct->CallSCreeenACtivity

或者假设接收者已经在使用该应用程序,那么

any activity(could be chat activity, main acitivty etc)->IncomingCallAct->CallSCreeenACtivity 

应用清单:-

<?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>

mainfest中的CallScreenActivity不需要做成singletask或者singletop,只需要将callid(帮助Sinchapi识别具体调用),调用开始时间等调用参数保存在sharedpreferences 或 sqlite db,使通知意图如下:

    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);

只要我们传递正确的callid,我们就可以通过点击通知恢复通话。