dialpad and gateway interaction
This commit is contained in:
parent
8ff365613a
commit
18c41eb05e
|
@ -79,7 +79,7 @@ dependencies {
|
||||||
implementation "com.squareup.okhttp3:okhttp:4.10.0"
|
implementation "com.squareup.okhttp3:okhttp:4.10.0"
|
||||||
|
|
||||||
implementation 'com.google.guava:guava:31.1-android'
|
implementation 'com.google.guava:guava:31.1-android'
|
||||||
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.49'
|
implementation 'io.michaelrocks:libphonenumber-android:8.12.49'
|
||||||
implementation 'im.conversations.webrtc:webrtc-android:104.0.0'
|
implementation 'im.conversations.webrtc:webrtc-android:104.0.0'
|
||||||
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
|
|
@ -2,10 +2,103 @@ package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
import io.michaelrocks.libphonenumber.android.NumberParseException;
|
||||||
|
import io.michaelrocks.libphonenumber.android.PhoneNumberUtil;
|
||||||
|
import io.michaelrocks.libphonenumber.android.Phonenumber;
|
||||||
|
|
||||||
public class PhoneNumberUtilWrapper {
|
public class PhoneNumberUtilWrapper {
|
||||||
|
|
||||||
|
private static volatile PhoneNumberUtil instance;
|
||||||
|
|
||||||
|
|
||||||
|
public static String getCountryForCode(String code) {
|
||||||
|
Locale locale = new Locale("", code);
|
||||||
|
return locale.getDisplayCountry();
|
||||||
|
}
|
||||||
|
|
||||||
public static String toFormattedPhoneNumber(Context context, Jid jid) {
|
public static String toFormattedPhoneNumber(Context context, Jid jid) {
|
||||||
throw new AssertionError("This method is not implemented in Conversations");
|
try {
|
||||||
|
return getInstance(context).format(toPhoneNumber(context, jid), PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL).replace(' ','\u202F');
|
||||||
|
} catch (Exception e) {
|
||||||
|
return jid.getEscapedLocal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Phonenumber.PhoneNumber toPhoneNumber(Context context, Jid jid) throws NumberParseException {
|
||||||
|
return getInstance(context).parse(jid.getEscapedLocal(), "de");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalize(Context context, String input) throws IllegalArgumentException, NumberParseException {
|
||||||
|
return normalize(context, input, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalize(Context context, String input, boolean preferNetwork) throws IllegalArgumentException, NumberParseException {
|
||||||
|
final Phonenumber.PhoneNumber number = getInstance(context).parse(input, LocationProvider.getUserCountry(context, preferNetwork));
|
||||||
|
if (!getInstance(context).isValidNumber(number)) {
|
||||||
|
throw new IllegalArgumentException(String.format("%s is not a valid phone number", input));
|
||||||
|
}
|
||||||
|
return normalize(context, number);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalize(Context context, Phonenumber.PhoneNumber phoneNumber) {
|
||||||
|
return getInstance(context).format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PhoneNumberUtil getInstance(final Context context) {
|
||||||
|
PhoneNumberUtil localInstance = instance;
|
||||||
|
if (localInstance == null) {
|
||||||
|
synchronized (PhoneNumberUtilWrapper.class) {
|
||||||
|
localInstance = instance;
|
||||||
|
if (localInstance == null) {
|
||||||
|
instance = localInstance = PhoneNumberUtil.createInstance(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return localInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Country> getCountries(final Context context) {
|
||||||
|
List<Country> countries = new ArrayList<>();
|
||||||
|
for (String region : getInstance(context).getSupportedRegions()) {
|
||||||
|
countries.add(new Country(region, getInstance(context).getCountryCodeForRegion(region)));
|
||||||
|
}
|
||||||
|
return countries;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Country implements Comparable<Country> {
|
||||||
|
private final String name;
|
||||||
|
private final String region;
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
Country(String region, int code) {
|
||||||
|
this.name = getCountryForCode(region);
|
||||||
|
this.region = region;
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRegion() {
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return '+' + String.valueOf(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Country o) {
|
||||||
|
return name.compareTo(o.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -149,6 +149,22 @@ public class Presences {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean anyIdentity(final String category, final String type) {
|
||||||
|
synchronized (this.presences) {
|
||||||
|
if (this.presences.size() == 0) {
|
||||||
|
// https://github.com/iNPUTmice/Conversations/issues/4230
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (Presence presence : this.presences.values()) {
|
||||||
|
ServiceDiscoveryResult disco = presence.getServiceDiscoveryResult();
|
||||||
|
if (disco != null && disco.hasIdentity(category, type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public Pair<Map<String, String>, Map<String, String>> toTypeAndNameMap() {
|
public Pair<Map<String, String>, Map<String, String>> toTypeAndNameMap() {
|
||||||
Map<String, String> typeMap = new HashMap<>();
|
Map<String, String> typeMap = new HashMap<>();
|
||||||
Map<String, String> nameMap = new HashMap<>();
|
Map<String, String> nameMap = new HashMap<>();
|
||||||
|
|
|
@ -153,6 +153,7 @@ import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.OnBindListener;
|
import eu.siacs.conversations.xmpp.OnBindListener;
|
||||||
import eu.siacs.conversations.xmpp.OnContactStatusChanged;
|
import eu.siacs.conversations.xmpp.OnContactStatusChanged;
|
||||||
|
import eu.siacs.conversations.xmpp.OnGatewayResult;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||||
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
|
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
|
||||||
import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
|
import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
|
||||||
|
@ -3376,6 +3377,24 @@ public class XmppConnectionService extends Service {
|
||||||
fetchConferenceConfiguration(conversation, null);
|
fetchConferenceConfiguration(conversation, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkIfMuc(final Account account, final Jid jid, eu.siacs.conversations.utils.Consumer<Boolean> cb) {
|
||||||
|
if (jid.isDomainJid()) {
|
||||||
|
// Spec basically says MUC needs to have a node
|
||||||
|
// And also specifies that MUC and MUC service should have the same identity...
|
||||||
|
cb.accept(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IqPacket request = mIqGenerator.queryDiscoInfo(jid.asBareJid());
|
||||||
|
sendIqPacket(account, request, (acct, reply) -> {
|
||||||
|
ServiceDiscoveryResult result = new ServiceDiscoveryResult(reply);
|
||||||
|
cb.accept(
|
||||||
|
result.getFeatures().contains("http://jabber.org/protocol/muc") &&
|
||||||
|
result.hasIdentity("conference", null)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) {
|
public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) {
|
||||||
IqPacket request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid());
|
IqPacket request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid());
|
||||||
sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() {
|
sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() {
|
||||||
|
@ -4741,6 +4760,24 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void fetchFromGateway(Account account, final Jid jid, final String input, final OnGatewayResult callback) {
|
||||||
|
IqPacket request = new IqPacket(input == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET);
|
||||||
|
request.setTo(jid);
|
||||||
|
Element query = request.query("jabber:iq:gateway");
|
||||||
|
if (input != null) {
|
||||||
|
Element prompt = query.addChild("prompt");
|
||||||
|
prompt.setContent(input);
|
||||||
|
}
|
||||||
|
sendIqPacket(account, request, (Account acct, IqPacket packet) -> {
|
||||||
|
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
||||||
|
callback.onGatewayResult(packet.query().findChildContent(input == null ? "prompt" : "jid"), null);
|
||||||
|
} else {
|
||||||
|
Element error = packet.findChild("error");
|
||||||
|
callback.onGatewayResult(null, error == null ? null : error.findChildContent("text"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void fetchCaps(Account account, final Jid jid, final Presence presence) {
|
public void fetchCaps(Account account, final Jid jid, final Presence presence) {
|
||||||
final Pair<String, String> key = new Pair<>(presence.getHash(), presence.getVer());
|
final Pair<String, String> key = new Pair<>(presence.getHash(), presence.getVer());
|
||||||
final ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key);
|
final ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key);
|
||||||
|
|
|
@ -80,12 +80,14 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
|
||||||
getString(R.string.block_jabber_id),
|
getString(R.string.block_jabber_id),
|
||||||
getString(R.string.block),
|
getString(R.string.block),
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
account.getJid().asBareJid().toEscapedString(),
|
account.getJid().asBareJid().toEscapedString(),
|
||||||
true,
|
true,
|
||||||
false
|
false,
|
||||||
|
EnterJidDialog.SanityCheck.NO
|
||||||
);
|
);
|
||||||
|
|
||||||
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
|
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid, x, y) -> {
|
||||||
Blockable blockable = new RawBlockable(account, contactJid);
|
Blockable blockable = new RawBlockable(account, contactJid);
|
||||||
if (xmppConnectionService.sendBlockRequest(blockable, false)) {
|
if (xmppConnectionService.sendBlockRequest(blockable, false)) {
|
||||||
Toast.makeText(BlocklistActivity.this, R.string.corresponding_conversations_closed, Toast.LENGTH_SHORT).show();
|
Toast.makeText(BlocklistActivity.this, R.string.corresponding_conversations_closed, Toast.LENGTH_SHORT).show();
|
||||||
|
|
|
@ -314,13 +314,15 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
|
||||||
mActivatedAccounts,
|
mActivatedAccounts,
|
||||||
getString(R.string.enter_contact),
|
getString(R.string.enter_contact),
|
||||||
getString(R.string.select),
|
getString(R.string.select),
|
||||||
|
null,
|
||||||
jid == null ? null : jid.asBareJid().toString(),
|
jid == null ? null : jid.asBareJid().toString(),
|
||||||
getIntent().getStringExtra(EXTRA_ACCOUNT),
|
getIntent().getStringExtra(EXTRA_ACCOUNT),
|
||||||
true,
|
true,
|
||||||
false
|
false,
|
||||||
|
EnterJidDialog.SanityCheck.NO
|
||||||
);
|
);
|
||||||
|
|
||||||
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
|
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid, x, y) -> {
|
||||||
final Intent request = getIntent();
|
final Intent request = getIntent();
|
||||||
final Intent data = new Intent();
|
final Intent data = new Intent();
|
||||||
data.putExtra("contact", contactJid.toString());
|
data.putExtra("contact", contactJid.toString());
|
||||||
|
|
|
@ -2,73 +2,106 @@ package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.InputType;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.michaelrocks.libphonenumber.android.NumberParseException;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.databinding.EnterJidDialogBinding;
|
import eu.siacs.conversations.databinding.EnterJidDialogBinding;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.entities.Contact;
|
||||||
|
import eu.siacs.conversations.entities.Presence;
|
||||||
|
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
|
||||||
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
|
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
|
||||||
import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
|
import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
|
||||||
import eu.siacs.conversations.ui.util.DelayedHintHelper;
|
import eu.siacs.conversations.ui.util.DelayedHintHelper;
|
||||||
|
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
import eu.siacs.conversations.xmpp.OnGatewayResult;
|
||||||
|
|
||||||
public class EnterJidDialog extends DialogFragment implements OnBackendConnected, TextWatcher {
|
public class EnterJidDialog extends DialogFragment implements OnBackendConnected, TextWatcher {
|
||||||
|
|
||||||
private static final List<String> SUSPICIOUS_DOMAINS =
|
private static final List<String> SUSPICIOUS_DOMAINS =
|
||||||
Arrays.asList("conference", "muc", "room", "rooms", "chat");
|
Arrays.asList("conference", "muc", "room", "rooms");
|
||||||
|
|
||||||
private OnEnterJidDialogPositiveListener mListener = null;
|
private OnEnterJidDialogPositiveListener mListener = null;
|
||||||
|
|
||||||
private static final String TITLE_KEY = "title";
|
private static final String TITLE_KEY = "title";
|
||||||
private static final String POSITIVE_BUTTON_KEY = "positive_button";
|
private static final String POSITIVE_BUTTON_KEY = "positive_button";
|
||||||
|
private static final String SECONDARY_BUTTON_KEY = "secondary_button";
|
||||||
private static final String PREFILLED_JID_KEY = "prefilled_jid";
|
private static final String PREFILLED_JID_KEY = "prefilled_jid";
|
||||||
private static final String ACCOUNT_KEY = "account";
|
private static final String ACCOUNT_KEY = "account";
|
||||||
private static final String ALLOW_EDIT_JID_KEY = "allow_edit_jid";
|
private static final String ALLOW_EDIT_JID_KEY = "allow_edit_jid";
|
||||||
private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list";
|
private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list";
|
||||||
private static final String SANITY_CHECK_JID = "sanity_check_jid";
|
private static final String SANITY_CHECK_JID = "sanity_check_jid";
|
||||||
|
private static final String SHOW_BOOKMARK_CHECKBOX = "show_bookmark_checkbox";
|
||||||
|
|
||||||
private KnownHostsAdapter knownHostsAdapter;
|
private KnownHostsAdapter knownHostsAdapter;
|
||||||
private Collection<String> whitelistedDomains = Collections.emptyList();
|
private Collection<String> whitelistedDomains = Collections.emptyList();
|
||||||
|
|
||||||
private EnterJidDialogBinding binding;
|
private EnterJidDialogBinding binding;
|
||||||
private AlertDialog dialog;
|
private AlertDialog dialog;
|
||||||
private boolean sanityCheckJid = false;
|
private SanityCheck sanityCheckJid = SanityCheck.NO;
|
||||||
|
|
||||||
private boolean issuedWarning = false;
|
private boolean issuedWarning = false;
|
||||||
|
private GatewayListAdapter gatewayListAdapter = new GatewayListAdapter();
|
||||||
|
|
||||||
|
public static enum SanityCheck {
|
||||||
|
NO,
|
||||||
|
YES,
|
||||||
|
ALLOW_MUC
|
||||||
|
}
|
||||||
|
|
||||||
public static EnterJidDialog newInstance(
|
public static EnterJidDialog newInstance(
|
||||||
final List<String> activatedAccounts,
|
final List<String> activatedAccounts,
|
||||||
final String title,
|
final String title,
|
||||||
final String positiveButton,
|
final String positiveButton,
|
||||||
|
final String secondaryButton,
|
||||||
final String prefilledJid,
|
final String prefilledJid,
|
||||||
final String account,
|
final String account,
|
||||||
boolean allowEditJid,
|
boolean allowEditJid,
|
||||||
final boolean sanity_check_jid) {
|
boolean showBookmarkCheckbox,
|
||||||
|
final SanityCheck sanity_check_jid) {
|
||||||
EnterJidDialog dialog = new EnterJidDialog();
|
EnterJidDialog dialog = new EnterJidDialog();
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString(TITLE_KEY, title);
|
bundle.putString(TITLE_KEY, title);
|
||||||
bundle.putString(POSITIVE_BUTTON_KEY, positiveButton);
|
bundle.putString(POSITIVE_BUTTON_KEY, positiveButton);
|
||||||
|
bundle.putString(SECONDARY_BUTTON_KEY, secondaryButton);
|
||||||
bundle.putString(PREFILLED_JID_KEY, prefilledJid);
|
bundle.putString(PREFILLED_JID_KEY, prefilledJid);
|
||||||
bundle.putString(ACCOUNT_KEY, account);
|
bundle.putString(ACCOUNT_KEY, account);
|
||||||
bundle.putBoolean(ALLOW_EDIT_JID_KEY, allowEditJid);
|
bundle.putBoolean(ALLOW_EDIT_JID_KEY, allowEditJid);
|
||||||
bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList<String>) activatedAccounts);
|
bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList<String>) activatedAccounts);
|
||||||
bundle.putBoolean(SANITY_CHECK_JID, sanity_check_jid);
|
bundle.putInt(SANITY_CHECK_JID, sanity_check_jid.ordinal());
|
||||||
|
bundle.putBoolean(SHOW_BOOKMARK_CHECKBOX, showBookmarkCheckbox);
|
||||||
dialog.setArguments(bundle);
|
dialog.setArguments(bundle);
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
@ -110,7 +143,11 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
binding.jid.setCursorVisible(false);
|
binding.jid.setCursorVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sanityCheckJid = getArguments().getBoolean(SANITY_CHECK_JID, false);
|
sanityCheckJid = SanityCheck.values()[getArguments().getInt(SANITY_CHECK_JID, SanityCheck.NO.ordinal())];
|
||||||
|
|
||||||
|
if (!getArguments().getBoolean(SHOW_BOOKMARK_CHECKBOX, false)) {
|
||||||
|
binding.bookmark.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
DelayedHintHelper.setHint(R.string.account_settings_example_jabber_id, binding.jid);
|
DelayedHintHelper.setHint(R.string.account_settings_example_jabber_id, binding.jid);
|
||||||
|
|
||||||
|
@ -129,64 +166,112 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
binding.account.setAdapter(adapter);
|
binding.account.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setView(binding.getRoot());
|
binding.gatewayList.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
binding.gatewayList.setAdapter(gatewayListAdapter);
|
||||||
builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null);
|
gatewayListAdapter.setOnEmpty(() -> binding.gatewayList.setVisibility(View.GONE));
|
||||||
this.dialog = builder.create();
|
gatewayListAdapter.setOnNonEmpty(() -> binding.gatewayList.setVisibility(View.VISIBLE));
|
||||||
|
|
||||||
View.OnClickListener dialogOnClick =
|
binding.account.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
v -> {
|
@Override
|
||||||
handleEnter(binding, account);
|
public void onItemSelected(AdapterView accountSpinner, View view, int position, long id) {
|
||||||
};
|
XmppActivity context = (XmppActivity) getActivity();
|
||||||
|
if (context == null || context.xmppConnectionService == null || accountJid() == null) return;
|
||||||
|
|
||||||
|
gatewayListAdapter.clear();
|
||||||
|
final Account account = context.xmppConnectionService.findAccountByJid(accountJid());
|
||||||
|
if (account == null) return;
|
||||||
|
|
||||||
|
for (final Contact contact : account.getRoster().getContacts()) {
|
||||||
|
if (contact.showInRoster() && (contact.getPresences().anyIdentity("gateway", null) || contact.getPresences().anySupport("jabber:iq:gateway"))) {
|
||||||
|
context.xmppConnectionService.fetchFromGateway(account, contact.getJid(), null, (final String prompt, String errorMessage) -> {
|
||||||
|
if (prompt == null && !contact.getPresences().anyIdentity("gateway", null)) return;
|
||||||
|
|
||||||
|
context.runOnUiThread(() -> {
|
||||||
|
gatewayListAdapter.add(contact, prompt);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView accountSpinner) {
|
||||||
|
gatewayListAdapter.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.setView(binding.getRoot());
|
||||||
|
builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null);
|
||||||
|
if (getArguments().getString(SECONDARY_BUTTON_KEY) == null) {
|
||||||
|
builder.setNegativeButton(R.string.cancel, null);
|
||||||
|
} else {
|
||||||
|
builder.setNegativeButton(getArguments().getString(SECONDARY_BUTTON_KEY), null);
|
||||||
|
builder.setNeutralButton(R.string.cancel, null);
|
||||||
|
}
|
||||||
|
this.dialog = builder.create();
|
||||||
|
|
||||||
binding.jid.setOnEditorActionListener(
|
binding.jid.setOnEditorActionListener(
|
||||||
(v, actionId, event) -> {
|
(v, actionId, event) -> {
|
||||||
handleEnter(binding, account);
|
handleEnter(binding, account, false);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(dialogOnClick);
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener((v) -> handleEnter(binding, account, false));
|
||||||
|
if (getArguments().getString(SECONDARY_BUTTON_KEY) != null) {
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener((v) -> handleEnter(binding, account, true));
|
||||||
|
}
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleEnter(EnterJidDialogBinding binding, String account) {
|
protected Jid accountJid() {
|
||||||
final Jid accountJid;
|
try {
|
||||||
|
if (Config.DOMAIN_LOCK != null) {
|
||||||
|
return Jid.ofEscaped((String) binding.account.getSelectedItem(), Config.DOMAIN_LOCK, null);
|
||||||
|
} else {
|
||||||
|
return Jid.ofEscaped((String) binding.account.getSelectedItem());
|
||||||
|
}
|
||||||
|
} catch (final IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEnter(EnterJidDialogBinding binding, String account, boolean secondary) {
|
||||||
if (!binding.account.isEnabled() && account == null) {
|
if (!binding.account.isEnabled() && account == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
final Jid accountJid = accountJid();
|
||||||
if (Config.DOMAIN_LOCK != null) {
|
final OnGatewayResult finish = (final String jidString, final String errorMessage) -> {
|
||||||
accountJid =
|
Activity context = getActivity();
|
||||||
Jid.ofEscaped(
|
if (context == null) return; // Race condition, we got the reply after the UI was closed
|
||||||
(String) binding.account.getSelectedItem(),
|
|
||||||
Config.DOMAIN_LOCK,
|
context.runOnUiThread(() -> {
|
||||||
null);
|
if (errorMessage != null) {
|
||||||
} else {
|
binding.jidLayout.setError(errorMessage);
|
||||||
accountJid = Jid.ofEscaped((String) binding.account.getSelectedItem());
|
|
||||||
}
|
|
||||||
} catch (final IllegalArgumentException e) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Jid contactJid;
|
if (jidString == null) {
|
||||||
|
binding.jidLayout.setError(getActivity().getString(R.string.invalid_jid));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Jid contactJid = null;
|
||||||
try {
|
try {
|
||||||
contactJid = Jid.ofEscaped(binding.jid.getText().toString().trim());
|
contactJid = Jid.ofEscaped(jidString);
|
||||||
} catch (final IllegalArgumentException e) {
|
} catch (final IllegalArgumentException e) {
|
||||||
binding.jidLayout.setError(getActivity().getString(R.string.invalid_jid));
|
binding.jidLayout.setError(getActivity().getString(R.string.invalid_jid));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!issuedWarning && sanityCheckJid) {
|
if (!issuedWarning && sanityCheckJid != SanityCheck.NO) {
|
||||||
if (contactJid.isDomainJid()) {
|
if (contactJid.isDomainJid()) {
|
||||||
binding.jidLayout.setError(
|
binding.jidLayout.setError(getActivity().getString(R.string.this_looks_like_a_domain));
|
||||||
getActivity().getString(R.string.this_looks_like_a_domain));
|
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
|
||||||
issuedWarning = true;
|
issuedWarning = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (suspiciousSubDomain(contactJid.getDomain().toEscapedString())) {
|
if (sanityCheckJid != SanityCheck.ALLOW_MUC && suspiciousSubDomain(contactJid.getDomain().toEscapedString())) {
|
||||||
binding.jidLayout.setError(
|
binding.jidLayout.setError(getActivity().getString(R.string.this_looks_like_channel));
|
||||||
getActivity().getString(R.string.this_looks_like_channel));
|
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
|
||||||
issuedWarning = true;
|
issuedWarning = true;
|
||||||
return;
|
return;
|
||||||
|
@ -195,7 +280,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
|
|
||||||
if (mListener != null) {
|
if (mListener != null) {
|
||||||
try {
|
try {
|
||||||
if (mListener.onEnterJidDialogPositive(accountJid, contactJid)) {
|
if (mListener.onEnterJidDialogPositive(accountJid, contactJid, secondary, binding.bookmark.isChecked())) {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
} catch (JidError error) {
|
} catch (JidError error) {
|
||||||
|
@ -204,6 +289,31 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
issuedWarning = false;
|
issuedWarning = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Pair<String,Pair<Jid,Presence>> p = gatewayListAdapter.getSelected();
|
||||||
|
final String type = gatewayListAdapter.getSelectedType();
|
||||||
|
|
||||||
|
// Resolve based on local settings before submission
|
||||||
|
if (type != null && (type.equals("pstn") || type.equals("sms"))) {
|
||||||
|
try {
|
||||||
|
binding.jid.setText(PhoneNumberUtilWrapper.normalize(getActivity(), binding.jid.getText().toString(), true));
|
||||||
|
} catch (NumberParseException | IllegalArgumentException | NullPointerException e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p == null) {
|
||||||
|
finish.onGatewayResult(binding.jid.getText().toString().trim(), null);
|
||||||
|
} else if (p.first != null) { // Gateway already responsed to jabber:iq:gateway once
|
||||||
|
final Account acct = ((XmppActivity) getActivity()).xmppConnectionService.findAccountByJid(accountJid);
|
||||||
|
((XmppActivity) getActivity()).xmppConnectionService.fetchFromGateway(acct, p.second.first, binding.jid.getText().toString().trim(), finish);
|
||||||
|
} else if (p.second.first.isDomainJid() && p.second.second.getServiceDiscoveryResult().getFeatures().contains("jid\\20escaping")) {
|
||||||
|
finish.onGatewayResult(Jid.ofLocalAndDomain(binding.jid.getText().toString().trim(), p.second.first.getDomain().toString()).toString(), null);
|
||||||
|
} else if (p.second.first.isDomainJid()) {
|
||||||
|
finish.onGatewayResult(Jid.ofLocalAndDomain(binding.jid.getText().toString().trim().replace("@", "%"), p.second.first.getDomain().toString()).toString(), null);
|
||||||
|
} else {
|
||||||
|
finish.onGatewayResult(null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) {
|
public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) {
|
||||||
|
@ -244,7 +354,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnEnterJidDialogPositiveListener {
|
public interface OnEnterJidDialogPositiveListener {
|
||||||
boolean onEnterJidDialogPositive(Jid account, Jid contact) throws EnterJidDialog.JidError;
|
boolean onEnterJidDialogPositive(Jid account, Jid contact, boolean secondary, boolean save) throws EnterJidDialog.JidError;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class JidError extends Exception {
|
public static class JidError extends Exception {
|
||||||
|
@ -276,4 +386,210 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
final String[] parts = domain.split("\\.");
|
final String[] parts = domain.split("\\.");
|
||||||
return parts.length >= 3 && SUSPICIOUS_DOMAINS.contains(parts[0]);
|
return parts.length >= 3 && SUSPICIOUS_DOMAINS.contains(parts[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected class GatewayListAdapter extends RecyclerView.Adapter<GatewayListAdapter.ViewHolder> {
|
||||||
|
protected class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
protected ToggleButton button;
|
||||||
|
protected int index;
|
||||||
|
|
||||||
|
public ViewHolder(View view, int i) {
|
||||||
|
super(view);
|
||||||
|
this.button = (ToggleButton) view.findViewById(R.id.button);
|
||||||
|
setIndex(i);
|
||||||
|
button.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
button.setChecked(true); // Force visual not to flap to unchecked
|
||||||
|
setSelected(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndex(int i) {
|
||||||
|
this.index = i;
|
||||||
|
button.setChecked(selected == i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useButton(int res) {
|
||||||
|
button.setText(res);
|
||||||
|
button.setTextOff(button.getText());
|
||||||
|
button.setTextOn(button.getText());
|
||||||
|
button.setChecked(selected == this.index);
|
||||||
|
binding.gatewayList.setVisibility(View.VISIBLE);
|
||||||
|
button.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useButton(String txt) {
|
||||||
|
button.setTextOff(txt);
|
||||||
|
button.setTextOn(txt);
|
||||||
|
button.setChecked(selected == this.index);
|
||||||
|
binding.gatewayList.setVisibility(View.VISIBLE);
|
||||||
|
button.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Pair<Contact,String>> gateways = new ArrayList();
|
||||||
|
protected int selected = 0;
|
||||||
|
protected Runnable onEmpty = () -> {};
|
||||||
|
protected Runnable onNonEmpty = () -> {};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
|
||||||
|
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.enter_jid_dialog_gateway_list_item, null);
|
||||||
|
return new ViewHolder(view, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(ViewHolder viewHolder, int i) {
|
||||||
|
viewHolder.setIndex(i);
|
||||||
|
|
||||||
|
if(i == 0) {
|
||||||
|
viewHolder.useButton(R.string.account_settings_jabber_id);
|
||||||
|
} else {
|
||||||
|
viewHolder.useButton(getLabel(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return this.gateways.size() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelected(int i) {
|
||||||
|
int old = this.selected;
|
||||||
|
this.selected = i;
|
||||||
|
|
||||||
|
if(i == 0) {
|
||||||
|
binding.jid.setThreshold(1);
|
||||||
|
binding.jid.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
|
||||||
|
binding.jidLayout.setHint(R.string.account_settings_jabber_id);
|
||||||
|
|
||||||
|
if(binding.jid.hasFocus()) {
|
||||||
|
binding.jid.setHint(R.string.account_settings_example_jabber_id);
|
||||||
|
} else {
|
||||||
|
DelayedHintHelper.setHint(R.string.account_settings_example_jabber_id, binding.jid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.jid.setThreshold(999999); // do not autocomplete
|
||||||
|
binding.jid.setHint(null);
|
||||||
|
binding.jid.setOnFocusChangeListener((v, hasFocus) -> {});
|
||||||
|
binding.jidLayout.setHint(this.gateways.get(i-1).second);
|
||||||
|
|
||||||
|
String type = getType(i);
|
||||||
|
if (type == null) type = "";
|
||||||
|
if (type.equals("pstn") || type.equals("sms")) {
|
||||||
|
binding.jid.setInputType(InputType.TYPE_CLASS_PHONE);
|
||||||
|
} else if (type.equals("email") || type.equals("sip")) {
|
||||||
|
binding.jid.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
|
||||||
|
|
||||||
|
if(binding.jid.hasFocus()) {
|
||||||
|
binding.jid.setHint(R.string.account_settings_example_jabber_id);
|
||||||
|
} else {
|
||||||
|
DelayedHintHelper.setHint(R.string.account_settings_example_jabber_id, binding.jid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.jid.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyItemChanged(old);
|
||||||
|
notifyItemChanged(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel(Contact gateway) {
|
||||||
|
String type = getType(gateway);
|
||||||
|
if (type != null) return type;
|
||||||
|
|
||||||
|
return gateway.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel(int i) {
|
||||||
|
if (i == 0) return null;
|
||||||
|
|
||||||
|
return getLabel(this.gateways.get(i-1).first);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType(int i) {
|
||||||
|
if (i == 0) return null;
|
||||||
|
|
||||||
|
return getType(this.gateways.get(i-1).first);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType(Contact gateway) {
|
||||||
|
List<String> types = getTypes(gateway);
|
||||||
|
return types.isEmpty() ? null : types.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getTypes(Contact gateway) {
|
||||||
|
List<String> types = new ArrayList<>();
|
||||||
|
|
||||||
|
for(Presence p : gateway.getPresences().getPresences()) {
|
||||||
|
if(p.getServiceDiscoveryResult() != null) {
|
||||||
|
for (ServiceDiscoveryResult.Identity id : p.getServiceDiscoveryResult().getIdentities()) {
|
||||||
|
if ("gateway".equals(id.getCategory())) types.add(id.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedType() {
|
||||||
|
return getType(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<String, Pair<Jid,Presence>> getSelected() {
|
||||||
|
if(this.selected == 0) {
|
||||||
|
return null; // No gateway, just use direct JID entry
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair<Contact,String> gateway = this.gateways.get(this.selected - 1);
|
||||||
|
|
||||||
|
Pair<Jid,Presence> presence = null;
|
||||||
|
for (Map.Entry<String,Presence> e : gateway.first.getPresences().getPresencesMap().entrySet()) {
|
||||||
|
Presence p = e.getValue();
|
||||||
|
if (p.getServiceDiscoveryResult() != null) {
|
||||||
|
if (p.getServiceDiscoveryResult().getFeatures().contains("jabber:iq:gateway")) {
|
||||||
|
if (e.getKey().equals("")) {
|
||||||
|
presence = new Pair<>(gateway.first.getJid(), p);
|
||||||
|
} else {
|
||||||
|
presence = new Pair<>(gateway.first.getJid().withResource(e.getKey()), p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (p.getServiceDiscoveryResult().hasIdentity("gateway", null)) {
|
||||||
|
if (e.getKey().equals("")) {
|
||||||
|
presence = new Pair<>(gateway.first.getJid(), p);
|
||||||
|
} else {
|
||||||
|
presence = new Pair<>(gateway.first.getJid().withResource(e.getKey()), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return presence == null ? null : new Pair(gateway.second, presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnEmpty(Runnable r) {
|
||||||
|
onEmpty = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnNonEmpty(Runnable r) {
|
||||||
|
onNonEmpty = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
gateways.clear();
|
||||||
|
onEmpty.run();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
setSelected(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Contact gateway, String prompt) {
|
||||||
|
if (getItemCount() < 2) onNonEmpty.run();
|
||||||
|
this.gateways.add(new Pair<>(gateway, prompt));
|
||||||
|
Collections.sort(this.gateways, (x, y) -> getLabel(x.first).compareTo(getLabel(y.first)));
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import android.app.PictureInPictureParams;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -40,6 +39,7 @@ import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.webrtc.RendererCommon;
|
import org.webrtc.RendererCommon;
|
||||||
import org.webrtc.SurfaceViewRenderer;
|
import org.webrtc.SurfaceViewRenderer;
|
||||||
import org.webrtc.VideoTrack;
|
import org.webrtc.VideoTrack;
|
||||||
|
@ -58,6 +58,7 @@ import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.services.AppRTCAudioManager;
|
import eu.siacs.conversations.services.AppRTCAudioManager;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.ui.widget.DialpadView;
|
||||||
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
||||||
import eu.siacs.conversations.ui.util.MainThreadExecutor;
|
import eu.siacs.conversations.ui.util.MainThreadExecutor;
|
||||||
import eu.siacs.conversations.ui.util.Rationals;
|
import eu.siacs.conversations.ui.util.Rationals;
|
||||||
|
@ -86,7 +87,7 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
|
|
||||||
private static final int CALL_DURATION_UPDATE_INTERVAL = 333;
|
private static final int CALL_DURATION_UPDATE_INTERVAL = 333;
|
||||||
|
|
||||||
private static final List<RtpEndUserState> END_CARD =
|
public static final List<RtpEndUserState> END_CARD =
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
RtpEndUserState.APPLICATION_ERROR,
|
RtpEndUserState.APPLICATION_ERROR,
|
||||||
RtpEndUserState.SECURITY_ERROR,
|
RtpEndUserState.SECURITY_ERROR,
|
||||||
|
@ -164,6 +165,17 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
|
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
|
||||||
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session);
|
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session);
|
||||||
setSupportActionBar(binding.toolbar);
|
setSupportActionBar(binding.toolbar);
|
||||||
|
|
||||||
|
binding.dialpad.setClickConsumer(tag -> {
|
||||||
|
final JingleRtpConnection connection =
|
||||||
|
this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
||||||
|
if (connection != null) connection.applyDtmfTone(tag);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
boolean dialpadVisible = savedInstanceState.getBoolean("dialpad_visible");
|
||||||
|
binding.dialpad.setVisibility(dialpadVisible ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -171,10 +183,12 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
getMenuInflater().inflate(R.menu.activity_rtp_session, menu);
|
getMenuInflater().inflate(R.menu.activity_rtp_session, menu);
|
||||||
final MenuItem help = menu.findItem(R.id.action_help);
|
final MenuItem help = menu.findItem(R.id.action_help);
|
||||||
final MenuItem gotoChat = menu.findItem(R.id.action_goto_chat);
|
final MenuItem gotoChat = menu.findItem(R.id.action_goto_chat);
|
||||||
|
final MenuItem dialpad = menu.findItem(R.id.action_dialpad);
|
||||||
final MenuItem switchToVideo = menu.findItem(R.id.action_switch_to_video);
|
final MenuItem switchToVideo = menu.findItem(R.id.action_switch_to_video);
|
||||||
help.setVisible(Config.HELP != null && isHelpButtonVisible());
|
help.setVisible(Config.HELP != null && isHelpButtonVisible());
|
||||||
gotoChat.setVisible(isSwitchToConversationVisible());
|
gotoChat.setVisible(isSwitchToConversationVisible());
|
||||||
switchToVideo.setVisible(isSwitchToVideoVisible());
|
switchToVideo.setVisible(isSwitchToVideoVisible());
|
||||||
|
dialpad.setVisible(isAudioOnlyConversation());
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +226,13 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
&& STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState());
|
&& STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAudioOnlyConversation() {
|
||||||
|
final JingleRtpConnection connection =
|
||||||
|
this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
||||||
|
|
||||||
|
return connection != null && !connection.getMedia().contains(Media.VIDEO);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isSwitchToVideoVisible() {
|
private boolean isSwitchToVideoVisible() {
|
||||||
final JingleRtpConnection connection =
|
final JingleRtpConnection connection =
|
||||||
this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
||||||
|
@ -229,6 +250,15 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
switchToConversation(conversation);
|
switchToConversation(conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void toggleDialpadVisibility() {
|
||||||
|
if (binding.dialpad.getVisibility() == View.VISIBLE) {
|
||||||
|
binding.dialpad.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
binding.dialpad.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_help:
|
case R.id.action_help:
|
||||||
|
@ -237,6 +267,9 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
case R.id.action_goto_chat:
|
case R.id.action_goto_chat:
|
||||||
switchToConversation();
|
switchToConversation();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_dialpad:
|
||||||
|
toggleDialpadVisibility();
|
||||||
|
return true;
|
||||||
case R.id.action_switch_to_video:
|
case R.id.action_switch_to_video:
|
||||||
requestPermissionAndSwitchToVideo();
|
requestPermissionAndSwitchToVideo();
|
||||||
return true;
|
return true;
|
||||||
|
@ -407,7 +440,7 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
|
|
||||||
private void putScreenInCallMode(final Set<Media> media) {
|
private void putScreenInCallMode(final Set<Media> media) {
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
if (Media.audioOnly(media)) {
|
if (!media.contains(Media.VIDEO)) {
|
||||||
final JingleRtpConnection rtpConnection =
|
final JingleRtpConnection rtpConnection =
|
||||||
rtpConnectionReference != null ? rtpConnectionReference.get() : null;
|
rtpConnectionReference != null ? rtpConnectionReference.get() : null;
|
||||||
final AppRTCAudioManager audioManager =
|
final AppRTCAudioManager audioManager =
|
||||||
|
@ -418,15 +451,6 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
acquireProximityWakeLock();
|
acquireProximityWakeLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lockOrientation(media);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void lockOrientation(final Set<Media> media) {
|
|
||||||
if (Media.audioOnly(media)) {
|
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
||||||
} else {
|
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("WakelockTimeout")
|
@SuppressLint("WakelockTimeout")
|
||||||
|
@ -552,7 +576,7 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setWidth(final RtpEndUserState state) {
|
private void setWith(final RtpEndUserState state) {
|
||||||
setWith(getWith(), state);
|
setWith(getWith(), state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -759,8 +783,9 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
.getJingleConnectionManager()
|
.getJingleConnectionManager()
|
||||||
.getTerminalSessionState(with, sessionId);
|
.getTerminalSessionState(with, sessionId);
|
||||||
if (terminatedRtpSession == null) {
|
if (terminatedRtpSession == null) {
|
||||||
throw new IllegalStateException(
|
Log.e(Config.LOGTAG, "failed to initialize activity with running rtp session. session not found");
|
||||||
"failed to initialize activity with running rtp session. session not found");
|
finish();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
initializeWithTerminatedSessionState(account, with, terminatedRtpSession);
|
initializeWithTerminatedSessionState(account, with, terminatedRtpSession);
|
||||||
return true;
|
return true;
|
||||||
|
@ -781,7 +806,7 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
requireRtpConnection().getState())) {
|
requireRtpConnection().getState())) {
|
||||||
putScreenInCallMode();
|
putScreenInCallMode();
|
||||||
}
|
}
|
||||||
setWidth(currentState);
|
setWith(currentState);
|
||||||
updateVideoViews(currentState);
|
updateVideoViews(currentState);
|
||||||
updateStateDisplay(currentState, media, contentAddition);
|
updateStateDisplay(currentState, media, contentAddition);
|
||||||
updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(currentState));
|
updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(currentState));
|
||||||
|
@ -1389,7 +1414,6 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
final AbstractJingleConnection.Id id = requireRtpConnection().getId();
|
final AbstractJingleConnection.Id id = requireRtpConnection().getId();
|
||||||
final boolean verified = requireRtpConnection().isVerified();
|
final boolean verified = requireRtpConnection().isVerified();
|
||||||
final Set<Media> media = getMedia();
|
final Set<Media> media = getMedia();
|
||||||
lockOrientation(media);
|
|
||||||
final ContentAddition contentAddition = getPendingContentAddition();
|
final ContentAddition contentAddition = getPendingContentAddition();
|
||||||
final Contact contact = getWith();
|
final Contact contact = getWith();
|
||||||
if (account == id.account && id.with.equals(with) && id.sessionId.equals(sessionId)) {
|
if (account == id.account && id.with.equals(with) && id.sessionId.equals(sessionId)) {
|
||||||
|
@ -1453,6 +1477,12 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull @NotNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putBoolean("dialpad_visible", binding.dialpad.getVisibility() == View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
private void updateRtpSessionProposalState(
|
private void updateRtpSessionProposalState(
|
||||||
final Account account, final Jid with, final RtpEndUserState state) {
|
final Account account, final Jid with, final RtpEndUserState state) {
|
||||||
final Intent currentIntent = getIntent();
|
final Intent currentIntent = getIntent();
|
||||||
|
|
|
@ -517,15 +517,17 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
||||||
ft.addToBackStack(null);
|
ft.addToBackStack(null);
|
||||||
EnterJidDialog dialog = EnterJidDialog.newInstance(
|
EnterJidDialog dialog = EnterJidDialog.newInstance(
|
||||||
mActivatedAccounts,
|
mActivatedAccounts,
|
||||||
getString(R.string.add_contact),
|
getString(R.string.start_conversation),
|
||||||
getString(R.string.add),
|
getString(R.string.message),
|
||||||
|
"Call",
|
||||||
prefilledJid,
|
prefilledJid,
|
||||||
invite == null ? null : invite.account,
|
invite == null ? null : invite.account,
|
||||||
invite == null || !invite.hasFingerprints(),
|
invite == null || !invite.hasFingerprints(),
|
||||||
true
|
true,
|
||||||
|
EnterJidDialog.SanityCheck.ALLOW_MUC
|
||||||
);
|
);
|
||||||
|
|
||||||
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
|
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid, call, save) -> {
|
||||||
if (!xmppConnectionServiceBound) {
|
if (!xmppConnectionServiceBound) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -534,25 +536,57 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Contact contact = account.getRoster().getContact(contactJid);
|
final Contact contact = account.getRoster().getContact(contactJid);
|
||||||
|
|
||||||
if (invite != null && invite.getName() != null) {
|
if (invite != null && invite.getName() != null) {
|
||||||
contact.setServerName(invite.getName());
|
contact.setServerName(invite.getName());
|
||||||
}
|
}
|
||||||
if (contact.isSelf()) {
|
|
||||||
switchToConversation(contact);
|
if (contact.isSelf() || contact.showInRoster()) {
|
||||||
|
switchToConversationDoNotAppend(contact, invite == null ? null : invite.getBody(), call ? "call" : null);
|
||||||
return true;
|
return true;
|
||||||
} else if (contact.showInRoster()) {
|
}
|
||||||
throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
|
|
||||||
|
xmppConnectionService.checkIfMuc(account, contactJid, (isMuc) -> {
|
||||||
|
if (isMuc) {
|
||||||
|
if (save) {
|
||||||
|
Bookmark bookmark = account.getBookmark(contactJid);
|
||||||
|
if (bookmark != null) {
|
||||||
|
openConversationsForBookmark(bookmark);
|
||||||
} else {
|
} else {
|
||||||
|
bookmark = new Bookmark(account, contactJid.asBareJid());
|
||||||
|
bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin));
|
||||||
|
final String nick = contactJid.getResource();
|
||||||
|
if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) {
|
||||||
|
bookmark.setNick(nick);
|
||||||
|
}
|
||||||
|
xmppConnectionService.createBookmark(account, bookmark);
|
||||||
|
final Conversation conversation = xmppConnectionService
|
||||||
|
.findOrCreateConversation(account, contactJid, true, true, true);
|
||||||
|
bookmark.setConversation(conversation);
|
||||||
|
switchToConversationDoNotAppend(conversation, invite == null ? null : invite.getBody());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, contactJid, true, true, true);
|
||||||
|
switchToConversationDoNotAppend(conversation, invite == null ? null : invite.getBody());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (save) {
|
||||||
final String preAuth = invite == null ? null : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH);
|
final String preAuth = invite == null ? null : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH);
|
||||||
xmppConnectionService.createContact(contact, true, preAuth);
|
xmppConnectionService.createContact(contact, true, preAuth);
|
||||||
if (invite != null && invite.hasFingerprints()) {
|
if (invite != null && invite.hasFingerprints()) {
|
||||||
xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
|
xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
|
||||||
}
|
}
|
||||||
switchToConversationDoNotAppend(contact, invite == null ? null : invite.getBody());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
switchToConversationDoNotAppend(contact, invite == null ? null : invite.getBody(), call ? "call" : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
dialog.dismiss();
|
||||||
|
} catch (final IllegalStateException e) { }
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
dialog.show(ft, FRAGMENT_TAG_DIALOG);
|
dialog.show(ft, FRAGMENT_TAG_DIALOG);
|
||||||
}
|
}
|
||||||
|
|
|
@ -551,6 +551,11 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
switchToConversation(conversation, text, false, null, false, true);
|
switchToConversation(conversation, text, false, null, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void switchToConversationDoNotAppend(Contact contact, String body, String postInit) {
|
||||||
|
Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
|
||||||
|
switchToConversation(conversation, body, false, null, false, true, postInit);
|
||||||
|
}
|
||||||
|
|
||||||
public void highlightInMuc(Conversation conversation, String nick) {
|
public void highlightInMuc(Conversation conversation, String nick) {
|
||||||
switchToConversation(conversation, null, false, nick, false, false);
|
switchToConversation(conversation, null, false, nick, false, false);
|
||||||
}
|
}
|
||||||
|
@ -559,7 +564,13 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
switchToConversation(conversation, null, false, nick, true, false);
|
switchToConversation(conversation, null, false, nick, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean doNotAppend) {
|
public void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean doNotAppend) {
|
||||||
|
switchToConversation(conversation, text, asQuote, nick, pm, doNotAppend, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean doNotAppend, String postInit) {
|
||||||
|
if (conversation == null) return;
|
||||||
|
|
||||||
Intent intent = new Intent(this, ConversationsActivity.class);
|
Intent intent = new Intent(this, ConversationsActivity.class);
|
||||||
intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
|
intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
|
||||||
intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
|
intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
|
||||||
|
@ -576,6 +587,7 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
if (doNotAppend) {
|
if (doNotAppend) {
|
||||||
intent.putExtra(ConversationsActivity.EXTRA_DO_NOT_APPEND, true);
|
intent.putExtra(ConversationsActivity.EXTRA_DO_NOT_APPEND, true);
|
||||||
}
|
}
|
||||||
|
intent.putExtra(ConversationsActivity.EXTRA_POST_INIT_ACTION, postInit);
|
||||||
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.siacs.conversations.ui.widget;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.databinding.DialpadBinding;
|
||||||
|
import eu.siacs.conversations.utils.Consumer;
|
||||||
|
|
||||||
|
public class DialpadView extends ConstraintLayout implements View.OnClickListener {
|
||||||
|
|
||||||
|
protected Consumer<String> clickConsumer = null;
|
||||||
|
|
||||||
|
public DialpadView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DialpadView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DialpadView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClickConsumer(Consumer<String> clickConsumer) {
|
||||||
|
this.clickConsumer = clickConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
DialpadBinding binding = DataBindingUtil.inflate(
|
||||||
|
LayoutInflater.from(getContext()),
|
||||||
|
R.layout.dialpad,
|
||||||
|
this,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
binding.setDialpadView(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
clickConsumer.accept(v.getTag().toString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,19 +21,25 @@ public class LocationProvider {
|
||||||
public static final GeoPoint FALLBACK = new GeoPoint(0.0, 0.0);
|
public static final GeoPoint FALLBACK = new GeoPoint(0.0, 0.0);
|
||||||
|
|
||||||
public static String getUserCountry(final Context context) {
|
public static String getUserCountry(final Context context) {
|
||||||
|
return getUserCountry(context, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getUserCountry(final Context context, boolean preferNetwork) {
|
||||||
try {
|
try {
|
||||||
final TelephonyManager tm = ContextCompat.getSystemService(context, TelephonyManager.class);
|
final TelephonyManager tm = ContextCompat.getSystemService(context, TelephonyManager.class);
|
||||||
if (tm == null) {
|
if (tm == null) {
|
||||||
return getUserCountryFallback();
|
return getUserCountryFallback();
|
||||||
}
|
}
|
||||||
final String simCountry = tm.getSimCountryIso();
|
final String simCountry = tm.getSimOperator().equals("20801") ? "us" : tm.getSimCountryIso();
|
||||||
if (simCountry != null && simCountry.length() == 2) { // SIM country code is available
|
final String networkCountry = tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA ? null : tm.getNetworkCountryIso(); // if device is not 3G would be unreliable
|
||||||
return simCountry.toUpperCase(Locale.US);
|
if (preferNetwork && networkCountry != null && networkCountry.length() == 2) {
|
||||||
} else if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) { // device is not 3G (would be unreliable)
|
|
||||||
String networkCountry = tm.getNetworkCountryIso();
|
|
||||||
if (networkCountry != null && networkCountry.length() == 2) { // network country code is available
|
|
||||||
return networkCountry.toUpperCase(Locale.US);
|
return networkCountry.toUpperCase(Locale.US);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (simCountry != null && simCountry.length() == 2) { // SIM country code is available
|
||||||
|
return simCountry.toUpperCase(Locale.US);
|
||||||
|
} else if (networkCountry != null && networkCountry.length() == 2) { // network country code is available
|
||||||
|
return networkCountry.toUpperCase(Locale.US);
|
||||||
}
|
}
|
||||||
return getUserCountryFallback();
|
return getUserCountryFallback();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package eu.siacs.conversations.xmpp;
|
||||||
|
|
||||||
|
public interface OnGatewayResult {
|
||||||
|
// if prompt is null, there was an error
|
||||||
|
// errorText may or may not be set
|
||||||
|
public void onGatewayResult(String prompt, String errorText);
|
||||||
|
}
|
|
@ -270,6 +270,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean applyDtmfTone(String tone) {
|
||||||
|
return webRTCWrapper.applyDtmfTone(tone);
|
||||||
|
}
|
||||||
|
|
||||||
private void receiveSessionTerminate(final JinglePacket jinglePacket) {
|
private void receiveSessionTerminate(final JinglePacket jinglePacket) {
|
||||||
respondOk(jinglePacket);
|
respondOk(jinglePacket);
|
||||||
final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason();
|
final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason();
|
||||||
|
|
|
@ -148,7 +148,7 @@ class ToneManager {
|
||||||
|
|
||||||
private void scheduleWaitingTone() {
|
private void scheduleWaitingTone() {
|
||||||
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(() -> {
|
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(() -> {
|
||||||
startTone(ToneGenerator.TONE_CDMA_DIAL_TONE_LITE, 750);
|
startTone(ToneGenerator.TONE_CDMA_NETWORK_USA_RINGBACK, 750);
|
||||||
}, 0, 3, TimeUnit.SECONDS);
|
}, 0, 3, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,14 +161,16 @@ class ToneManager {
|
||||||
currentTone.cancel(true);
|
currentTone.cancel(true);
|
||||||
}
|
}
|
||||||
if (toneGenerator != null) {
|
if (toneGenerator != null) {
|
||||||
|
// catch race condition with already-released generator
|
||||||
|
try {
|
||||||
toneGenerator.stopTone();
|
toneGenerator.stopTone();
|
||||||
|
} catch (final RuntimeException e) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startTone(final int toneType, final int durationMs) {
|
public void startTone(final int toneType, final int durationMs) {
|
||||||
if (this.toneGenerator != null) {
|
if (this.toneGenerator != null) {
|
||||||
this.toneGenerator.release();;
|
this.toneGenerator.release();
|
||||||
|
|
||||||
}
|
}
|
||||||
final AudioManager audioManager = ContextCompat.getSystemService(context, AudioManager.class);
|
final AudioManager audioManager = ContextCompat.getSystemService(context, AudioManager.class);
|
||||||
final boolean ringerModeNormal = audioManager == null || audioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL;
|
final boolean ringerModeNormal = audioManager == null || audioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.media.ToneGenerator;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
@ -8,6 +9,7 @@ import android.util.Log;
|
||||||
|
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
@ -20,6 +22,7 @@ import org.webrtc.CandidatePairChangeEvent;
|
||||||
import org.webrtc.DataChannel;
|
import org.webrtc.DataChannel;
|
||||||
import org.webrtc.DefaultVideoDecoderFactory;
|
import org.webrtc.DefaultVideoDecoderFactory;
|
||||||
import org.webrtc.DefaultVideoEncoderFactory;
|
import org.webrtc.DefaultVideoEncoderFactory;
|
||||||
|
import org.webrtc.DtmfSender;
|
||||||
import org.webrtc.EglBase;
|
import org.webrtc.EglBase;
|
||||||
import org.webrtc.IceCandidate;
|
import org.webrtc.IceCandidate;
|
||||||
import org.webrtc.MediaConstraints;
|
import org.webrtc.MediaConstraints;
|
||||||
|
@ -38,6 +41,7 @@ import org.webrtc.voiceengine.WebRtcAudioEffects;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
@ -57,6 +61,25 @@ public class WebRTCWrapper {
|
||||||
|
|
||||||
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
|
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
private static final int TONE_DURATION = 500;
|
||||||
|
private static final Map<String,Integer> TONE_CODES;
|
||||||
|
static {
|
||||||
|
ImmutableMap.Builder<String,Integer> builder = new ImmutableMap.Builder<>();
|
||||||
|
builder.put("0", ToneGenerator.TONE_DTMF_0);
|
||||||
|
builder.put("1", ToneGenerator.TONE_DTMF_1);
|
||||||
|
builder.put("2", ToneGenerator.TONE_DTMF_2);
|
||||||
|
builder.put("3", ToneGenerator.TONE_DTMF_3);
|
||||||
|
builder.put("4", ToneGenerator.TONE_DTMF_4);
|
||||||
|
builder.put("5", ToneGenerator.TONE_DTMF_5);
|
||||||
|
builder.put("6", ToneGenerator.TONE_DTMF_6);
|
||||||
|
builder.put("7", ToneGenerator.TONE_DTMF_7);
|
||||||
|
builder.put("8", ToneGenerator.TONE_DTMF_8);
|
||||||
|
builder.put("9", ToneGenerator.TONE_DTMF_9);
|
||||||
|
builder.put("*", ToneGenerator.TONE_DTMF_S);
|
||||||
|
builder.put("#", ToneGenerator.TONE_DTMF_P);
|
||||||
|
TONE_CODES = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
private static final Set<String> HARDWARE_AEC_BLACKLIST =
|
private static final Set<String> HARDWARE_AEC_BLACKLIST =
|
||||||
new ImmutableSet.Builder<String>()
|
new ImmutableSet.Builder<String>()
|
||||||
.add("Pixel")
|
.add("Pixel")
|
||||||
|
@ -510,8 +533,14 @@ public class WebRTCWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isMicrophoneEnabled() {
|
boolean isMicrophoneEnabled() {
|
||||||
final Optional<AudioTrack> audioTrack =
|
Optional<AudioTrack> audioTrack = null;
|
||||||
TrackWrapper.get(peerConnection, this.localAudioTrack);
|
try {
|
||||||
|
audioTrack = TrackWrapper.get(peerConnection, this.localAudioTrack);
|
||||||
|
} catch (final IllegalStateException e) {
|
||||||
|
Log.d(Config.LOGTAG, "unable to check microphone", e);
|
||||||
|
// ignoring race condition in case sender has been disposed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (audioTrack.isPresent()) {
|
if (audioTrack.isPresent()) {
|
||||||
try {
|
try {
|
||||||
return audioTrack.get().enabled();
|
return audioTrack.get().enabled();
|
||||||
|
@ -526,8 +555,14 @@ public class WebRTCWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean setMicrophoneEnabled(final boolean enabled) {
|
boolean setMicrophoneEnabled(final boolean enabled) {
|
||||||
final Optional<AudioTrack> audioTrack =
|
Optional<AudioTrack> audioTrack = null;
|
||||||
TrackWrapper.get(peerConnection, this.localAudioTrack);
|
try {
|
||||||
|
audioTrack = TrackWrapper.get(peerConnection, this.localAudioTrack);
|
||||||
|
} catch (final IllegalStateException e) {
|
||||||
|
Log.d(Config.LOGTAG, "unable to toggle microphone", e);
|
||||||
|
// ignoring race condition in case sender has been disposed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (audioTrack.isPresent()) {
|
if (audioTrack.isPresent()) {
|
||||||
try {
|
try {
|
||||||
audioTrack.get().setEnabled(enabled);
|
audioTrack.get().setEnabled(enabled);
|
||||||
|
@ -650,6 +685,15 @@ public class WebRTCWrapper {
|
||||||
return peerConnection;
|
return peerConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean applyDtmfTone(String tone) {
|
||||||
|
if (toneManager == null || peerConnection == null || localAudioTrack == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
localAudioTrack.rtpSender.dtmf().insertDtmf(tone, TONE_DURATION, 100);
|
||||||
|
toneManager.startTone(TONE_CODES.get(tone), TONE_DURATION);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private PeerConnectionFactory requirePeerConnectionFactory() {
|
private PeerConnectionFactory requirePeerConnectionFactory() {
|
||||||
final PeerConnectionFactory peerConnectionFactory = this.peerConnectionFactory;
|
final PeerConnectionFactory peerConnectionFactory = this.peerConnectionFactory;
|
||||||
|
|
9
src/main/res/drawable/ic_dialpad_white_24dp.xml
Normal file
9
src/main/res/drawable/ic_dialpad_white_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,19c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,1c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,7c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,13c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,13c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,7c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,7c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,1c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"
|
||||||
|
android:fillColor="#FFFFFF"/>
|
||||||
|
</vector>
|
|
@ -74,7 +74,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="32dp"/>
|
android:layout_height="32dp"/>
|
||||||
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
|
@ -92,12 +91,21 @@
|
||||||
android:textAppearance="@style/TextAppearance.Conversations.Title.Monospace"
|
android:textAppearance="@style/TextAppearance.Conversations.Title.Monospace"
|
||||||
tools:text="01:23" />
|
tools:text="01:23" />
|
||||||
|
|
||||||
<com.makeramen.roundedimageview.RoundedImageView
|
<eu.siacs.conversations.ui.widget.DialpadView
|
||||||
|
layout="@layout/dialpad"
|
||||||
|
android:id="@+id/dialpad"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/contact_photo"
|
android:id="@+id/contact_photo"
|
||||||
android:layout_width="@dimen/publish_avatar_size"
|
android:layout_width="@dimen/publish_avatar_size"
|
||||||
android:layout_height="@dimen/publish_avatar_size"
|
android:layout_height="@dimen/publish_avatar_size"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
app:riv_corner_radius="@dimen/incoming_call_radius" />
|
app:strokeColor="?colorAccent"
|
||||||
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.IncomingCall" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
@ -178,7 +186,7 @@
|
||||||
app:elevation="4dp"
|
app:elevation="4dp"
|
||||||
app:fabCustomSize="72dp"
|
app:fabCustomSize="72dp"
|
||||||
app:maxImageSize="36dp"
|
app:maxImageSize="36dp"
|
||||||
tools:visibility="visible" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/accept_call"
|
android:id="@+id/accept_call"
|
||||||
|
@ -193,7 +201,7 @@
|
||||||
app:elevation="4dp"
|
app:elevation="4dp"
|
||||||
app:fabCustomSize="72dp"
|
app:fabCustomSize="72dp"
|
||||||
app:maxImageSize="36dp"
|
app:maxImageSize="36dp"
|
||||||
tools:visibility="visible" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
@ -204,7 +212,7 @@
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_margin="@dimen/in_call_fab_margin"
|
android:layout_margin="@dimen/in_call_fab_margin"
|
||||||
android:layout_toStartOf="@+id/end_call"
|
android:layout_toStartOf="@+id/end_call"
|
||||||
android:visibility="gone"
|
android:visibility="visible"
|
||||||
app:backgroundTint="?color_background_primary"
|
app:backgroundTint="?color_background_primary"
|
||||||
app:elevation="4dp"
|
app:elevation="4dp"
|
||||||
app:fabSize="mini"
|
app:fabSize="mini"
|
||||||
|
@ -230,7 +238,7 @@
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_margin="@dimen/in_call_fab_margin"
|
android:layout_margin="@dimen/in_call_fab_margin"
|
||||||
android:layout_toEndOf="@+id/end_call"
|
android:layout_toEndOf="@+id/end_call"
|
||||||
android:visibility="gone"
|
android:visibility="visible"
|
||||||
app:backgroundTint="?color_background_primary"
|
app:backgroundTint="?color_background_primary"
|
||||||
app:elevation="4dp"
|
app:elevation="4dp"
|
||||||
app:fabSize="mini"
|
app:fabSize="mini"
|
||||||
|
@ -243,7 +251,7 @@
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_margin="@dimen/in_call_fab_margin"
|
android:layout_margin="@dimen/in_call_fab_margin"
|
||||||
android:layout_toEndOf="@+id/in_call_action_right"
|
android:layout_toEndOf="@+id/in_call_action_right"
|
||||||
android:visibility="gone"
|
android:visibility="visible"
|
||||||
app:backgroundTint="?color_background_primary"
|
app:backgroundTint="?color_background_primary"
|
||||||
app:elevation="4dp"
|
app:elevation="4dp"
|
||||||
app:fabSize="mini"
|
app:fabSize="mini"
|
||||||
|
|
385
src/main/res/layout/dialpad.xml
Normal file
385
src/main/res/layout/dialpad.xml
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<data>
|
||||||
|
<variable name="dialpadView" type="eu.siacs.conversations.ui.widget.DialpadView"/>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/dialpad_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:paddingTop="@dimen/medium_margin"
|
||||||
|
tools:ignore="HardcodedText">
|
||||||
|
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_1_holder"
|
||||||
|
android:tag="1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="@dimen/activity_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/dialpad_2_holder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/dialpad_2_holder"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/dialpad_2_holder"
|
||||||
|
android:focusable="true" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_1"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="1" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_2_holder"
|
||||||
|
android:tag="2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/dialpad_5_holder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/dialpad_3_holder"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/dialpad_1_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_2"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="2" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_2_letters"
|
||||||
|
style="@style/DialpadLetterStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/dialpad_2"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="ABC" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_3_holder"
|
||||||
|
android:tag="3"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginEnd="@dimen/activity_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/dialpad_2_holder"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/dialpad_2_holder"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/dialpad_2_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_3"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="3" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_3_letters"
|
||||||
|
style="@style/DialpadLetterStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/dialpad_3"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="DEF" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_4_holder"
|
||||||
|
android:tag="4"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="@dimen/activity_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/dialpad_5_holder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/dialpad_5_holder"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/dialpad_5_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_4"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="4" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_4_letters"
|
||||||
|
style="@style/DialpadLetterStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/dialpad_4"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="GHI" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_5_holder"
|
||||||
|
android:tag="5"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/dialpad_8_holder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/dialpad_6_holder"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/dialpad_4_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_5"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="5" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_5_letters"
|
||||||
|
style="@style/DialpadLetterStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/dialpad_5"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="JKL" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_6_holder"
|
||||||
|
android:tag="6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginEnd="@dimen/activity_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/dialpad_5_holder"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/dialpad_5_holder"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/dialpad_5_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_6"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="6" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_6_letters"
|
||||||
|
style="@style/DialpadLetterStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/dialpad_6"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="MNO" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_7_holder"
|
||||||
|
android:tag="7"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="@dimen/activity_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/dialpad_8_holder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/dialpad_8_holder"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/dialpad_8_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_7"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="7" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_7_letters"
|
||||||
|
style="@style/DialpadLetterStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/dialpad_7"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="PQRS" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_8_holder"
|
||||||
|
android:tag="8"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/dialpad_0_holder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/dialpad_9_holder"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/dialpad_7_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_8"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="8" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_8_letters"
|
||||||
|
style="@style/DialpadLetterStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/dialpad_8"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="TUV" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_9_holder"
|
||||||
|
android:tag="9"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginEnd="@dimen/activity_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/dialpad_8_holder"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/dialpad_8_holder"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/dialpad_8_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_9"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="9" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_9_letters"
|
||||||
|
style="@style/DialpadLetterStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/dialpad_9"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="WXYZ" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_asterisk_holder"
|
||||||
|
android:tag="*"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="@dimen/activity_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/dialpad_0_holder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/dialpad_0_holder"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/dialpad_0_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_asterisk"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="*" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_0_holder"
|
||||||
|
android:tag="0"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/activity_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/dialpad_pound_holder"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/dialpad_asterisk_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_0"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="0" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_plus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/dialpad_0"
|
||||||
|
android:layout_alignBottom="@+id/dialpad_0"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_toEndOf="@+id/dialpad_0"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/small_margin"
|
||||||
|
android:text="+"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:onClick="@{dialpadView::onClick}"
|
||||||
|
android:id="@+id/dialpad_pound_holder"
|
||||||
|
android:tag="#"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginEnd="@dimen/activity_margin"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/dialpad_0_holder"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/dialpad_0_holder"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/dialpad_0_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialpad_pound"
|
||||||
|
style="@style/DialpadNumberStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginEnd="@dimen/activity_margin"
|
||||||
|
android:text="#" />
|
||||||
|
</RelativeLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
|
@ -22,6 +22,11 @@
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/gateway_list"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/jid_layout"
|
android:id="@+id/jid_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -38,5 +43,14 @@
|
||||||
android:imeOptions="actionDone|flagNoExtractUi"
|
android:imeOptions="actionDone|flagNoExtractUi"
|
||||||
android:inputType="textEmailAddress" />
|
android:inputType="textEmailAddress" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/bookmark"
|
||||||
|
style="@style/Widget.Conversations.CheckBox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:checked="true"
|
||||||
|
android:text="Save as Contact / Bookmark"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
14
src/main/res/layout/enter_jid_dialog_gateway_list_item.xml
Normal file
14
src/main/res/layout/enter_jid_dialog_gateway_list_item.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingRight="5dp">
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?attr/edit_text_color"
|
||||||
|
android:textSize="?attr/TextSizeBody1" />
|
||||||
|
</LinearLayout>
|
|
@ -8,6 +8,11 @@
|
||||||
android:icon="?attr/icon_help"
|
android:icon="?attr/icon_help"
|
||||||
android:title="@string/help"
|
android:title="@string/help"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_dialpad"
|
||||||
|
android:icon="@drawable/ic_dialpad_white_24dp"
|
||||||
|
android:title="@string/action_dialpad"
|
||||||
|
app:showAsAction="always" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_goto_chat"
|
android:id="@+id/action_goto_chat"
|
||||||
android:icon="?attr/icon_goto_chat"
|
android:icon="?attr/icon_goto_chat"
|
||||||
|
|
|
@ -73,5 +73,8 @@
|
||||||
<dimen name="bigger_text_size">16sp</dimen>
|
<dimen name="bigger_text_size">16sp</dimen>
|
||||||
<dimen name="big_text_size">18sp</dimen>
|
<dimen name="big_text_size">18sp</dimen>
|
||||||
|
|
||||||
|
<dimen name="dialpad_text_size">30sp</dimen>
|
||||||
|
<dimen name="activity_margin">16dp</dimen>
|
||||||
|
|
||||||
<dimen name="colorpicker_hue_width">30dp</dimen>
|
<dimen name="colorpicker_hue_width">30dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="action_settings">Settings</string>
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_dialpad">Dialpad</string>
|
||||||
<string name="action_add">New conversation</string>
|
<string name="action_add">New conversation</string>
|
||||||
<string name="action_accounts">Manage accounts</string>
|
<string name="action_accounts">Manage accounts</string>
|
||||||
<string name="action_account">Manage account</string>
|
<string name="action_account">Manage account</string>
|
||||||
|
|
|
@ -159,4 +159,20 @@
|
||||||
<style name="TextAppearance.Conversations.Body1.Secondary.OnDark" parent="TextAppearance.Conversations.Body1">
|
<style name="TextAppearance.Conversations.Body1.Secondary.OnDark" parent="TextAppearance.Conversations.Body1">
|
||||||
<item name="android:textColor">@color/white70</item>
|
<item name="android:textColor">@color/white70</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="DialpadNumberStyle">
|
||||||
|
<item name="android:includeFontPadding">false</item>
|
||||||
|
<item name="android:textSize">@dimen/dialpad_text_size</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="DialpadLetterStyle">
|
||||||
|
<item name="android:textSize">@dimen/smaller_text_size</item>
|
||||||
|
<item name="android:alpha">0.8</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<style name="ShapeAppearanceOverlay.IncomingCall" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
||||||
|
<item name="cornerFamily">rounded</item>
|
||||||
|
<item name="cornerSize">@dimen/incoming_call_radius</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue