Эх сурвалжийг харах

add silent call feature

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Marcel Hibbe 3 жил өмнө
parent
commit
a23d4ef692

+ 13 - 7
app/src/main/java/com/nextcloud/talk/activities/CallActivity.java

@@ -231,6 +231,7 @@ public class CallActivity extends CallBaseActivity {
     private boolean microphoneOn = false;
 
     private boolean isVoiceOnlyCall;
+    private boolean isCallWithoutNotification;
     private boolean isIncomingCallFromNotification;
     private Handler callControlHandler = new Handler();
     private Handler callInfosHandler = new Handler();
@@ -287,6 +288,7 @@ public class CallActivity extends CallBaseActivity {
         conversationPassword = extras.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_PASSWORD(), "");
         conversationName = extras.getString(BundleKeys.INSTANCE.getKEY_CONVERSATION_NAME(), "");
         isVoiceOnlyCall = extras.getBoolean(BundleKeys.INSTANCE.getKEY_CALL_VOICE_ONLY(), false);
+        isCallWithoutNotification = extras.getBoolean(BundleKeys.INSTANCE.getKEY_CALL_WITHOUT_NOTIFICATION(), false);
 
         if (extras.containsKey(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL())) {
             isIncomingCallFromNotification = extras.getBoolean(BundleKeys.INSTANCE.getKEY_FROM_NOTIFICATION_START_CALL());
@@ -1356,7 +1358,11 @@ public class CallActivity extends CallBaseActivity {
 
         int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
 
-        ncApi.joinCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken), inCallFlag)
+        ncApi.joinCall(
+                credentials,
+                ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken),
+                inCallFlag,
+                isCallWithoutNotification)
             .subscribeOn(Schedulers.io())
             .retry(3)
             .observeOn(AndroidSchedulers.mainThread())
