diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index 17b2e83554..4569c5ec00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -67,6 +67,7 @@ public interface BindableConversationItem extends Unbindable { void onSafetyNumberLearnMoreClicked(@NonNull Recipient recipient); void onJoinGroupCallClicked(); void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId); + void onEnableCallNotificationsClicked(); /** @return true if handled, false if you want to let the normal url handling continue */ boolean onUrlClicked(@NonNull String url); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index c33bdaa871..a87b1acbf9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -88,6 +88,7 @@ import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity; import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener; import org.thoughtcrime.securesms.conversation.ConversationAdapter.StickyHeaderViewHolder; import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory; +import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessageDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; @@ -1558,6 +1559,23 @@ public class ConversationFragment extends LoggingFragment { public void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId) { GroupLinkInviteFriendsBottomSheetDialogFragment.show(requireActivity().getSupportFragmentManager(), groupId); } + + @Override + public void onEnableCallNotificationsClicked() { + EnableCallNotificationSettingsDialog.fixAutomatically(requireContext()); + if (EnableCallNotificationSettingsDialog.shouldShow(requireContext())) { + EnableCallNotificationSettingsDialog.show(getChildFragmentManager()); + } else { + refreshList(); + } + } + } + + public void refreshList() { + ConversationAdapter listAdapter = getListAdapter(); + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java index e118b18aeb..d301b34b70 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -23,6 +23,7 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.VerifyIdentityActivity; +import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil; import org.thoughtcrime.securesms.database.model.LiveUpdateMessage; @@ -302,6 +303,14 @@ public final class ConversationUpdateItem extends FrameLayout eventListener.onInviteFriendsToGroupClicked(conversationRecipient.requireGroupId().requireV2()); } }); + } else if ((conversationMessage.getMessageRecord().isMissedAudioCall() || conversationMessage.getMessageRecord().isMissedVideoCall()) && EnableCallNotificationSettingsDialog.shouldShow(getContext())) { + actionButton.setVisibility(VISIBLE); + actionButton.setText(R.string.ConversationUpdateItem_enable_call_notifications); + actionButton.setOnClickListener(v -> { + if (eventListener != null) { + eventListener.onEnableCallNotificationsClicked(); + } + }); } else { actionButton.setVisibility(GONE); actionButton.setOnClickListener(null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/EnableCallNotificationSettingsDialog.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/EnableCallNotificationSettingsDialog.java new file mode 100644 index 0000000000..21c2c78862 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/EnableCallNotificationSettingsDialog.java @@ -0,0 +1,233 @@ +package org.thoughtcrime.securesms.conversation.ui.error; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.conversation.ConversationFragment; +import org.thoughtcrime.securesms.notifications.NotificationChannels; +import org.thoughtcrime.securesms.util.DeviceProperties; +import org.thoughtcrime.securesms.util.TextSecurePreferences; + +/** + * Provide basic steps to fix potential call notification issues based on what we can detect on the system + * and app settings. + */ +@TargetApi(26) +public final class EnableCallNotificationSettingsDialog extends DialogFragment { + + private static final String TAG = Log.tag(EnableCallNotificationSettingsDialog.class); + private static final String FRAGMENT_TAG = "MissedCallCheckSettingsDialog"; + + private static final int NOTIFICATIONS_DISABLED = 1 << 1; + private static final int CALL_NOTIFICATIONS_DISABLED = 1 << 2; + private static final int CALL_CHANNEL_INVALID = 1 << 4; + private static final int BACKGROUND_RESTRICTED = 1 << 8; + + private View view; + + public static boolean shouldShow(@NonNull Context context) { + return getCallNotificationSettingsBitmask(context) != 0; + } + + public static void fixAutomatically(@NonNull Context context) { + if (areCallNotificationsDisabled(context)) { + TextSecurePreferences.setCallNotificationsEnabled(context, true); + Toast.makeText(context, R.string.EnableCallNotificationSettingsDialog__call_notifications_enabled, Toast.LENGTH_SHORT).show(); + } + } + + public static void show(@NonNull FragmentManager fragmentManager) { + if (fragmentManager.findFragmentByTag(FRAGMENT_TAG) != null) { + Log.i(TAG, "Dialog already being shown"); + return; + } + + new EnableCallNotificationSettingsDialog().show(fragmentManager, FRAGMENT_TAG); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(requireContext(), R.style.Signal_ThemeOverlay_Dialog_Rounded); + + Runnable action = null; + switch (getCallNotificationSettingsBitmask(requireContext())) { + case NOTIFICATIONS_DISABLED: + dialogBuilder.setTitle(R.string.EnableCallNotificationSettingsDialog__enable_call_notifications) + .setMessage(R.string.EnableCallNotificationSettingsDialog__to_receive_call_notifications_tap_settings_and_turn_on_show_notifications) + .setPositiveButton(R.string.EnableCallNotificationSettingsDialog__settings, null); + action = this::showNotificationSettings; + break; + case CALL_CHANNEL_INVALID: + dialogBuilder.setTitle(R.string.EnableCallNotificationSettingsDialog__enable_call_notifications) + .setMessage(R.string.EnableCallNotificationSettingsDialog__to_receive_call_notifications_tap_settings_and_turn_on_notifications) + .setPositiveButton(R.string.EnableCallNotificationSettingsDialog__settings, null); + action = this::showNotificationChannelSettings; + break; + case BACKGROUND_RESTRICTED: + dialogBuilder.setTitle(R.string.EnableCallNotificationSettingsDialog__enable_background_activity) + .setMessage(R.string.EnableCallNotificationSettingsDialog__to_receive_call_notifications_tap_settings_and_enable_background_activity_in_battery_settings) + .setPositiveButton(R.string.EnableCallNotificationSettingsDialog__settings, null); + action = this::showAppSettings; + break; + default: + dialogBuilder.setTitle(R.string.EnableCallNotificationSettingsDialog__enable_call_notifications) + .setView(createView()) + .setPositiveButton(android.R.string.ok, null); + break; + } + + dialogBuilder.setNegativeButton(android.R.string.cancel, null); + + AlertDialog dialog = dialogBuilder.create(); + + if (action != null) { + final Runnable localAction = action; + dialog.setOnShowListener(d -> dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> localAction.run())); + } + + return dialog; + } + + @Override + public void onResume() { + super.onResume(); + if (getCallNotificationSettingsBitmask(requireContext()) == 0) { + dismissAllowingStateLoss(); + } else if (view != null) { + bind(view); + } + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + super.onDismiss(dialog); + if (getParentFragment() instanceof ConversationFragment) { + ((ConversationFragment) getParentFragment()).refreshList(); + } + } + + @SuppressLint("InflateParams") + private @NonNull View createView() { + view = LayoutInflater.from(getContext()).inflate(R.layout.enable_call_notification_settings_dialog_fragment, null, false); + bind(view); + return view; + } + + private void bind(@NonNull View view) { + TextView allConfigured = view.findViewById(R.id.enable_call_notification_settings_dialog_system_all_configured); + AppCompatImageView systemSettingIndicator = view.findViewById(R.id.enable_call_notification_settings_dialog_system_setting_indicator); + TextView systemSettingText = view.findViewById(R.id.enable_call_notification_settings_dialog_system_setting_text); + AppCompatImageView channelSettingIndicator = view.findViewById(R.id.enable_call_notification_settings_dialog_channel_setting_indicator); + TextView channelSettingText = view.findViewById(R.id.enable_call_notification_settings_dialog_channel_setting_text); + AppCompatImageView backgroundRestrictedIndicator = view.findViewById(R.id.enable_call_notification_settings_dialog_background_restricted_indicator); + TextView backgroundRestrictedText = view.findViewById(R.id.enable_call_notification_settings_dialog_background_restricted_text); + + if (areNotificationsDisabled(requireContext())) { + systemSettingIndicator.setVisibility(View.VISIBLE); + systemSettingText.setVisibility(View.VISIBLE); + systemSettingText.setOnClickListener(v -> showNotificationSettings()); + } else { + systemSettingIndicator.setVisibility(View.GONE); + systemSettingText.setVisibility(View.GONE); + systemSettingText.setOnClickListener(null); + } + + if (isCallChannelInvalid(requireContext())) { + channelSettingIndicator.setVisibility(View.VISIBLE); + channelSettingText.setVisibility(View.VISIBLE); + channelSettingText.setOnClickListener(v -> showNotificationChannelSettings()); + } else { + channelSettingIndicator.setVisibility(View.GONE); + channelSettingText.setVisibility(View.GONE); + channelSettingText.setOnClickListener(null); + } + + if (isBackgroundRestricted(requireContext())) { + backgroundRestrictedIndicator.setVisibility(View.VISIBLE); + backgroundRestrictedText.setVisibility(View.VISIBLE); + backgroundRestrictedText.setOnClickListener(v -> showAppSettings()); + } else { + backgroundRestrictedIndicator.setVisibility(View.GONE); + backgroundRestrictedText.setVisibility(View.GONE); + backgroundRestrictedText.setOnClickListener(null); + } + + allConfigured.setVisibility(shouldShow(requireContext()) ? View.GONE : View.VISIBLE); + } + + private void showNotificationSettings() { + Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().getPackageName()); + startActivity(intent); + } + + private void showNotificationChannelSettings() { + NotificationChannels.openChannelSettings(requireContext(), NotificationChannels.CALLS); + } + + private void showAppSettings() { + Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", requireContext().getPackageName(), null)); + startActivity(intent); + } + + private static boolean areNotificationsDisabled(@NonNull Context context) { + return !NotificationChannels.areNotificationsEnabled(context); + } + + private static boolean areCallNotificationsDisabled(Context context) { + return !TextSecurePreferences.isCallNotificationsEnabled(context); + } + + private static boolean isCallChannelInvalid(Context context) { + return !NotificationChannels.isCallsChannelValid(context); + } + + private static boolean isBackgroundRestricted(Context context) { + return Build.VERSION.SDK_INT >= 28 && DeviceProperties.isBackgroundRestricted(context); + } + + private static int getCallNotificationSettingsBitmask(@NonNull Context context) { + int bitmask = 0; + + if (areNotificationsDisabled(context)) { + bitmask |= NOTIFICATIONS_DISABLED; + } + + if (areCallNotificationsDisabled(context)) { + bitmask |= CALL_NOTIFICATIONS_DISABLED; + } + + if (isCallChannelInvalid(context)) { + bitmask |= CALL_CHANNEL_INVALID; + } + + if (isBackgroundRestricted(context)) { + bitmask |= BACKGROUND_RESTRICTED; + } + + return bitmask; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java index 1d2cb3597a..47e0480032 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -4,6 +4,7 @@ import android.annotation.TargetApi; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.graphics.Color; @@ -13,6 +14,7 @@ import android.os.AsyncTask; import android.os.Build; import android.provider.Settings; import android.text.TextUtils; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -219,10 +221,15 @@ public class NotificationChannels { return; } - Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); - intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); - context.startActivity(intent); + try { + Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Channel settings activity not found", e); + Toast.makeText(context, R.string.NotificationChannels__no_activity_available_to_open_notification_channel_settings, Toast.LENGTH_SHORT).show(); + } } /** @@ -413,6 +420,16 @@ public class NotificationChannels { return group != null && !group.isBlocked(); } + public static boolean isCallsChannelValid(@NonNull Context context) { + if (!supported()) { + return true; + } + + NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); + NotificationChannel channel = notificationManager.getNotificationChannel(CALLS); + + return channel != null && channel.getImportance() == NotificationManager.IMPORTANCE_HIGH; + } /** * Whether or not notifications for the entire app are enabled. diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java index fba10f50c3..f709ad932a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -31,10 +31,8 @@ import org.signal.ringrtc.HttpHeader; import org.signal.ringrtc.Remote; import org.signal.storageservice.protos.groups.GroupExternalCredential; import org.signal.zkgroup.VerificationFailedException; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.WebRtcCallActivity; -import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -57,6 +55,7 @@ import org.thoughtcrime.securesms.service.webrtc.IdleActionProcessor; import org.thoughtcrime.securesms.service.webrtc.WebRtcInteractor; import org.thoughtcrime.securesms.service.webrtc.WebRtcUtil; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; +import org.thoughtcrime.securesms.util.AppForegroundObserver; import org.thoughtcrime.securesms.util.BubbleUtil; import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.ListenableFutureTask; @@ -90,10 +89,14 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import static org.thoughtcrime.securesms.events.WebRtcViewModel.GroupCallState.IDLE; +import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.CALL_INCOMING; + public class WebRtcCallService extends Service implements CallManager.Observer, - BluetoothStateManager.BluetoothStateListener, - CameraEventListener, - GroupCall.Observer + BluetoothStateManager.BluetoothStateListener, + CameraEventListener, + GroupCall.Observer, + AppForegroundObserver.Listener { private static final String TAG = Log.tag(WebRtcCallService.class); @@ -276,6 +279,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, new SignalAudioManager(this), bluetoothStateManager, this, + this, this); return true; } @@ -479,15 +483,16 @@ public class WebRtcCallService extends Service implements CallManager.Observer, return listenableFutureTask; } - public void startCallCardActivityIfPossible() { + public boolean startCallCardActivityIfPossible() { if (Build.VERSION.SDK_INT >= 29 && !ApplicationDependencies.getAppForegroundObserver().isForegrounded()) { - return; + return false; } Intent activityIntent = new Intent(); activityIntent.setClass(this, WebRtcCallActivity.class); activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(activityIntent); + return true; } private static @NonNull OfferMessage.Type getOfferTypeFromCallMediaType(@NonNull CallManager.CallMediaType mediaType) { @@ -516,6 +521,18 @@ public class WebRtcCallService extends Service implements CallManager.Observer, return null; } + @Override + public void onForeground() { + WebRtcViewModel.State callState = serviceState.getCallInfoState().getCallState(); + if (callState == CALL_INCOMING && serviceState.getCallInfoState().getGroupCallState() == IDLE) { + startCallCardActivityIfPossible(); + } + ApplicationDependencies.getAppForegroundObserver().removeListener(this); + } + + @Override + public void onBackground() { } + private static class WiredHeadsetStateReceiver extends BroadcastReceiver { @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/CallSetupActionProcessorDelegate.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/CallSetupActionProcessorDelegate.java index 61e00d20de..cb0a148e98 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/CallSetupActionProcessorDelegate.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/CallSetupActionProcessorDelegate.java @@ -5,6 +5,7 @@ import androidx.annotation.NonNull; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallManager; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.ringrtc.CallState; import org.thoughtcrime.securesms.ringrtc.Camera; @@ -37,6 +38,7 @@ public class CallSetupActionProcessorDelegate extends WebRtcActionProcessor { RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer(); + ApplicationDependencies.getAppForegroundObserver().removeListener(webRtcInteractor.getForegroundListener()); webRtcInteractor.startAudioCommunication(activePeer.getState() == CallState.REMOTE_RINGING); webRtcInteractor.setWantsBluetoothConnection(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java index 7071469ea5..59ff052d49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java @@ -12,6 +12,7 @@ import org.signal.ringrtc.CallId; import org.thoughtcrime.securesms.components.webrtc.OrientationAwareVideoSink; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.notifications.DoNotDisturbUtil; @@ -157,7 +158,11 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { boolean shouldDisturbUserWithCall = DoNotDisturbUtil.shouldDisturbUserWithCall(context.getApplicationContext(), recipient); if (shouldDisturbUserWithCall) { - webRtcInteractor.startWebRtcCallActivityIfPossible(); + boolean started = webRtcInteractor.startWebRtcCallActivityIfPossible(); + if (!started) { + Log.i(TAG, "Unable to start call activity due to OS version or not being in the foreground"); + ApplicationDependencies.getAppForegroundObserver().addListener(webRtcInteractor.getForegroundListener()); + } } webRtcInteractor.initializeAudioForCall(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java index 92f7ec93e8..9456a42863 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java @@ -13,6 +13,7 @@ import org.signal.ringrtc.CallId; import org.signal.ringrtc.GroupCall; import org.thoughtcrime.securesms.components.sensors.Orientation; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -723,6 +724,8 @@ public abstract class WebRtcActionProcessor { return currentState; } + ApplicationDependencies.getAppForegroundObserver().removeListener(webRtcInteractor.getForegroundListener()); + webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING); webRtcInteractor.stopForegroundService(); boolean playDisconnectSound = (activePeer.getState() == CallState.DIALING) || diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java index 465177b98f..8dd7693a72 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.ringrtc.CameraEventListener; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; +import org.thoughtcrime.securesms.util.AppForegroundObserver; import org.thoughtcrime.securesms.webrtc.audio.BluetoothStateManager; import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger; import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager; @@ -29,13 +30,14 @@ import java.util.UUID; */ public class WebRtcInteractor { - @NonNull private final WebRtcCallService webRtcCallService; - @NonNull private final CallManager callManager; - @NonNull private final LockManager lockManager; - @NonNull private final SignalAudioManager audioManager; - @NonNull private final BluetoothStateManager bluetoothStateManager; - @NonNull private final CameraEventListener cameraEventListener; - @NonNull private final GroupCall.Observer groupCallObserver; + @NonNull private final WebRtcCallService webRtcCallService; + @NonNull private final CallManager callManager; + @NonNull private final LockManager lockManager; + @NonNull private final SignalAudioManager audioManager; + @NonNull private final BluetoothStateManager bluetoothStateManager; + @NonNull private final CameraEventListener cameraEventListener; + @NonNull private final GroupCall.Observer groupCallObserver; + @NonNull private final AppForegroundObserver.Listener foregroundListener; public WebRtcInteractor(@NonNull WebRtcCallService webRtcCallService, @NonNull CallManager callManager, @@ -43,7 +45,8 @@ public class WebRtcInteractor { @NonNull SignalAudioManager audioManager, @NonNull BluetoothStateManager bluetoothStateManager, @NonNull CameraEventListener cameraEventListener, - @NonNull GroupCall.Observer groupCallObserver) + @NonNull GroupCall.Observer groupCallObserver, + @NonNull AppForegroundObserver.Listener foregroundListener) { this.webRtcCallService = webRtcCallService; this.callManager = callManager; @@ -52,6 +55,7 @@ public class WebRtcInteractor { this.bluetoothStateManager = bluetoothStateManager; this.cameraEventListener = cameraEventListener; this.groupCallObserver = groupCallObserver; + this.foregroundListener = foregroundListener; } @NonNull CameraEventListener getCameraEventListener() { @@ -70,6 +74,10 @@ public class WebRtcInteractor { return groupCallObserver; } + @NonNull AppForegroundObserver.Listener getForegroundListener() { + return foregroundListener; + } + void setWantsBluetoothConnection(boolean enabled) { bluetoothStateManager.setWantsConnection(enabled); } @@ -118,8 +126,8 @@ public class WebRtcInteractor { webRtcCallService.insertMissedCall(remotePeer, signal, timestamp, isVideoOffer); } - void startWebRtcCallActivityIfPossible() { - webRtcCallService.startCallCardActivityIfPossible(); + boolean startWebRtcCallActivityIfPossible() { + return webRtcCallService.startCallCardActivityIfPossible(); } void registerPowerButtonReceiver() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 4f27cd446d..168c718ea1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -987,6 +987,10 @@ public class TextSecurePreferences { return getBooleanPreference(context, NOTIFICATION_PREF, true); } + public static void setCallNotificationsEnabled(Context context, boolean enabled) { + setBooleanPreference(context, CALL_NOTIFICATIONS_PREF, enabled); + } + public static boolean isCallNotificationsEnabled(Context context) { return getBooleanPreference(context, CALL_NOTIFICATIONS_PREF, true); } diff --git a/app/src/main/res/drawable/ic_check_24.xml b/app/src/main/res/drawable/ic_check_24.xml index 3f70fa3fe8..6554551de0 100644 --- a/app/src/main/res/drawable/ic_check_24.xml +++ b/app/src/main/res/drawable/ic_check_24.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_info_white_24.xml b/app/src/main/res/drawable/ic_info_white_24.xml index e0e1e9d88a..27437af495 100644 --- a/app/src/main/res/drawable/ic_info_white_24.xml +++ b/app/src/main/res/drawable/ic_info_white_24.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/layout/enable_call_notification_settings_dialog_fragment.xml b/app/src/main/res/layout/enable_call_notification_settings_dialog_fragment.xml new file mode 100644 index 0000000000..075132a598 --- /dev/null +++ b/app/src/main/res/layout/enable_call_notification_settings_dialog_fragment.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9020c8e5ca..8386f7c7de 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -640,7 +640,7 @@ Error revoking invite Error revoking invites - + Pending member requests No member requests to show. @@ -799,7 +799,7 @@ Enabled Disabled Default - + Shareable group link Manage & share @@ -1722,7 +1722,7 @@ Messages Unknown Voice Notes - + No activity available to open notification channel settings. @@ -1871,6 +1871,7 @@ Return to call Call is full Invite friends + Enable Call Notifications Play … Pause @@ -1899,6 +1900,19 @@ View Previous verified + + Call notifications enabled. + Enable call notifications + Enable background activity + Everything looks good now! + To receive call notifications, tap here and turn on \"Show notifications.\" + To receive call notifications, tap here and turn on notifications and make sure Sound and Pop-up are enabled. + To receive call notifications, tap here and enable background activity in \"Battery\" settings. + Settings + To receive call notifications, tap Settings and turn on \"Show notifications.\" + To receive call notifications, tap Settings and turn on notifications and make sure Sound and Pop-up are enabled. + To receive call notifications, tap Settings and enable background activity in \"Battery\" settings. + Loading countries… Search