Separate call system from the rest of the application

This commit is contained in:
Pierre HUBERT 2019-05-01 16:01:48 +02:00
parent 6ebb259232
commit d94a22aa4f
38 changed files with 262 additions and 116 deletions

View File

@ -55,6 +55,17 @@ android {
buildConfigField "String", "pdf_view_url", "\"https://pdfviewer.communiquons.org/?file=\"" buildConfigField "String", "pdf_view_url", "\"https://pdfviewer.communiquons.org/?file=\""
} }
} }
flavorDimensions "versions"
productFlavors {
// "Normal" version of the application
normal { }
// Version with video calls features
videoCalls {}
}
} }
dependencies { dependencies {
@ -64,8 +75,12 @@ dependencies {
implementation 'com.android.support:design:28.0.0' implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:preference-v7:28.0.0' implementation 'com.android.support:preference-v7:28.0.0'
implementation 'com.android.support:support-v4:28.0.0' implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.squareup.okhttp3:okhttp:3.12.1'
implementation 'org.whispersystems:webrtc-android:M71' // Library required to make video calls
videoCallsImplementation 'com.squareup.okhttp3:okhttp:3.12.1'
videoCallsImplementation 'org.whispersystems:webrtc-android:M71'
//Test
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

View File

@ -7,12 +7,6 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Video calls require camera and microphone -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- To save file (eg. images) into user storage --> <!-- To save file (eg. images) into user storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@ -81,32 +75,6 @@
android:name=".ui.activities.AboutActivity" android:name=".ui.activities.AboutActivity"
android:label="@string/activity_about_title" /> android:label="@string/activity_about_title" />
<!-- Call activity -->
<activity
android:name=".ui.activities.CallActivity"
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">
<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> </application>

View File

@ -3,10 +3,8 @@ package org.communiquons.android.comunic.client.ui.activities;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.Uri; import android.net.Uri;
@ -26,24 +24,18 @@ import android.widget.Toast;
import org.communiquons.android.comunic.client.BuildConfig; import org.communiquons.android.comunic.client.BuildConfig;
import org.communiquons.android.comunic.client.R; import org.communiquons.android.comunic.client.R;
import org.communiquons.android.comunic.client.ui.utils.PermissionsUtils;
import org.communiquons.crashreporter.CrashReporter;
import org.communiquons.android.comunic.client.data.enums.VirtualDirectoryType; import org.communiquons.android.comunic.client.data.enums.VirtualDirectoryType;
import org.communiquons.android.comunic.client.data.helpers.APIRequestHelper; import org.communiquons.android.comunic.client.data.helpers.APIRequestHelper;
import org.communiquons.android.comunic.client.data.helpers.AccountHelper; import org.communiquons.android.comunic.client.data.helpers.AccountHelper;
import org.communiquons.android.comunic.client.data.helpers.ConversationsListHelper; import org.communiquons.android.comunic.client.data.helpers.ConversationsListHelper;
import org.communiquons.android.comunic.client.data.helpers.DatabaseHelper; import org.communiquons.android.comunic.client.data.helpers.DatabaseHelper;
import org.communiquons.android.comunic.client.data.helpers.DebugHelper; import org.communiquons.android.comunic.client.data.helpers.DebugHelper;
import org.communiquons.android.comunic.client.data.models.CallInformation;
import org.communiquons.android.comunic.client.data.models.NotificationsCount; import org.communiquons.android.comunic.client.data.models.NotificationsCount;
import org.communiquons.android.comunic.client.data.models.VirtualDirectory; import org.communiquons.android.comunic.client.data.models.VirtualDirectory;
import org.communiquons.android.comunic.client.data.runnables.FriendRefreshLoopRunnable; import org.communiquons.android.comunic.client.data.runnables.FriendRefreshLoopRunnable;
import org.communiquons.android.comunic.client.data.services.NotificationsService; import org.communiquons.android.comunic.client.data.services.NotificationsService;
import org.communiquons.android.comunic.client.data.utils.PreferencesUtils; import org.communiquons.android.comunic.client.data.utils.PreferencesUtils;
import org.communiquons.android.comunic.client.ui.asynctasks.CreateCallForConversationTask;
import org.communiquons.android.comunic.client.ui.asynctasks.FindVirtualDirectoryTask; import org.communiquons.android.comunic.client.ui.asynctasks.FindVirtualDirectoryTask;
import org.communiquons.android.comunic.client.ui.asynctasks.GetCallConfigurationTask;
import org.communiquons.android.comunic.client.ui.asynctasks.SafeAsyncTask;
import org.communiquons.android.comunic.client.ui.fragments.ConversationFragment; import org.communiquons.android.comunic.client.ui.fragments.ConversationFragment;
import org.communiquons.android.comunic.client.ui.fragments.ConversationsListFragment; import org.communiquons.android.comunic.client.ui.fragments.ConversationsListFragment;
import org.communiquons.android.comunic.client.ui.fragments.FriendsListFragment; import org.communiquons.android.comunic.client.ui.fragments.FriendsListFragment;
@ -56,13 +48,14 @@ import org.communiquons.android.comunic.client.ui.fragments.groups.GroupPageMain
import org.communiquons.android.comunic.client.ui.fragments.groups.UserGroupsFragment; import org.communiquons.android.comunic.client.ui.fragments.groups.UserGroupsFragment;
import org.communiquons.android.comunic.client.ui.fragments.userpage.UserAccessDeniedFragment; import org.communiquons.android.comunic.client.ui.fragments.userpage.UserAccessDeniedFragment;
import org.communiquons.android.comunic.client.ui.fragments.userpage.UserPageFragment; import org.communiquons.android.comunic.client.ui.fragments.userpage.UserPageFragment;
import org.communiquons.android.comunic.client.ui.listeners.OnOpenCallListener;
import org.communiquons.android.comunic.client.ui.listeners.OnOpenPageListener; import org.communiquons.android.comunic.client.ui.listeners.OnOpenPageListener;
import org.communiquons.android.comunic.client.ui.listeners.onPostOpenListener; import org.communiquons.android.comunic.client.ui.listeners.onPostOpenListener;
import org.communiquons.android.comunic.client.ui.listeners.openConversationListener; import org.communiquons.android.comunic.client.ui.listeners.openConversationListener;
import org.communiquons.android.comunic.client.ui.listeners.updateConversationListener; import org.communiquons.android.comunic.client.ui.listeners.updateConversationListener;
import org.communiquons.android.comunic.client.ui.utils.PermissionsUtils;
import org.communiquons.android.comunic.client.ui.utils.UiUtils; import org.communiquons.android.comunic.client.ui.utils.UiUtils;
import org.communiquons.android.comunic.client.ui.views.NavigationBar; import org.communiquons.android.comunic.client.ui.views.NavigationBar;
import org.communiquons.crashreporter.CrashReporter;
import java.util.Objects; import java.util.Objects;
@ -77,9 +70,9 @@ import static org.communiquons.android.comunic.client.ui.Constants.PreferencesKe
* *
* @author Pierre HUBERT * @author Pierre HUBERT
*/ */
public class MainActivity extends BaseActivity implements public abstract class AbstractMainActivity extends BaseActivity implements
openConversationListener, updateConversationListener, OnOpenPageListener, openConversationListener, updateConversationListener, OnOpenPageListener,
onPostOpenListener, NavigationBar.OnNavigationItemSelectedListener, OnOpenCallListener { onPostOpenListener, NavigationBar.OnNavigationItemSelectedListener {
/** /**
* Debug tag * Debug tag
@ -191,11 +184,6 @@ public class MainActivity extends BaseActivity implements
IntentFilter intentFilter = new IntentFilter(NotificationsService.BROADCAST_ACTION); IntentFilter intentFilter = new IntentFilter(NotificationsService.BROADCAST_ACTION);
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter); LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
//Get calls configuration
GetCallConfigurationTask callConfigurationTask = new GetCallConfigurationTask(this);
callConfigurationTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
getTasksManager().addTask(callConfigurationTask);
//Request a few permissions //Request a few permissions
PermissionsUtils.RequestPermissions(this, PermissionsUtils.RequestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
@ -369,12 +357,7 @@ public class MainActivity extends BaseActivity implements
PopupMenu popupMenu = new PopupMenu(this, PopupMenu popupMenu = new PopupMenu(this,
mNavBar.getItemIdentifierView(R.id.action_more)); mNavBar.getItemIdentifierView(R.id.action_more));
onCreateOptionsMenu(popupMenu.getMenu()); onCreateOptionsMenu(popupMenu.getMenu());
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { popupMenu.setOnMenuItemClickListener(this::onOptionsItemSelected);
@Override
public boolean onMenuItemClick(MenuItem item) {
return onOptionsItemSelected(item);
}
});
popupMenu.show(); popupMenu.show();
return false; return false;
} }
@ -397,7 +380,7 @@ public class MainActivity extends BaseActivity implements
if(!(activity instanceof MainActivity)) if(!(activity instanceof MainActivity))
throw new RuntimeException("Specified activity is not an instance of activity!"); throw new RuntimeException("Specified activity is not an instance of activity!");
((MainActivity)activity).mNavBar.setIdentifierSelected(id); ((AbstractMainActivity)activity).mNavBar.setIdentifierSelected(id);
} }
@ -469,23 +452,17 @@ public class MainActivity extends BaseActivity implements
.setMessage(R.string.popup_signout_message) .setMessage(R.string.popup_signout_message)
.setCancelable(true) .setCancelable(true)
.setPositiveButton(R.string.popup_signout_confirm_button, .setPositiveButton(R.string.popup_signout_confirm_button,
new DialogInterface.OnClickListener() { (dialog, which) -> {
@Override
public void onClick(DialogInterface dialog, int which) {
//Sign out user //Sign out user
accountHelper.sign_out(); accountHelper.sign_out();
//Redirect to login activity //Redirect to login activity
startActivity(new Intent(MainActivity.this, LoginActivity.class)); startActivity(new Intent(AbstractMainActivity.this, LoginActivity.class));
}
}) })
.setNegativeButton(R.string.popup_signout_cancel_button, new DialogInterface.OnClickListener() { .setNegativeButton(R.string.popup_signout_cancel_button, (dialog, which) -> {
@Override
public void onClick(DialogInterface dialog, int which) {
//Nothing now //Nothing now
}
}) })
//Show popup //Show popup
@ -537,12 +514,9 @@ public class MainActivity extends BaseActivity implements
unsetFindVirtualDirectoryTask(); unsetFindVirtualDirectoryTask();
mFindVirtualDirectoryTask = new FindVirtualDirectoryTask(this); mFindVirtualDirectoryTask = new FindVirtualDirectoryTask(this);
mFindVirtualDirectoryTask.setOnPostExecuteListener(new SafeAsyncTask.OnPostExecuteListener<VirtualDirectory>() { mFindVirtualDirectoryTask.setOnPostExecuteListener(virtualDirectory -> {
@Override
public void OnPostExecute(VirtualDirectory virtualDirectory) {
dialog.dismiss(); dialog.dismiss();
openDirectory(virtualDirectory); openDirectory(virtualDirectory);
}
}); });
mFindVirtualDirectoryTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, tag); mFindVirtualDirectoryTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, tag);
@ -724,7 +698,7 @@ public class MainActivity extends BaseActivity implements
if (integer != null) if (integer != null)
openConversation(integer); openConversation(integer);
else else
Toast.makeText(MainActivity.this, R.string.err_get_private_conversation, Toast.makeText(AbstractMainActivity.this, R.string.err_get_private_conversation,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, userID); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, userID);
@ -875,40 +849,5 @@ public class MainActivity extends BaseActivity implements
transaction.commit(); transaction.commit();
} }
@Override
public void createCallForConversation(int convID) {
final Dialog dialog = UiUtils.create_loading_dialog(this);
//Create the call for the conversation
CreateCallForConversationTask task = new CreateCallForConversationTask(this);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, convID);
getTasksManager().addTask(task, true);
task.setOnPostExecuteListener(new SafeAsyncTask.OnPostExecuteListener<CallInformation>() {
@Override
public void OnPostExecute(@Nullable CallInformation callInformation) {
dialog.dismiss();
//Check for errors
if(callInformation == null)
Toast.makeText(
MainActivity.this,
R.string.err_create_call_for_conversation,
Toast.LENGTH_SHORT).show();
else
//Open call
openCall(callInformation.getId());
}
});
}
@Override
public void openCall(int callID) {
Intent intent = new Intent(this, CallActivity.class);
intent.putExtra(CallActivity.ARGUMENT_CALL_ID, callID);
startActivity(intent);
} }
}

View File

@ -500,9 +500,7 @@ public class ConversationFragment extends Fragment
//Add call button (if possible) //Add call button (if possible)
if(CallsHelper.IsCallSystemAvailable() && info.getMembers().size() > 1 && info.getMembers().size() <= if(CallsHelper.IsCallSystemAvailableForConversation(info) && !mHasCallButton) {
Objects.requireNonNull(CallsHelper.GetCallsConfiguration()).getMaximumNumberMembers() &&
!mHasCallButton) {
mHasCallButton = true; mHasCallButton = true;

View File

@ -0,0 +1,64 @@
package org.communiquons.android.comunic.client.data.helpers;
import android.content.Context;
import android.support.annotation.Nullable;
import org.communiquons.android.comunic.client.data.models.ConversationInfo;
/**
* Calls helper
*
* Placeholder for the flavor of the application where there is no call possible
*
* @author Pierre HUBERT
*/
public class CallsHelper extends BaseHelper {
/**
* Debug tag
*/
private static final String TAG = CallsHelper.class.getSimpleName();
public CallsHelper(Context context) {
super(context);
}
/**
* Get call configuration if required
*/
public void getCallConfigurationIfRequired(){
// Do nothing
}
/**
* Get Calls configuration, if available
*
* Note if IsCallSystemAvailable returned TRUE, it is guaranteed that this method WILL NOT
* return null
*
* @return Calls configuration
*/
@Nullable
public static Object GetCallsConfiguration(){
return null;
}
/**
* No call possible with this flavour
*/
public static boolean IsCallSystemAvailable(){
return false;
}
/**
* No call for no conversation
*
* @param conversation Target conversation
* @return false
*/
public static boolean IsCallSystemAvailableForConversation(ConversationInfo conversation){
return false;
}
}

View File

@ -0,0 +1,10 @@
package org.communiquons.android.comunic.client.ui.activities;
/**
* Main activity for abstract configuration
*
* @author Pierre HUBERT
*/
public class MainActivity extends AbstractMainActivity {
}

View File

@ -0,0 +1,23 @@
package org.communiquons.android.comunic.client.ui.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Pending class broadcast receiver
*
* Does nothing in this flavor
*
* @author Pierre HUBERT
*/
public class PendingCallsBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Do nothing
}
public static void RemoveCallNotification(Context context){
// Do nothing
}
}

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.communiquons.android.comunic.client">
<!-- Video calls require camera and microphone -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_scheme"
android:icon="@drawable/ic_app_rounded"
android:label="@string/app_name"
android:roundIcon="@drawable/ic_app_rounded"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<!-- Call activity -->
<activity
android:name=".ui.activities.CallActivity"
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">
<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

@ -11,6 +11,7 @@ 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.CallMember;
import org.communiquons.android.comunic.client.data.models.CallResponse; 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.CallsConfiguration;
import org.communiquons.android.comunic.client.data.models.ConversationInfo;
import org.communiquons.android.comunic.client.data.models.NextPendingCallInformation; import org.communiquons.android.comunic.client.data.models.NextPendingCallInformation;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -18,6 +19,7 @@ import org.json.JSONObject;
import org.webrtc.PeerConnection; import org.webrtc.PeerConnection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects;
/** /**
* Calls helper * Calls helper
@ -93,6 +95,16 @@ public class CallsHelper extends BaseHelper {
return mCallsConfiguration != null && mCallsConfiguration.isEnabled(); return mCallsConfiguration != null && mCallsConfiguration.isEnabled();
} }
/**
* Check out whether call system is available for a given conversation or not
*/
public static boolean IsCallSystemAvailableForConversation(ConversationInfo conversation){
return CallsHelper.IsCallSystemAvailable() &&
conversation.getMembers().size() > 1 &&
conversation.getMembers().size() <= Objects.requireNonNull(
CallsHelper.GetCallsConfiguration()).getMaximumNumberMembers();
}
/** /**
* Get the list of STUN and TURN servers * Get the list of STUN and TURN servers
* *

View File

@ -0,0 +1,66 @@
package org.communiquons.android.comunic.client.ui.activities;
import android.app.Dialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.Toast;
import org.communiquons.android.comunic.client.R;
import org.communiquons.android.comunic.client.ui.asynctasks.CreateCallForConversationTask;
import org.communiquons.android.comunic.client.ui.asynctasks.GetCallConfigurationTask;
import org.communiquons.android.comunic.client.ui.listeners.OnOpenCallListener;
import org.communiquons.android.comunic.client.ui.utils.UiUtils;
/**
* MainActivity implementation for video calls
*
* @author Pierre HUBERT
*/
public class MainActivity extends AbstractMainActivity implements OnOpenCallListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Get calls configuration
GetCallConfigurationTask callConfigurationTask = new GetCallConfigurationTask(this);
callConfigurationTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
getTasksManager().addTask(callConfigurationTask);
}
@Override
public void createCallForConversation(int convID) {
final Dialog dialog = UiUtils.create_loading_dialog(this);
//Create the call for the conversation
CreateCallForConversationTask task = new CreateCallForConversationTask(this);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, convID);
getTasksManager().addTask(task, true);
task.setOnPostExecuteListener(callInformation -> {
dialog.dismiss();
//Check for errors
if(callInformation == null)
Toast.makeText(
MainActivity.this,
R.string.err_create_call_for_conversation,
Toast.LENGTH_SHORT).show();
else
//Open call
openCall(callInformation.getId());
});
}
@Override
public void openCall(int callID) {
Intent intent = new Intent(this, CallActivity.class);
intent.putExtra(CallActivity.ARGUMENT_CALL_ID, callID);
startActivity(intent);
}
}

View File

@ -23,7 +23,6 @@ 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.CallActivity;
import org.communiquons.android.comunic.client.ui.activities.IncomingCallActivity; 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.GetNextPendingCallTask;
import org.communiquons.android.comunic.client.ui.asynctasks.SafeAsyncTask;
import org.communiquons.android.comunic.client.ui.utils.UiUtils; import org.communiquons.android.comunic.client.ui.utils.UiUtils;
import java.util.Objects; import java.util.Objects;