@@ -1825,11 +1831,11 @@ public class CallActivity extends CallBaseActivity {
         int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
 
         ncApi.getPeersForCall(
-            credentials,
-            ApiUtils.getUrlForCall(
-                apiVersion,
-                baseUrl,
-                roomToken))
+                credentials,
+                ApiUtils.getUrlForCall(
+                    apiVersion,
+                    baseUrl,
+                    roomToken))
             .subscribeOn(Schedulers.io())
             .subscribe(new Observer<ParticipantsOverall>() {
                 @Override
@@ -2468,7 +2474,7 @@ public class CallActivity extends CallBaseActivity {
             mediaPlayer.setDataSource(this, ringtoneUri);
             mediaPlayer.setLooping(true);
             AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(
-                AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                    AudioAttributes.CONTENT_TYPE_SONIFICATION)
                 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                 .build();
             mediaPlayer.setAudioAttributes(audioAttributes);

+ 22 - 21
app/src/main/java/com/nextcloud/talk/api/NcApi.java

@@ -211,7 +211,8 @@ public interface NcApi {
     @FormUrlEncoded
     @POST
     Observable<GenericOverall> joinCall(@Nullable @Header("Authorization") String authorization, @Url String url,
-                                        @Field("flags") Integer inCall);
+                                        @Field("flags") Integer inCall,
+                                        @Field("silent") Boolean callWithoutNotification);
 
     /*
     Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /call/callToken
@@ -239,8 +240,8 @@ public interface NcApi {
     */
     @GET
     Observable<SignalingOverall> pullSignalingMessages(@Nullable @Header("Authorization") String authorization, @Url
-            String
-            url);
+        String
+        url);
 
      /*
         QueryMap items are as follows:
@@ -259,7 +260,7 @@ public interface NcApi {
     @FormUrlEncoded
     @PUT
     Observable<GenericOverall> setUserData(@Header("Authorization") String authorization, @Url String url,
-                                                  @Field("key") String key, @Field("value") String value);
+                                           @Field("key") String key, @Field("value") String value);
 
 
     /*
@@ -281,14 +282,14 @@ public interface NcApi {
 
     @POST
     Observable<PushRegistrationOverall> registerDeviceForNotificationsWithNextcloud(@Header("Authorization")
-                                                                                            String authorization,
+                                                                                        String authorization,
                                                                                     @Url String url,
                                                                                     @QueryMap Map<String,
-                                                                                            String> options);
+                                                                                        String> options);
 
     @DELETE
     Observable<GenericOverall> unregisterDeviceForNotificationsWithNextcloud(@Header("Authorization")
-                                                                                     String authorization,
+                                                                                 String authorization,
                                                                              @Url String url);
 
     @FormUrlEncoded
@@ -438,10 +439,10 @@ public interface NcApi {
     @FormUrlEncoded
     @POST
     Observable<GenericOverall> sendLocation(@Header("Authorization") String authorization,
-                                                        @Url String url,
-                                                        @Field("objectType") String objectType,
-                                                        @Field("objectId") String objectId,
-                                                        @Field("metaData") String metaData);
+                                            @Url String url,
+                                            @Field("objectType") String objectType,
+                                            @Field("objectId") String objectId,
+                                            @Field("metaData") String metaData);
 
     @DELETE
     Observable<GenericOverall> clearChatHistory(@Header("Authorization") String authorization, @Url String url);
@@ -484,23 +485,23 @@ public interface NcApi {
     @FormUrlEncoded
     @PUT
     Observable<GenericOverall> setPredefinedStatusMessage(@Header("Authorization") String authorization,
-                                      @Url String url,
-                                      @Field("messageId") String selectedPredefinedMessageId,
-                                      @Field("clearAt") Long clearAt);
+                                                          @Url String url,
+                                                          @Field("messageId") String selectedPredefinedMessageId,
+                                                          @Field("clearAt") Long clearAt);
 
     @FormUrlEncoded
     @PUT
     Observable<GenericOverall> setCustomStatusMessage(@Header("Authorization") String authorization,
-                                  @Url String url,
-                                  @Field("statusIcon") String statusIcon,
-                                  @Field("message") String message,
-                                  @Field("clearAt") Long clearAt);
+                                                      @Url String url,
+                                                      @Field("statusIcon") String statusIcon,
+                                                      @Field("message") String message,
+                                                      @Field("clearAt") Long clearAt);
 
     @FormUrlEncoded
     @PUT
     Observable<GenericOverall> setStatusType(@Header("Authorization") String authorization,
-                                                      @Url String url,
-                                                      @Field("statusType") String statusType);
+                                             @Url String url,
+                                             @Field("statusType") String statusType);
 
     @GET
     Observable<StatusesOverall> getUserStatuses(@Header("Authorization") String authorization, @Url String url);
@@ -508,7 +509,7 @@ public interface NcApi {
 
     @POST
     Observable<GenericOverall> sendReaction(@Header("Authorization") String authorization, @Url String url,
-                                 @Query("reaction") String reaction);
+                                            @Query("reaction") String reaction);
 
     @DELETE
     Observable<GenericOverall> deleteReaction(@Header("Authorization") String authorization, @Url String url,

+ 55 - 7
app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt

@@ -103,7 +103,6 @@ import com.nextcloud.talk.BuildConfig
 import com.nextcloud.talk.R
 import com.nextcloud.talk.activities.CallActivity
 import com.nextcloud.talk.activities.MainActivity
-import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
 import com.nextcloud.talk.activities.TakePhotoActivity
 import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
 import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder
@@ -143,6 +142,7 @@ import com.nextcloud.talk.models.json.conversations.RoomsOverall
 import com.nextcloud.talk.models.json.generic.GenericOverall
 import com.nextcloud.talk.models.json.mention.Mention
 import com.nextcloud.talk.presenters.MentionAutocompletePresenter
+import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
 import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
 import com.nextcloud.talk.ui.dialog.AttachmentDialog
 import com.nextcloud.talk.ui.dialog.MessageActionsDialog
@@ -886,6 +886,35 @@ class ChatController(args: Bundle) :
         popupMenu.show()
     }
 
+    private fun showCallButtonMenu(isVoiceOnlyCall: Boolean) {
+        val anchor: View? = if (isVoiceOnlyCall) {
+            activity?.findViewById(R.id.conversation_voice_call)
+        } else {
+            activity?.findViewById(R.id.conversation_video_call)
+        }
+
+        if (anchor != null) {
+            val popupMenu = PopupMenu(
+                ContextThemeWrapper(view?.context, R.style.CallButtonMenu),
+                anchor,
+                Gravity.END
+            )
+            popupMenu.inflate(R.menu.chat_call_menu)
+
+            popupMenu.setOnMenuItemClickListener { item: MenuItem ->
+                when (item.itemId) {
+                    R.id.call_without_notification -> startACall(isVoiceOnlyCall, true)
+                }
+                true
+            }
+
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                popupMenu.setForceShowIcon(true)
+            }
+            popupMenu.show()
+        }
+    }
+
     private fun startPlayback(message: ChatMessage) {
 
         if (!this.isAttached) {
@@ -1827,7 +1856,7 @@ class ChatController(args: Bundle) :
                         }
                         if (startCallFromNotification != null && startCallFromNotification ?: false) {
                             startCallFromNotification = false
-                            startACall(voiceOnly)
+                            startACall(voiceOnly, false)
                         }
                     }
 
@@ -2403,6 +2432,22 @@ class ChatController(args: Bundle) :
         if (CapabilitiesUtil.isAbleToCall(conversationUser)) {
             conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
             conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
+
+            if (CapabilitiesUtil.hasSpreedFeatureCapability(conversationUser, "silent-call")) {
+                Handler().post {
+                    activity?.findViewById<View?>(R.id.conversation_voice_call)?.setOnLongClickListener {
+                        showCallButtonMenu(true)
+                        true
+                    }
+                }
+
+                Handler().post {
+                    activity?.findViewById<View?>(R.id.conversation_video_call)?.setOnLongClickListener {
+                        showCallButtonMenu(false)
+                        true
+                    }
+                }
+            }
         } else {
             menu.removeItem(R.id.conversation_video_call)
             menu.removeItem(R.id.conversation_voice_call)
@@ -2425,11 +2470,11 @@ class ChatController(args: Bundle) :
                 return true
             }
             R.id.conversation_video_call -> {
-                startACall(false)
+                startACall(false, false)
                 return true
             }
             R.id.conversation_voice_call -> {
-                startACall(true)
+                startACall(true, false)
                 return true
             }
             R.id.conversation_info -> {
@@ -2493,19 +2538,19 @@ class ChatController(args: Bundle) :
             currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED
     }
 
-    private fun startACall(isVoiceOnlyCall: Boolean) {
+    private fun startACall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean) {
         if (currentConversation?.canStartCall == false && currentConversation?.hasCall == false) {
             Toast.makeText(context, R.string.startCallForbidden, Toast.LENGTH_LONG).show()
         } else {
             ApplicationWideCurrentRoomHolder.getInstance().isDialing = true
-            val callIntent = getIntentForCall(isVoiceOnlyCall)
+            val callIntent = getIntentForCall(isVoiceOnlyCall, callWithoutNotification)
             if (callIntent != null) {
                 startActivity(callIntent)
             }
         }
     }
 
-    private fun getIntentForCall(isVoiceOnlyCall: Boolean): Intent? {
+    private fun getIntentForCall(isVoiceOnlyCall: Boolean, callWithoutNotification: Boolean): Intent? {
         currentConversation?.let {
             val bundle = Bundle()
             bundle.putString(KEY_ROOM_TOKEN, roomToken)
@@ -2518,6 +2563,9 @@ class ChatController(args: Bundle) :
             if (isVoiceOnlyCall) {
                 bundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true)
             }
+            if (callWithoutNotification) {
+                bundle.putBoolean(BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION, true)
+            }
 
             return if (activity != null) {
                 val callIntent = Intent(activity, CallActivity::class.java)

+ 1 - 0
app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt

@@ -55,6 +55,7 @@ object BundleKeys {
     val KEY_INVITED_EMAIL = "KEY_INVITED_EMAIL"
     val KEY_CONVERSATION_NAME = "KEY_CONVERSATION_NAME"
     val KEY_CALL_VOICE_ONLY = "KEY_CALL_VOICE_ONLY"
+    val KEY_CALL_WITHOUT_NOTIFICATION = "KEY_CALL_WITHOUT_NOTIFICATION"
     val KEY_ACTIVE_CONVERSATION = "KEY_ACTIVE_CONVERSATION"
     val KEY_SERVER_CAPABILITIES = "KEY_SERVER_CAPABILITIES"
     val KEY_FROM_NOTIFICATION_START_CALL = "KEY_FROM_NOTIFICATION_START_CALL"

+ 26 - 0
app/src/main/res/menu/chat_call_menu.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Marcel Hibbe
+  ~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/call_without_notification"
+        android:icon="@drawable/ic_baseline_notifications_off_24"
+        android:title="@string/call_without_notification" />
+</menu>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -522,5 +522,6 @@
 
     <string name="reactions_tab_all">All</string>
     <string name="send_without_notification">Send without notification</string>
+    <string name="call_without_notification">Call without notification</string>
 
 </resources>

+ 2 - 0
app/src/main/res/values/styles.xml

@@ -63,6 +63,8 @@
         <item name="iconTint">@color/fontAppbar</item>
     </style>
 
+    <style name="CallButtonMenu" parent="@style/ChatSendButtonMenu"></style>
+
     <style name="BottomNavigationView" parent="@style/Widget.MaterialComponents.BottomNavigationView">
         <item name="elevation">1dp</item>
     </style>