diff --git a/app/src/main/java/org/linphone/LinphoneActivity.java b/app/src/main/java/org/linphone/LinphoneActivity.java index 404964729..4de17425a 100644 --- a/app/src/main/java/org/linphone/LinphoneActivity.java +++ b/app/src/main/java/org/linphone/LinphoneActivity.java @@ -637,6 +637,10 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick changeCurrentFragment(FragmentsAvailable.ABOUT, null); } + public void displayRecordings() { + + } + public void displayContactsForEdition(String sipAddress, String displayName) { Bundle extras = new Bundle(); extras.putBoolean("EditOnClick", true); @@ -1606,6 +1610,7 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick if (getResources().getBoolean(R.bool.enable_in_app_purchase)) { sideMenuItems.add(new MenuItem(getResources().getString(R.string.inapp), R.drawable.menu_options)); } + sideMenuItems.add(new MenuItem(getResources().getString(R.string.menu_recordings), R.drawable.menu_recordings)); sideMenuItems.add(new MenuItem(getResources().getString(R.string.menu_about), R.drawable.menu_about)); sideMenuContent = findViewById(R.id.side_menu_content); sideMenuItemList = findViewById(R.id.item_list); @@ -1629,6 +1634,9 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick LinphoneActivity.instance().displayInapp(); } } + if (sideMenuItemList.getAdapter().getItem(i).toString().equals(R.string.menu_recordings)) { + LinphoneActivity.instance().displayRecordings(); + } openOrCloseSideMenu(false); } }); diff --git a/app/src/main/java/org/linphone/call/CallActivity.java b/app/src/main/java/org/linphone/call/CallActivity.java index dd24a68dd..55649595a 100644 --- a/app/src/main/java/org/linphone/call/CallActivity.java +++ b/app/src/main/java/org/linphone/call/CallActivity.java @@ -101,6 +101,7 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList private static final int PERMISSIONS_REQUEST_CAMERA = 202; private static final int PERMISSIONS_ENABLED_CAMERA = 203; private static final int PERMISSIONS_ENABLED_MIC = 204; + private static final int PERMISSIONS_EXTERNAL_STORAGE = 205; private static CallActivity instance; @@ -109,14 +110,14 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList private ImageView switchCamera; private TextView missedChats; private RelativeLayout mActiveCallHeader, sideMenuContent, avatar_layout; - private ImageView pause, hangUp, dialer, video, micro, speaker, options, addCall, transfer, conference, conferenceStatus, contactPicture; + private ImageView pause, hangUp, dialer, video, micro, speaker, options, addCall, transfer, conference, conferenceStatus, contactPicture, recordCall, recording; private ImageView audioRoute, routeSpeaker, routeEarpiece, routeBluetooth, menu, chat; private LinearLayout mNoCurrentCall, callInfo, mCallPaused; private ProgressBar videoProgress; private StatusFragment status; private CallAudioFragment audioCallFragment; private CallVideoFragment videoCallFragment; - private boolean isSpeakerEnabled = false, isMicMuted = false, isTransferAllowed, isVideoAsk; + private boolean isSpeakerEnabled = false, isMicMuted = false, isTransferAllowed, isVideoAsk, isRecording = false; private LinearLayout mControlsLayout; private Numpad numpad; private int cameraNumber; @@ -194,7 +195,7 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList return; } else if (state == State.Paused || state == State.PausedByRemote || state == State.Pausing) { if (LinphoneManager.getLc().getCurrentCall() != null) { - enabledVideoButton(false); + video.setEnabled(false); } if (isVideoEnabled(call)) { showAudioView(); @@ -207,7 +208,7 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList } } if (LinphoneManager.getLc().getCurrentCall() != null) { - enabledVideoButton(true); + video.setEnabled(true); } } else if (state == State.StreamsRunning) { switchVideo(isVideoEnabled(call)); @@ -344,7 +345,7 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList //TopBar video = findViewById(R.id.video); video.setOnClickListener(this); - enabledVideoButton(false); + video.setEnabled(false); videoProgress = findViewById(R.id.video_in_progress); videoProgress.setVisibility(View.GONE); @@ -380,7 +381,7 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList pause = findViewById(R.id.pause); pause.setOnClickListener(this); - enabledPauseButton(false); + pause.setEnabled(false); mActiveCallHeader = findViewById(R.id.active_call); mNoCurrentCall = findViewById(R.id.no_current_call); @@ -402,6 +403,15 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList conference.setEnabled(false); conference.setOnClickListener(this); + recordCall = findViewById(R.id.record_call); + recordCall.setOnClickListener(this); + recordCall.setEnabled(false); + + recording = findViewById(R.id.recording); + recording.setOnClickListener(this); + recording.setEnabled(false); + recording.setVisibility(View.GONE); + try { audioRoute = findViewById(R.id.audio_route); audioRoute.setOnClickListener(this); @@ -489,6 +499,15 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList } }); break; + case PERMISSIONS_EXTERNAL_STORAGE: + LinphoneUtils.dispatchOnUIThread(new Runnable() { + @Override + public void run() { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + toggleCallRecording(!isRecording); + } + } + }); } } @@ -528,7 +547,7 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList public void refreshInCallActions() { if (!LinphonePreferences.instance().isVideoEnabled() || isConferenceRunning) { - enabledVideoButton(false); + video.setEnabled(false); } else { if (video.isEnabled()) { if (isVideoEnabled(LinphoneManager.getLc().getCurrentCall())) { @@ -582,33 +601,27 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList } //Enabled transfer button - if (isTransferAllowed && !LinphoneManager.getLc().soundResourcesLocked()) - enabledTransferButton(true); + transfer.setEnabled(isTransferAllowed && !LinphoneManager.getLc().soundResourcesLocked()); //Enable conference button - if (LinphoneManager.getLc().getCallsNb() > 1 && LinphoneManager.getLc().getCallsNb() > confsize && !LinphoneManager.getLc().soundResourcesLocked()) { - enabledConferenceButton(true); - } else { - enabledConferenceButton(false); - } + conference.setEnabled(LinphoneManager.getLc().getCallsNb() > 1 && LinphoneManager.getLc().getCallsNb() > confsize && !LinphoneManager.getLc().soundResourcesLocked()); addCall.setEnabled(LinphoneManager.getLc().getCallsNb() < LinphoneManager.getLc().getMaxCalls() && !LinphoneManager.getLc().soundResourcesLocked()); options.setEnabled(!getResources().getBoolean(R.bool.disable_options_in_call) && (addCall.isEnabled() || transfer.isEnabled())); - if (LinphoneManager.getLc().getCurrentCall() != null && LinphonePreferences.instance().isVideoEnabled() && !LinphoneManager.getLc().getCurrentCall().mediaInProgress()) { - enabledVideoButton(true); - } else { - enabledVideoButton(false); - } - if (LinphoneManager.getLc().getCurrentCall() != null && !LinphoneManager.getLc().getCurrentCall().mediaInProgress()) { - enabledPauseButton(true); - } else { - enabledPauseButton(false); - } + recordCall.setEnabled(!LinphoneManager.getLc().soundResourcesLocked()); + recordCall.setImageResource(isRecording ? R.drawable.options_rec_selected : R.drawable.options_rec_default); + + recording.setEnabled(isRecording); + recording.setVisibility(isRecording ? View.VISIBLE : View.GONE); + + + video.setEnabled(LinphoneManager.getLc().getCurrentCall() != null && LinphonePreferences.instance().isVideoEnabled() && !LinphoneManager.getLc().getCurrentCall().mediaInProgress()); + + pause.setEnabled(LinphoneManager.getLc().getCurrentCall() != null && !LinphoneManager.getLc().getCurrentCall().mediaInProgress()); + micro.setEnabled(true); - if (!isTablet()) { - speaker.setEnabled(true); - } + speaker.setEnabled(!isTablet()); transfer.setEnabled(true); pause.setEnabled(true); dialer.setEnabled(true); @@ -649,6 +662,17 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList toggleSpeaker(); } else if (id == R.id.add_call) { goBackToDialer(); + } else if (id == R.id.record_call) { + int externalStorage = getPackageManager().checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, getPackageName()); + Log.i("[Permission] External storage permission is " + (externalStorage == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")); + + if (externalStorage == PackageManager.PERMISSION_GRANTED) { + toggleCallRecording(!isRecording); + } else { + checkAndRequestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, PERMISSIONS_EXTERNAL_STORAGE); + } + } else if (id == R.id.recording) { + toggleCallRecording(false); } else if (id == R.id.pause) { pauseOrResumeCall(LinphoneManager.getLc().getCurrentCall()); } else if (id == R.id.hang_up) { @@ -700,35 +724,31 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList } } - private void enabledVideoButton(boolean enabled) { - if (enabled) { - video.setEnabled(true); - } else { - video.setEnabled(false); - } - } + private void toggleCallRecording(boolean enable) { + Call call = LinphoneManager.getLc().getCurrentCall(); - private void enabledPauseButton(boolean enabled) { - if (enabled) { - pause.setEnabled(true); - } else { - pause.setEnabled(false); - } - } + if (call == null) return; - private void enabledTransferButton(boolean enabled) { - if (enabled) { - transfer.setEnabled(true); - } else { - transfer.setEnabled(false); - } - } + if (enable && !isRecording) { + call.startRecording(); + Log.d("start call recording"); - private void enabledConferenceButton(boolean enabled) { - if (enabled) { - conference.setEnabled(true); - } else { - conference.setEnabled(false); + recordCall.setImageResource(R.drawable.options_rec_selected); + + recording.setVisibility(View.VISIBLE); + recording.setEnabled(true); + + isRecording = true; + } else if (!enable && isRecording) { + call.stopRecording(); + Log.d("stop call recording"); + + recordCall.setImageResource(R.drawable.options_rec_default); + + recording.setVisibility(View.GONE); + recording.setEnabled(false); + + isRecording = false; } } @@ -910,6 +930,10 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList Core lc = LinphoneManager.getLc(); Call currentCall = lc.getCurrentCall(); + if (isRecording) { + toggleCallRecording(false); + } + if (currentCall != null) { lc.terminateCall(currentCall); } else if (lc.isInConference()) { @@ -963,6 +987,7 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList transfer.setVisibility(View.INVISIBLE); addCall.setVisibility(View.INVISIBLE); conference.setVisibility(View.INVISIBLE); + recordCall.setVisibility(View.INVISIBLE); displayVideoCall(false); numpad.setVisibility(View.GONE); options.setSelected(false); @@ -1023,12 +1048,14 @@ public class CallActivity extends LinphoneGenericActivity implements OnClickList } addCall.setVisibility(View.INVISIBLE); conference.setVisibility(View.INVISIBLE); + recordCall.setVisibility(View.INVISIBLE); } else { //Display options if (isTransferAllowed) { transfer.setVisibility(View.VISIBLE); } addCall.setVisibility(View.VISIBLE); conference.setVisibility(View.VISIBLE); + recordCall.setVisibility(View.VISIBLE); options.setSelected(true); transfer.setEnabled(LinphoneManager.getLc().getCurrentCall() != null); } diff --git a/app/src/main/java/org/linphone/utils/FileUtils.java b/app/src/main/java/org/linphone/utils/FileUtils.java index 7249b2cab..6eebd6548 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.java +++ b/app/src/main/java/org/linphone/utils/FileUtils.java @@ -19,6 +19,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import android.annotation.SuppressLint; import android.content.Context; import android.database.Cursor; import android.net.Uri; @@ -28,6 +29,7 @@ import android.provider.OpenableColumns; import android.text.TextUtils; import org.linphone.LinphoneManager; +import org.linphone.core.Address; import org.linphone.core.ChatMessage; import org.linphone.core.Content; import org.linphone.core.Friend; @@ -40,6 +42,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; @@ -197,6 +200,30 @@ public class FileUtils { return storageDir; } + public static String getRecordingsDirectory(Context mContext) { + String recordingsDir = Environment.getExternalStorageDirectory() + "/" + mContext.getString(mContext.getResources().getIdentifier("app_name", "string", mContext.getPackageName())) + "/recordings"; + File file = new File(recordingsDir); + if (!file.isDirectory() || !file.exists()) { + Log.w("Directory " + file + " doesn't seem to exists yet, let's create it"); + file.mkdirs(); + LinphoneManager.getInstance().getMediaScanner().scanFile(file); + } + return recordingsDir; + } + + @SuppressLint("SimpleDateFormat") + public static String getCallRecordingFilename(Context context, Address address) { + String fileName = getRecordingsDirectory(context) + "/"; + + String name = address.getDisplayName() == null ? address.getUsername() : address.getDisplayName(); + fileName += name + "_"; + + DateFormat format = new SimpleDateFormat("dd-MM-yyyy-HH-mm-ss"); + fileName += format.format(new Date()) + ".mkv"; + + return fileName; + } + public static void scanFile(ChatMessage message) { String appData = message.getAppdata(); if (appData == null) { diff --git a/app/src/main/res/drawable/menu_recordings.png b/app/src/main/res/drawable/menu_recordings.png new file mode 100644 index 000000000..3bef81d13 Binary files /dev/null and b/app/src/main/res/drawable/menu_recordings.png differ diff --git a/app/src/main/res/drawable/options_rec_default.png b/app/src/main/res/drawable/options_rec_default.png new file mode 100644 index 000000000..075c09314 Binary files /dev/null and b/app/src/main/res/drawable/options_rec_default.png differ diff --git a/app/src/main/res/drawable/options_rec_selected.png b/app/src/main/res/drawable/options_rec_selected.png new file mode 100644 index 000000000..0ef1ca322 Binary files /dev/null and b/app/src/main/res/drawable/options_rec_selected.png differ diff --git a/app/src/main/res/drawable/recording.png b/app/src/main/res/drawable/recording.png new file mode 100644 index 000000000..fb4e82669 Binary files /dev/null and b/app/src/main/res/drawable/recording.png differ diff --git a/app/src/main/res/layout-land/call.xml b/app/src/main/res/layout-land/call.xml index 9208c06a0..5e4fa5fc9 100644 --- a/app/src/main/res/layout-land/call.xml +++ b/app/src/main/res/layout-land/call.xml @@ -181,6 +181,19 @@ android:layout_gravity="center"/> + + + + + + + + Assistant Settings + My recordings About Quit @@ -286,6 +287,9 @@ Display filter: Call + + No recordings. + Send log Reset log @@ -546,4 +550,5 @@ Linphone instant messages notifications Group chat room subject Group chat room info + Record call