Can respond to incoming calls.

This commit is contained in:
Pierre HUBERT 2019-02-25 15:46:55 +01:00
parent 6b3d46f04f
commit 8b6c64fd2a
14 changed files with 482 additions and 35 deletions

View File

@ -74,16 +74,30 @@
<!-- Call activity -->
<activity
android:name=".ui.activities.CallActivity"
android:label="@string/activity_call_label"/>
android:label="@string/activity_call_label" />
<!-- Incoming call activity -->
<activity android:name=".ui.activities.IncomingCallActivity" />
<!-- New calls available receiver -->
<receiver android:name=".ui.receivers.PendingCallsBroadcastReceiver" android:exported="false">
<receiver
android:name=".ui.receivers.PendingCallsBroadcastReceiver"
android:exported="false">
<intent-filter>
<action android:name="org.communiquons.android.comunic.client.NEW_CALLS_AVAILABLE" />
</intent-filter>
</receiver>
<!-- Reject new call receiver -->
<receiver
android:name=".ui.receivers.RejectCallReceiver"
android:exported="false">
<intent-filter>
<action android:name="org.communiquons.android.comunic.client.REJECT_INCOMING_CALL" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -9,6 +9,7 @@ import org.communiquons.android.comunic.client.data.models.APIRequest;
import org.communiquons.android.comunic.client.data.models.APIResponse;
import org.communiquons.android.comunic.client.data.models.CallInformation;
import org.communiquons.android.comunic.client.data.models.CallMember;
import org.communiquons.android.comunic.client.data.models.CallResponse;
import org.communiquons.android.comunic.client.data.models.CallsConfiguration;
import org.communiquons.android.comunic.client.data.models.NextPendingCallInformation;
import org.json.JSONArray;
@ -171,6 +172,26 @@ public class CallsHelper extends BaseHelper {
return name;
}
/**
* Respond to a call request
*
* @param response The response to the call
* @return TRUE for a success / FALSE else
*/
public boolean respondToCall(@NonNull CallResponse response){
APIRequest request = new APIRequest(getContext(), "calls/respond");
request.addInt("call_id", response.getCallID());
request.addBoolean("accept", response.isAccept());
try {
return request.exec().getJSONObject().has("success");
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Turn a {@link JSONObject} object into a {@link CallsConfiguration} object.

View File

@ -0,0 +1,34 @@
package org.communiquons.android.comunic.client.data.models;
/**
* Response of a user to a call
*
* @author Pierre HUBERT
*/
public class CallResponse {
//Private fields
private int callID;
private boolean accept;
public CallResponse(int callID, boolean accept) {
this.callID = callID;
this.accept = accept;
}
public int getCallID() {
return callID;
}
public void setCallID(int callID) {
this.callID = callID;
}
public boolean isAccept() {
return accept;
}
public void setAccept(boolean accept) {
this.accept = accept;
}
}

View File

@ -72,6 +72,13 @@ public final class Constants {
public static final String ACTION_NOTIFY_NEW_CALLS_AVAILABLE =
"org.communiquons.android.comunic.client.NEW_CALLS_AVAILABLE";
/**
* Intent used to reject incoming call
*/
public static final String ACTION_REJECT_INCOMING_CALL =
"org.communiquons.android.comunic.client.REJECT_INCOMING_CALL";
}
/**

View File

@ -1,8 +1,8 @@
package org.communiquons.android.comunic.client.ui.activities;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
@ -20,6 +20,12 @@ public abstract class BaseActivity extends AppCompatActivity {
*/
private SafeAsyncTasksManager mSafeAsyncTasksManager = null;
/**
* Current active class
*/
@Nullable
private static String mActiveActivityName = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -36,6 +42,22 @@ public abstract class BaseActivity extends AppCompatActivity {
mSafeAsyncTasksManager.unsetAllTasks();
}
@Override
protected void onStart() {
super.onStart();
mActiveActivityName = getClass().getSimpleName();
}
@Override
protected void onStop() {
super.onStop();
if(getClass().getSimpleName().equals(mActiveActivityName))
mActiveActivityName = null;
}
@NonNull
@Override
public ActionBar getSupportActionBar() {
@ -51,4 +73,14 @@ public abstract class BaseActivity extends AppCompatActivity {
public SafeAsyncTasksManager getTasksManager() {
return mSafeAsyncTasksManager;
}
/**
* Check out whether an activity is the active activity or not
*
* @param activity The activity to check
* @return True if the activity is the active one / false else
*/
public static boolean IsActiveActivity(Class<? extends BaseActivity> activity){
return activity.getSimpleName().equals(mActiveActivityName);
}
}

View File

@ -1,7 +1,6 @@
package org.communiquons.android.comunic.client.ui.activities;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import org.communiquons.android.comunic.client.R;
@ -14,7 +13,7 @@ import java.util.Objects;
*
* @author Pierre HUBERT
*/
public class CallActivity extends AppCompatActivity {
public class CallActivity extends BaseActivity {
/**
* Mandatory argument that includes call id
@ -29,9 +28,6 @@ public class CallActivity extends AppCompatActivity {
//Hide call bar
Objects.requireNonNull(getSupportActionBar()).hide();
((TextView)findViewById(R.id.call_id)).setText(
"Call " + getIntent().getExtras().getInt(ARGUMENT_CALL_ID));
}
@Override
@ -40,5 +36,8 @@ public class CallActivity extends AppCompatActivity {
//Hide call notifications
PendingCallsBroadcastReceiver.RemoveCallNotification(this);
((TextView)findViewById(R.id.call_id)).setText(
"Call " + getIntent().getExtras().getInt(ARGUMENT_CALL_ID));
}
}

View File

@ -0,0 +1,159 @@
package org.communiquons.android.comunic.client.ui.activities;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.communiquons.android.comunic.client.R;
import org.communiquons.android.comunic.client.data.models.NextPendingCallInformation;
import org.communiquons.android.comunic.client.data.utils.AccountUtils;
import org.communiquons.android.comunic.client.ui.Constants;
import org.communiquons.android.comunic.client.ui.asynctasks.GetNextPendingCallTask;
import org.communiquons.android.comunic.client.ui.asynctasks.SafeAsyncTask;
import org.communiquons.android.comunic.client.ui.receivers.PendingCallsBroadcastReceiver;
import org.communiquons.android.comunic.client.ui.receivers.RejectCallReceiver;
/**
* Incoming call activity
*
* @author Pierre HUBERT
*/
public class IncomingCallActivity extends BaseActivity implements SafeAsyncTask.OnPostExecuteListener<NextPendingCallInformation>, View.OnClickListener {
private NextPendingCallInformation mNextPendingCallInformation;
private RefreshThread mRefreshThread = null;
private Button mAcceptButton;
private Button mRejectButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_incoming_call);
getSupportActionBar().hide();
PendingCallsBroadcastReceiver.RemoveCallNotification(this);
mAcceptButton = findViewById(R.id.accept_button);
mRejectButton = findViewById(R.id.reject_button);
if(IsActiveActivity(IncomingCallActivity.class))
finish();
}
@Override
protected void onStart() {
super.onStart();
mRefreshThread = new RefreshThread();
mRefreshThread.start();
}
@Override
protected void onStop() {
super.onStop();
mRefreshThread.interrupt();
}
private void getNextPendingCall() {
//Get next pending call
GetNextPendingCallTask getNextPendingCallTask = new GetNextPendingCallTask(getApplicationContext());
getNextPendingCallTask.setOnPostExecuteListener(this);
getNextPendingCallTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
getTasksManager().addTask(getNextPendingCallTask);
}
@Override
public void OnPostExecute(@Nullable NextPendingCallInformation nextPendingCallInformation) {
if(nextPendingCallInformation == null){
Toast.makeText(this, R.string.err_get_next_pending_call_info, Toast.LENGTH_SHORT).show();
return;
}
if(!nextPendingCallInformation.isHasPendingCall()
|| nextPendingCallInformation.hasAllMembersLeftCallExcept(AccountUtils.getID(this)))
finish();
this.mNextPendingCallInformation = nextPendingCallInformation;
((TextView)findViewById(R.id.call_title)).setText(nextPendingCallInformation.getCallName());
mAcceptButton.setOnClickListener(this);
mRejectButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
//Accept call
if(v.equals(mAcceptButton)){
Intent intent = new Intent(this, CallActivity.class);
intent.putExtra(CallActivity.ARGUMENT_CALL_ID, mNextPendingCallInformation.getId());
startActivity(intent);
finish();
}
//Reject calls
if(v.equals(mRejectButton)){
Intent intent = new Intent(this, RejectCallReceiver.class);
intent.setAction(Constants.IntentActions.ACTION_REJECT_INCOMING_CALL);
intent.putExtra(RejectCallReceiver.ARGUMENT_CALL_ID, mNextPendingCallInformation.getId());
sendBroadcast(intent);
finish();
}
}
/**
* Class used to refresh call information
*/
private class RefreshThread extends Thread {
private boolean stop = false;
private final Object o = new Object();
@Override
public void run() {
super.run();
while(!stop){
//Execute task
runOnUiThread(new Runnable() {
@Override
public void run() {
getNextPendingCall();
}
});
synchronized (o) {
//Looping
try {
o.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
interrupt();
}
}
}
}
public void interrupt(){
stop = true;
}
}
}

View File

@ -0,0 +1,23 @@
package org.communiquons.android.comunic.client.ui.asynctasks;
import android.content.Context;
import org.communiquons.android.comunic.client.data.helpers.CallsHelper;
import org.communiquons.android.comunic.client.data.models.CallResponse;
/**
* Respond to call task
*
* @author Pierre HUBERT
*/
public class RespondToCallTask extends SafeAsyncTask<CallResponse, Void, Boolean> {
public RespondToCallTask(Context context) {
super(context);
}
@Override
protected Boolean doInBackground(CallResponse... callResponses) {
return new CallsHelper(getContext()).respondToCall(callResponses[0]);
}
}

View File

@ -18,7 +18,9 @@ import org.communiquons.android.comunic.client.R;
import org.communiquons.android.comunic.client.data.models.NextPendingCallInformation;
import org.communiquons.android.comunic.client.data.utils.AccountUtils;
import org.communiquons.android.comunic.client.ui.Constants;
import org.communiquons.android.comunic.client.ui.activities.BaseActivity;
import org.communiquons.android.comunic.client.ui.activities.CallActivity;
import org.communiquons.android.comunic.client.ui.activities.IncomingCallActivity;
import org.communiquons.android.comunic.client.ui.asynctasks.GetNextPendingCallTask;
import org.communiquons.android.comunic.client.ui.asynctasks.SafeAsyncTask;
import org.communiquons.android.comunic.client.ui.utils.UiUtils;
@ -50,6 +52,11 @@ public class PendingCallsBroadcastReceiver extends BroadcastReceiver {
*/
private boolean locked = false;
/**
* The ID of the last shown notification
*/
private static int mLastNotificationCallID = 0;
@Override
public void onReceive(final Context context, Intent intent) {
@ -61,6 +68,12 @@ public class PendingCallsBroadcastReceiver extends BroadcastReceiver {
throw new RuntimeException("Unexpected call of " + TAG);
//Check if user is already calling
if(CheckIfUseless(context, null)) {
Log.v(TAG, "Reported that a new call is available but skipped because considered as useless.");
return;
}
//Check if service is currently locked
if(locked) {
Log.e(TAG, "New call skipped because this class is locked");
@ -93,29 +106,20 @@ public class PendingCallsBroadcastReceiver extends BroadcastReceiver {
return;
}
//Check if there is no pending call
if(!info.isHasPendingCall()) {
//Remove any related notification
RemoveCallNotification(context);
//Check if it is useless to continue
if(CheckIfUseless(context, info)) {
return;
}
//Check if all notification members left the call
if(info.hasAllMembersLeftCallExcept(AccountUtils.getID(context)))
return;
//Create notification
//Accept intent
Intent acceptIntent = new Intent(context, CallActivity.class);
acceptIntent.putExtra(CallActivity.ARGUMENT_CALL_ID, info.getId());
PendingIntent pendingAcceptIntent
= PendingIntent.getActivity(context, 0, acceptIntent, 0);
//Full screen call request intent
Intent fullScreenCallRequestIntent = new Intent(context, IncomingCallActivity.class);
PendingIntent pendingFullScreenRequestIntent = PendingIntent.getActivity(context,
0, fullScreenCallRequestIntent, 0);
//Create and show notification
NotificationCompat.Builder builder =
@ -123,14 +127,9 @@ public class PendingCallsBroadcastReceiver extends BroadcastReceiver {
.setSmallIcon(R.drawable.ic_call)
.setContentTitle(info.getCallName())
.setContentText(UiUtils.getString(context, R.string.notification_call_content, info.getCallName()))
.setContentIntent(pendingAcceptIntent)
.setFullScreenIntent(pendingAcceptIntent, true)
.setPriority(PRIORITY_HIGH)
//Accept action
.addAction(R.drawable.ic_call,
UiUtils.getString(context, R.string.notification_call_accept), pendingAcceptIntent);
//.addAction(R.drawable.ic_call, R.string.notification_call_reject, null)
//.setContentIntent(pendingAcceptIntent)
.setFullScreenIntent(pendingFullScreenRequestIntent, true)
.setPriority(PRIORITY_HIGH);
//Create notification channel if required
@ -146,7 +145,28 @@ public class PendingCallsBroadcastReceiver extends BroadcastReceiver {
Notification n = builder.build();
n.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
NotificationManagerCompat.from(context).notify(CALL_NOTIFICATION_ID, n);
mLastNotificationCallID = info.getId();
NotificationManagerCompat.from(context).notify(GetNotificationID(mLastNotificationCallID), n);
}
/**
* Check if calling this receiver is useless for now
*
* @param context Context of application
* @param call Optional information about a pending call
* @return TRUE if useless / FALSE else
*/
public static boolean CheckIfUseless(Context context, @Nullable NextPendingCallInformation call){
boolean useless = BaseActivity.IsActiveActivity(IncomingCallActivity.class)
|| BaseActivity.IsActiveActivity(CallActivity.class)
|| (call != null && (
!call.isHasPendingCall()
|| call.hasAllMembersLeftCallExcept(AccountUtils.getID(context))));
if(useless)
RemoveCallNotification(context);
return useless;
}
/**
@ -155,6 +175,15 @@ public class PendingCallsBroadcastReceiver extends BroadcastReceiver {
* @param context The context of the application
*/
public static void RemoveCallNotification(Context context){
NotificationManagerCompat.from(context).cancel(CALL_NOTIFICATION_ID);
NotificationManagerCompat.from(context).cancel(GetNotificationID(mLastNotificationCallID));
}
/**
* Get the ID of a notification for a call
*
* @param callID The ID of the target call
*/
private static int GetNotificationID(int callID){
return CALL_NOTIFICATION_ID*1000 + callID;
}
}

View File

@ -0,0 +1,49 @@
package org.communiquons.android.comunic.client.ui.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.util.Log;
import org.communiquons.android.comunic.client.data.models.CallResponse;
import org.communiquons.android.comunic.client.ui.asynctasks.RespondToCallTask;
import java.util.Objects;
import static org.communiquons.android.comunic.client.ui.Constants.IntentActions.ACTION_REJECT_INCOMING_CALL;
/**
* This broadcast receiver is used to reject incoming calls
*
* @author Pierre HUBERT
*/
public class RejectCallReceiver extends BroadcastReceiver {
/**
* Debug tag
*/
private static final String TAG = RejectCallReceiver.class.getSimpleName();
/**
* Mandatory argument that includes call id
*/
public static final String ARGUMENT_CALL_ID = "call_id";
@Override
public void onReceive(Context context, Intent intent) {
if(intent == null || !ACTION_REJECT_INCOMING_CALL.equals(intent.getAction()))
throw new RuntimeException(TAG + " was incorrectly triggered!");
int callID = Objects.requireNonNull(intent.getExtras()).getInt(ARGUMENT_CALL_ID);
//Send the response back to the server
Log.v(TAG, "Reject call " + callID);
RespondToCallTask respondToCallTask = new RespondToCallTask(context);
respondToCallTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new CallResponse(
callID,
false
));
}
}

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activities.IncomingCallActivity">
<TextView
android:id="@+id/call_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintBottom_toTopOf="@+id/linearLayout3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Call title" />
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/reject_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/holo_red_dark"
android:textColor="@android:color/white"
android:text="@string/button_reject_call"
tools:ignore="ButtonStyle" />
<Button
android:id="@+id/accept_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/holo_green_dark"
android:textColor="@android:color/white"
android:text="@string/button_accept_call"
tools:ignore="ButtonStyle" />
</LinearLayout>
<TextView
android:id="@+id/textView14"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="7dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="@string/title_incoming_call"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintBottom_toTopOf="@+id/call_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

View File

@ -325,4 +325,8 @@
<string name="notification_call_reject">Rejeter</string>
<string name="notification_call_content">%s vous appelle.</string>
<string name="action_accelerate_notifications_refresh">Accélérer le rafraîchissement des notifications</string>
<string name="button_reject_call">Rejeter</string>
<string name="button_accept_call">Répondre</string>
<string name="title_incoming_call">Appel entrant</string>
<string name="err_get_next_pending_call_info">Impossible de récupérer les informations de l\'appel en cours !</string>
</resources>

View File

@ -8,7 +8,7 @@
<color name="darker_gray">#aaa</color>
<color name="darker_darker_gray">#5b5b5b</color>
<color name="dark_blue">#303f9f</color>
<color name="holo_red_dark">#ffcc0000</color>
<color name="holo_red_dark">#cc0000</color>
<color name="default_drawable_color">#000000</color>

View File

@ -324,4 +324,8 @@
<string name="notification_call_reject">Reject call</string>
<string name="notification_call_content">%s is calling you</string>
<string name="action_accelerate_notifications_refresh">Accelerate notifications refresh</string>
<string name="button_reject_call">Reject call</string>
<string name="button_accept_call">Accept call</string>
<string name="title_incoming_call">Incoming call</string>
<string name="err_get_next_pending_call_info">Could not get pending call information!</string>
</resources>