Improve Call Notification UX when things don't go as planned.

This commit is contained in:
Cody Henthorne
2021-03-31 13:45:28 -04:00
committed by Alex Hart
parent b053fbc4a7
commit d8dead82b6
15 changed files with 442 additions and 27 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}
}