diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ffa281d..e14b038 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -76,6 +76,14 @@ android:name=".ui.activities.CallActivity" android:label="@string/activity_call_label"/> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/CallsHelper.java b/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/CallsHelper.java index 61f92d2..851a540 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/CallsHelper.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/CallsHelper.java @@ -1,6 +1,7 @@ package org.communiquons.android.comunic.client.data.helpers; import android.content.Context; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import org.communiquons.android.comunic.client.data.enums.MemberCallStatus; @@ -9,6 +10,7 @@ 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.CallsConfiguration; +import org.communiquons.android.comunic.client.data.models.NextPendingCallInformation; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -106,7 +108,7 @@ public class CallsHelper extends BaseHelper { //Execute request APIResponse response = request.exec(); - return JSONObjectToCallInformation(response.getJSONObject()); + return JSONObjectToCallInformation(response.getJSONObject(), null); } catch (Exception e) { e.printStackTrace(); @@ -115,6 +117,60 @@ public class CallsHelper extends BaseHelper { } + /** + * Get the next pending call for a user + * + * @return Next pending call for a user + */ + @Nullable + public NextPendingCallInformation getNextPendingCall(){ + + APIRequest request = new APIRequest(getContext(), "calls/nextPending"); + + try { + JSONObject object = request.exec().getJSONObject(); + + //Check if there is no pending call available + NextPendingCallInformation call = new NextPendingCallInformation(); + if(object.has("notice")){ + call.setHasPendingCall(false); + return call; + } + + + call.setHasPendingCall(true); + JSONObjectToCallInformation(object, call); + return call; + + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + + } + + + /** + * Try to get and return call information + * + * @param call Target call information + * @return The name of call / null in case of failure + */ + @Nullable + public String getCallName(@NonNull CallInformation call){ + + //Get call name + String name = new ConversationsListHelper(getContext()) + .getConversationName(call.getConversationID()); + + if(name == null) + return null; + + call.setCallName(name); + return name; + } + /** * Turn a {@link JSONObject} object into a {@link CallsConfiguration} object. @@ -149,13 +205,18 @@ public class CallsHelper extends BaseHelper { * Turn a {@link JSONObject} into {@link CallInformation} object * * @param object object to convert + * @param call Call object to fill (null = none) * @return Generated CallInformation object * @throws JSONException in case of failure */ - private static CallInformation JSONObjectToCallInformation(JSONObject object) + private static CallInformation JSONObjectToCallInformation(JSONObject object, + @Nullable CallInformation call) throws JSONException { - CallInformation call = new CallInformation(); + //Check if object has to be instanced + if(call == null) + call = new CallInformation(); + call.setId(object.getInt("id")); call.setConversationID(object.getInt("conversation_id")); call.setLastActive(object.getInt("last_active")); diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/ConversationsListHelper.java b/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/ConversationsListHelper.java index 27e3905..c248e7b 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/ConversationsListHelper.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/ConversationsListHelper.java @@ -56,6 +56,15 @@ public class ConversationsListHelper { this.dbHelper = dbHelper; } + /** + * The constructor of the class + * + * @param context The context of execution of the application + */ + public ConversationsListHelper(Context context){ + this(context, DatabaseHelper.getInstance(context)); + } + /** * Get the list of conversation or null in case of failure * @@ -147,17 +156,17 @@ public class ConversationsListHelper { /** * Get the display name of a conversation * - * @param infos Informations about a conversation + * @param info Information about a conversation * @return The name of the conversation */ - public String getDisplayName(ConversationsInfo infos){ + public String getDisplayName(ConversationsInfo info){ //Check if a specific name has been specified - if(infos.hasName()) - return infos.getName(); + if(info.hasName()) + return info.getName(); //Get the list of members of the conversation - ArrayList members = infos.getMembers(); + ArrayList members = info.getMembers(); //Get the ID of the three first members ArrayList membersToGet = new ArrayList<>(); @@ -203,6 +212,24 @@ public class ConversationsListHelper { return name; } + /** + * Get the name of a conversation specified by its ID + * + * @param convID The ID of the target conversation + * @return The name of the conversation / null in case of failure + */ + @Nullable + public String getConversationName(int convID){ + + ConversationsInfo info = getInfosSingle(convID, true); + + if(info == null) + return null; + + return getDisplayName(info); + + } + /** * Delete a conversation specified by its ID * diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/models/CallInformation.java b/app/src/main/java/org/communiquons/android/comunic/client/data/models/CallInformation.java index 95d99e5..81aa245 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/models/CallInformation.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/models/CallInformation.java @@ -18,6 +18,9 @@ public class CallInformation { private ArrayList members = null; + private String callName = null; + + public int getId() { return id; } @@ -54,7 +57,29 @@ public class CallInformation { members.add(member); } + /** + * Check out whether all the members of the call left it or not except a specific user + * + * @return TRUE if all members except specified user ID left the call / FALSE else + */ + public boolean hasAllMembersLeftCallExcept(int userID){ + + for(CallMember member : members) + if(!member.leftCall() && member.getUserID() != userID) + return false; + + return true; + } + public void setMembers(ArrayList members) { this.members = members; } + + public String getCallName() { + return callName; + } + + public void setCallName(String callName) { + this.callName = callName; + } } diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/models/CallMember.java b/app/src/main/java/org/communiquons/android/comunic/client/data/models/CallMember.java index abaa1f2..6eb8db0 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/models/CallMember.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/models/CallMember.java @@ -56,4 +56,13 @@ public class CallMember { public void setStatus(MemberCallStatus status) { this.status = status; } + + /** + * Check out whether the member left the call or not + * + * @return TRUE if the user is considered as out of the conversation + */ + public boolean leftCall(){ + return status == MemberCallStatus.HANG_UP || status == MemberCallStatus.REJECTED; + } } diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/models/NextPendingCallInformation.java b/app/src/main/java/org/communiquons/android/comunic/client/data/models/NextPendingCallInformation.java new file mode 100644 index 0000000..233e25a --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/models/NextPendingCallInformation.java @@ -0,0 +1,21 @@ +package org.communiquons.android.comunic.client.data.models; + +/** + * Pending call information object + * + * @author Pierre HUBERT + */ +public class NextPendingCallInformation extends CallInformation { + + //Private fields + private boolean hasPendingCall; + + + public boolean isHasPendingCall() { + return hasPendingCall; + } + + public void setHasPendingCall(boolean hasPendingCall) { + this.hasPendingCall = hasPendingCall; + } +} diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/models/NotificationsCount.java b/app/src/main/java/org/communiquons/android/comunic/client/data/models/NotificationsCount.java index f57dac7..2d05be9 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/models/NotificationsCount.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/models/NotificationsCount.java @@ -1,7 +1,7 @@ package org.communiquons.android.comunic.client.data.models; /** - * Notifications count service + * Notifications count class * * @author Pierre HUBERT * Created by pierre on 4/9/18. @@ -13,7 +13,7 @@ public class NotificationsCount { private int notificationsCount; private int conversationsCount; private int friendsRequestsCount; - private int pendingCalls; + private int pendingCalls = -1; //Set and get notifications count diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/services/NotificationsService.java b/app/src/main/java/org/communiquons/android/comunic/client/data/services/NotificationsService.java index 553f45d..2a33272 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/services/NotificationsService.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/services/NotificationsService.java @@ -5,6 +5,7 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Intent; +import android.content.IntentFilter; import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; @@ -15,14 +16,15 @@ import android.util.Log; import org.communiquons.android.comunic.client.R; import org.communiquons.android.comunic.client.data.helpers.AccountHelper; import org.communiquons.android.comunic.client.data.helpers.CallsHelper; -import org.communiquons.android.comunic.client.data.models.NotificationsCount; import org.communiquons.android.comunic.client.data.helpers.NotificationsHelper; +import org.communiquons.android.comunic.client.data.models.NotificationsCount; import org.communiquons.android.comunic.client.data.utils.PreferencesUtils; import org.communiquons.android.comunic.client.ui.activities.MainActivity; - -import java.util.Objects; +import org.communiquons.android.comunic.client.ui.receivers.PendingCallsBroadcastReceiver; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static org.communiquons.android.comunic.client.ui.Constants.IntentActions.ACTION_NOTIFY_NEW_CALLS_AVAILABLE; +import static org.communiquons.android.comunic.client.ui.Constants.Notifications.MAIN_NOTIFICATION_ID; import static org.communiquons.android.comunic.client.ui.Constants.NotificationsChannels.GLOBAL_CHANNEL_DESCRIPTION; import static org.communiquons.android.comunic.client.ui.Constants.NotificationsChannels.GLOBAL_CHANNEL_ID; import static org.communiquons.android.comunic.client.ui.Constants.NotificationsChannels.GLOBAL_CHANNEL_NAME; @@ -54,11 +56,6 @@ public class NotificationsService extends IntentService { public static final String BROADCAST_EXTRA_UNREAD_CONVERSATIONS = "UnreadConversations"; public static final String BROADCAST_EXTRA_NUMBER_FRIENDSHIP_REQUESTS = "NumberFriendsRequests"; - /** - * Main notification ID - */ - private final static int MAIN_NOTIFICATION_ID = 0; - /** * Keep run status */ @@ -81,6 +78,13 @@ public class NotificationsService extends IntentService { */ public NotificationsService(){ super("NotificationsService"); + + //Register calls broadcast register + IntentFilter callFilters = new IntentFilter(); + callFilters.addAction(ACTION_NOTIFY_NEW_CALLS_AVAILABLE); + LocalBroadcastManager.getInstance(this) + .registerReceiver(new PendingCallsBroadcastReceiver(), callFilters); + } /** @@ -161,7 +165,22 @@ public class NotificationsService extends IntentService { .putExtra(BROADCAST_EXTRA_UNREAD_CONVERSATIONS, count.getConversationsCount()) .putExtra(BROADCAST_EXTRA_NUMBER_FRIENDSHIP_REQUESTS, count.getFriendsRequestsCount()); + //Send new calls information LocalBroadcastManager.getInstance(this).sendBroadcast(pushIntent); + + //If new calls are available, notify system + if(CallsHelper.IsCallSystemAvailable()){ + + if(count.hasPendingCalls()) { + Intent callIntent = new Intent(this, PendingCallsBroadcastReceiver.class); + callIntent.setAction(ACTION_NOTIFY_NEW_CALLS_AVAILABLE); + LocalBroadcastManager.getInstance(this).sendBroadcast(callIntent); + } + else + PendingCallsBroadcastReceiver.RemoveCallNotification(this); + + } + } Log.v(TAG, "Stop service"); @@ -193,6 +212,7 @@ public class NotificationsService extends IntentService { //Get notification manager to push notification NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(MAIN_NOTIFICATION_ID, mBuilder.build()); + } /** diff --git a/app/src/main/java/org/communiquons/android/comunic/client/ui/Constants.java b/app/src/main/java/org/communiquons/android/comunic/client/ui/Constants.java index 97cdb19..0d1d374 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/ui/Constants.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/ui/Constants.java @@ -1,7 +1,5 @@ package org.communiquons.android.comunic.client.ui; -import android.app.NotificationManager; - /** * UI constants * @@ -63,6 +61,19 @@ public final class Constants { } + /** + * Intents actions + */ + public final class IntentActions { + + /** + * Intent used to notify of new available calls + */ + public static final String ACTION_NOTIFY_NEW_CALLS_AVAILABLE = + "org.communiquons.android.comunic.client.NEW_CALLS_AVAILABLE"; + + } + /** * Notifications channels */ @@ -75,6 +86,13 @@ public final class Constants { public static final String GLOBAL_CHANNEL_NAME = "MainNotificationChannel"; public static final String GLOBAL_CHANNEL_DESCRIPTION = "Global Comunic notifications"; + + /** + * Call channel information + */ + public static final String CALL_CHANNEL_ID = "CallChannel"; + public static final String CALL_CHANNEL_NAME = "Call Notification Channel"; + public static final String CALL_CHANNEL_DESCRIPTION = "Channel used to notify incoming calls"; } /** @@ -87,5 +105,11 @@ public final class Constants { */ public static final int MAIN_NOTIFICATION_ID = 0; + + /** + * Call notification ID + */ + public static final int CALL_NOTIFICATION_ID = 1; + } } diff --git a/app/src/main/java/org/communiquons/android/comunic/client/ui/activities/CallActivity.java b/app/src/main/java/org/communiquons/android/comunic/client/ui/activities/CallActivity.java index 19f92da..5a3b799 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/ui/activities/CallActivity.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/ui/activities/CallActivity.java @@ -1,10 +1,11 @@ package org.communiquons.android.comunic.client.ui.activities; -import android.support.v7.app.AppCompatActivity; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import android.widget.TextView; import org.communiquons.android.comunic.client.R; +import org.communiquons.android.comunic.client.ui.receivers.PendingCallsBroadcastReceiver; import java.util.Objects; @@ -32,4 +33,12 @@ public class CallActivity extends AppCompatActivity { ((TextView)findViewById(R.id.call_id)).setText( "Call " + getIntent().getExtras().getInt(ARGUMENT_CALL_ID)); } + + @Override + protected void onResume() { + super.onResume(); + + //Hide call notifications + PendingCallsBroadcastReceiver.RemoveCallNotification(this); + } } diff --git a/app/src/main/java/org/communiquons/android/comunic/client/ui/asynctasks/GetNextPendingCallTask.java b/app/src/main/java/org/communiquons/android/comunic/client/ui/asynctasks/GetNextPendingCallTask.java new file mode 100644 index 0000000..3ad4a68 --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/ui/asynctasks/GetNextPendingCallTask.java @@ -0,0 +1,32 @@ +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.NextPendingCallInformation; + +/** + * Get next pending call task + * + * @author Pierre HUBERT + */ +public class GetNextPendingCallTask extends SafeAsyncTask { + + public GetNextPendingCallTask(Context context) { + super(context); + } + + @Override + protected NextPendingCallInformation doInBackground(Void... voids) { + + CallsHelper callsHelper = new CallsHelper(getContext()); + NextPendingCallInformation call = callsHelper.getNextPendingCall(); + + //Load call name if possible + if(call == null || (call.isHasPendingCall() && callsHelper.getCallName(call) == null)) + return null; + + return call; + } + +} diff --git a/app/src/main/java/org/communiquons/android/comunic/client/ui/receivers/PendingCallsBroadcastReceiver.java b/app/src/main/java/org/communiquons/android/comunic/client/ui/receivers/PendingCallsBroadcastReceiver.java new file mode 100644 index 0000000..8c847da --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/ui/receivers/PendingCallsBroadcastReceiver.java @@ -0,0 +1,160 @@ +package org.communiquons.android.comunic.client.ui.receivers; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; +import android.util.Log; + +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.CallActivity; +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; + +import java.util.Objects; + +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.support.v4.app.NotificationCompat.PRIORITY_HIGH; +import static org.communiquons.android.comunic.client.ui.Constants.Notifications.CALL_NOTIFICATION_ID; +import static org.communiquons.android.comunic.client.ui.Constants.NotificationsChannels.CALL_CHANNEL_DESCRIPTION; +import static org.communiquons.android.comunic.client.ui.Constants.NotificationsChannels.CALL_CHANNEL_ID; + +/** + * Receiver for pending calls + * + * This receiver get calls when new calls are reported to be available for the user + * + * @author Pierre HUBERT + */ +public class PendingCallsBroadcastReceiver extends BroadcastReceiver { + + /** + * Debug tag + */ + private static final String TAG = PendingCallsBroadcastReceiver.class.getSimpleName(); + + /** + * This variable is set to true if another call is being processed + */ + private boolean locked = false; + + + @Override + public void onReceive(final Context context, Intent intent) { + + //Check if the intent is valid + if(intent == null + || !Objects.equals(intent.getAction(), + Constants.IntentActions.ACTION_NOTIFY_NEW_CALLS_AVAILABLE)) + throw new RuntimeException("Unexpected call of " + TAG); + + + //Check if service is currently locked + if(locked) { + Log.e(TAG, "New call skipped because this class is locked"); + return; + } + locked = true; + + //Get next pending notification + GetNextPendingCallTask task = new GetNextPendingCallTask(context); + task.setOnPostExecuteListener(new SafeAsyncTask.OnPostExecuteListener() { + @Override + public void OnPostExecute(NextPendingCallInformation nextPendingCallInformation) { + locked = false; + onGotCall(context, nextPendingCallInformation); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + /** + * Method called when we have got new pending call information + * + * @param context Application context + * @param info Information about the call + */ + private void onGotCall(Context context, @Nullable NextPendingCallInformation info){ + + if(info == null){ + Log.e(TAG, "Could not get information about next pending call!"); + return; + } + + //Check if there is no pending call + if(!info.isHasPendingCall()) { + + //Remove any related notification + RemoveCallNotification(context); + + 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); + + //Create and show notification + NotificationCompat.Builder builder = + new NotificationCompat.Builder(context, CALL_CHANNEL_ID) + .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) + + + //Create notification channel if required + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ + NotificationChannel channel = new NotificationChannel(CALL_CHANNEL_ID, + CALL_CHANNEL_DESCRIPTION, IMPORTANCE_HIGH); + channel.setDescription(CALL_CHANNEL_DESCRIPTION); + + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + + Notification n = builder.build(); + n.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; + + NotificationManagerCompat.from(context).notify(CALL_NOTIFICATION_ID, n); + } + + /** + * Remove any visible call notification + * + * @param context The context of the application + */ + public static void RemoveCallNotification(Context context){ + NotificationManagerCompat.from(context).cancel(CALL_NOTIFICATION_ID); + } +} diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b57b1b5..a9ba16f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -320,4 +320,8 @@ Impossible de récupérer les informations sur le groupe ! Accès sur invitation Une erreur a survenu lors de la création d\'un appel pour la conversation ! + Appel + Répondre + Rejeter + %s vous appelle. \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 188ad7d..08ccabf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -320,4 +320,7 @@ Could not get results of your search! Could not create a call for the conversation! Call + Respond + Reject call + %s is calling you