From 1ffc81e9d36a578c8710b0fa5ffa643ea693bd6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com> Date: Mon, 12 Oct 2015 00:20:00 -0400 Subject: [PATCH] model: fix number matching Introduce and make use of SipUri, a class to parse and carry URIs handled by Ring (tel, sip, ring etc.). Replaces canonicalNumber to provide a consistent URI representation. This allows to match differently-formated URIs to build the model. Tuleap: #3 Change-Id: Ifcb03e3f7de0c67a56bd18209bb8115171d52dbb --- .../java/cx/ring/client/CallActivity.java | 8 +- .../cx/ring/client/ConversationActivity.java | 63 +++++++------- .../java/cx/ring/history/HistoryEntry.java | 20 ++++- .../main/java/cx/ring/model/CallContact.java | 74 ++++++++-------- .../src/main/java/cx/ring/model/SipUri.java | 87 +++++++++++++++++++ .../src/main/res/layout/frag_call_list.xml | 28 ++++-- 6 files changed, 202 insertions(+), 78 deletions(-) create mode 100644 ring-android/app/src/main/java/cx/ring/model/SipUri.java diff --git a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java index c481bca87..9375f6c26 100644 --- a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java +++ b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java @@ -38,6 +38,7 @@ import android.util.Log; import cx.ring.R; import cx.ring.fragments.CallFragment; import cx.ring.model.Conversation; +import cx.ring.model.SipUri; import cx.ring.model.TextMessage; import cx.ring.model.account.Account; import cx.ring.model.CallContact; @@ -153,6 +154,10 @@ public class CallActivity extends Activity implements LocalService.Callbacks, Ca if(!checkExternalCall()) { mDisplayedConference = getIntent().getParcelableExtra("conference"); + if (!mDisplayedConference.hasMultipleParticipants()) { + Conversation conv = service.startConversation(mDisplayedConference.getParticipants().get(0).getContact()); + mDisplayedConference.getParticipants().get(0).setContact(conv.getContact()); + } } Log.i(TAG, "CallActivity onCreate in:" + mDisplayedConference.isIncoming() + " out:" + mDisplayedConference.isOnGoing() + " contact" + mDisplayedConference.getParticipants().get(0).getContact().getDisplayName()); init = true; @@ -185,7 +190,8 @@ public class CallActivity extends Activity implements LocalService.Callbacks, Ca if (u != null) { String number = u.getSchemeSpecificPart(); Log.w(TAG, "number " + number); - number = CallContact.canonicalNumber(number); + SipUri uri = new SipUri(number); + number = uri.getRawUriString(); Log.w(TAG, "canonicalNumber " + number); CallContact c = service.findContactByNumber(number); Conversation conv = service.getByContact(c); diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java index 023bcbb65..23fcf437f 100644 --- a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java +++ b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java @@ -63,7 +63,7 @@ public class ConversationActivity extends Activity { private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder binder) { - service = ((LocalService.LocalBinder)binder).getService(); + service = ((LocalService.LocalBinder) binder).getService(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(LocalService.ACTION_CONF_UPDATE); registerReceiver(receiver, intentFilter); @@ -127,7 +127,9 @@ public class ConversationActivity extends Activity { public void onReceive(Context context, Intent intent) { Log.w(TAG, "onReceive " + intent.getAction() + " " + intent.getDataString()); //conversation = service.getConversation(conversation.getId()); - conversation = service.getByContact(conversation.getContact()); + Conversation newc = service.getByContact(conversation.getContact()); + if (newc != null) + conversation = newc; adapter.updateDataset(conversation.getHistory()); scrolltoBottom(); Conference conf = conversation.getCurrentCall(); @@ -231,16 +233,16 @@ public class ConversationActivity extends Activity { boolean sep = false; boolean sep_same = false; - if (position > 0 && texts.get(position-1).text != null) { - TextMessage msg = texts.get(position-1).text; + if (position > 0 && texts.get(position - 1).text != null) { + TextMessage msg = texts.get(position - 1).text; if (msg.isIncoming() && txt.text.isIncoming() && msg.getNumber().equals(txt.text.getNumber())) sep_same = true; } - if (position > 0 && texts.get(position-1).text != null && position < texts.size()-1) { - TextMessage msg = texts.get(position+1).text; + if (position > 0 && texts.get(position - 1).text != null && position < texts.size() - 1) { + TextMessage msg = texts.get(position + 1).text; if (msg != null) { long diff = msg.getTimestamp() - txt.text.getTimestamp(); - if (diff > 30*1000) + if (diff > 30 * 1000) sep = true; } else { sep = true; @@ -345,7 +347,7 @@ public class ConversationActivity extends Activity { if (number == null || number.isEmpty()) number = conversation.contact.getPhones().get(0).getNumber(); try { - service.getRemoteService().sendAccountTextMessage(account, number, txt); + service.getRemoteService().sendAccountTextMessage(account, CallContact.canonicalNumber(number), txt); } catch (RemoteException e) { e.printStackTrace(); } @@ -359,7 +361,7 @@ public class ConversationActivity extends Activity { } private void onAudioCall() { - Conference conf = conversation.getCurrentCall(); + Conference conf = conversation == null ? null : conversation.getCurrentCall(); if (conf != null) { startActivity(new Intent(ConversationActivity.this.getApplicationContext(), CallActivity.class).putExtra("conference", conversation.getCurrentCall())); return; @@ -370,7 +372,7 @@ public class ConversationActivity extends Activity { return; } - Account usedAccount = service.getAccounts().get(0); + Account usedAccount = null; CallContact contact = null; if (conversation != null) { String last_used = conversation.getLastAccountUsed(); @@ -387,33 +389,30 @@ public class ConversationActivity extends Activity { } } contact = conversation.getContact(); - } + } String number = preferredNumber; - if (number == null) - number = conversation.getLastNumberUsed(usedAccount.getAccountID()); - if (number == null && contact != null) - number = contact.getPhones().get(0).getNumber(); - - //conversation.getHistory().getAccountID() - //if (usedAccount.isRegistered() || usedAccount.isIP2IP()) { - /* Bundle args = new Bundle(); - args.putParcelable(SipCall.ACCOUNT, usedAccount); - args.putInt(SipCall.STATE, SipCall.State.NONE); - args.putInt(SipCall.TYPE, SipCall.Direction.OUTGOING); - args.putParcelable(SipCall.CONTACT, contact);*/ + if (usedAccount != null) { + if (number == null) + number = conversation.getLastNumberUsed(usedAccount.getAccountID()); + if (number == null && contact != null) + number = contact.getPhones().get(0).getNumber(); + } else { + if (number == null && contact != null) + number = contact.getPhones().get(0).getNumber(); + usedAccount = service.guessAccount(contact, number); + } + number = CallContact.canonicalNumber(number); + SipCall call = new SipCall(null, usedAccount.getAccountID(), number, SipCall.Direction.OUTGOING); call.setContact(contact); - try { - launchCallActivity(call); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, e.toString()); - } - /*} else { - createNotRegisteredDialog().show(); - }*/ + try { + launchCallActivity(call); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, e.toString()); + } } } diff --git a/ring-android/app/src/main/java/cx/ring/history/HistoryEntry.java b/ring-android/app/src/main/java/cx/ring/history/HistoryEntry.java index bf7c89f26..97f946149 100644 --- a/ring-android/app/src/main/java/cx/ring/history/HistoryEntry.java +++ b/ring-android/app/src/main/java/cx/ring/history/HistoryEntry.java @@ -176,12 +176,28 @@ public class HistoryEntry implements Parcelable { return c; return null; } + public HistoryCall getLastIncomingCall() { + for (HistoryCall c : calls.descendingMap().values()) + if (c.isIncoming()) + return c; + return null; + } + public TextMessage getLastIncomingText() { + for (TextMessage c : text_messages.descendingMap().values()) + if (c.isIncoming()) + return c; + return null; + } public String getLastNumberUsed() { HistoryCall call = getLastOutgoingCall(); TextMessage text = getLastOutgoingText(); - if (call == null && text == null) - return null; + if (call == null && text == null) { + call = getLastIncomingCall(); + text = getLastIncomingText(); + if (call == null && text == null) + return null; + } if (call == null) return text.getNumber(); if (text == null) diff --git a/ring-android/app/src/main/java/cx/ring/model/CallContact.java b/ring-android/app/src/main/java/cx/ring/model/CallContact.java index e11861c07..90edf823e 100644 --- a/ring-android/app/src/main/java/cx/ring/model/CallContact.java +++ b/ring-android/app/src/main/java/cx/ring/model/CallContact.java @@ -32,7 +32,6 @@ package cx.ring.model; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -44,25 +43,28 @@ import android.os.Parcelable; import android.provider.ContactsContract.Profile; import android.support.annotation.NonNull; -import cx.ring.client.ConversationActivity; -import cx.ring.service.LocalService; - public class CallContact implements Parcelable { - static public final Pattern ANGLE_BRACKETS_PATTERN = Pattern.compile("(?:[^<>]+<)?([^<>]+)>?\\s*"); - public static final Pattern RING_ID_PATTERN = Pattern.compile("^\\s*(?:ring(?:[\\s\\:]+))?(\\p{XDigit}{40})(?:@ring\\.dht)?\\s*$", Pattern.CASE_INSENSITIVE); - public static int DEFAULT_ID = 0; private long id; private String key; private String mDisplayName; private long photo_id; - private ArrayList<Phone> phones/*, sip_phones*/; + private final ArrayList<Phone> phones; private String mEmail; private boolean isUser; private WeakReference<Bitmap> contact_photo = new WeakReference<>(null); + private boolean stared = false; + + public CallContact(long cID) { + this(cID, null, null, -1); + } + + public CallContact(long cID, String k, String displayName, long photoID) { + this(cID, k, displayName, photoID, new ArrayList<Phone>(), null, false); + } - private CallContact(long cID, String k, String displayName, long photoID, ArrayList<Phone> p, String mail, boolean user) { + public CallContact(long cID, String k, String displayName, long photoID, ArrayList<Phone> p, String mail, boolean user) { id = cID; key = k; mDisplayName = displayName; @@ -72,19 +74,14 @@ public class CallContact implements Parcelable { isUser = user; } - private static String nobracketsNumber(@NonNull String number) { - Matcher m = ANGLE_BRACKETS_PATTERN.matcher(number); - if (m.find()) - return m.group(1); - return number; + public void setContactInfos(String k, String displayName, long photo_id) { + key = k; + mDisplayName = displayName; + this.photo_id = photo_id; } public static String canonicalNumber(@NonNull String number) { - number = nobracketsNumber(number); - Matcher m = RING_ID_PATTERN.matcher(number); - if (m.find()) - return "ring:"+m.group(1); - return number; + return new SipUri(number).getRawUriString(); } public ArrayList<String> getIds() { @@ -107,6 +104,7 @@ public class CallContact implements Parcelable { } public CallContact(Parcel in) { + phones = new ArrayList<>(); readFromParcel(in); } @@ -119,8 +117,6 @@ public class CallContact implements Parcelable { return mDisplayName; if (!phones.isEmpty()) return phones.get(0).getNumber(); - /*if (!sip_phones.isEmpty()) - return sip_phones.get(0).getNumber();*/ return ""; } @@ -136,9 +132,9 @@ public class CallContact implements Parcelable { return phones; } - public void setPhones(ArrayList<Phone> phones) { + /*public void setPhones(ArrayList<Phone> phones) { this.phones = phones; - } + }*/ public String getEmail() { return mEmail; @@ -175,6 +171,17 @@ public class CallContact implements Parcelable { return key; } + public void setStared(boolean stared) { + this.stared = stared; + } + public void setStared() { + this.stared = true; + } + + public boolean isStared() { + return stared; + } + public static class ContactBuilder { long contactID; @@ -223,9 +230,9 @@ public class CallContact implements Parcelable { return new CallContact(-1, null, to, 0, phones, "", false); } - public static CallContact buildUserContact(ContentResolver cr) { + public static CallContact buildUserContact(ContentResolver c) { String[] mProjection = new String[] { Profile._ID, Profile.LOOKUP_KEY, Profile.DISPLAY_NAME_PRIMARY, Profile.PHOTO_ID }; - Cursor mProfileCursor = cr.query(Profile.CONTENT_URI, mProjection, null, null, null); + Cursor mProfileCursor = c.query(Profile.CONTENT_URI, mProjection, null, null, null); CallContact result; if (mProfileCursor.getCount() > 0) { mProfileCursor.moveToFirst(); @@ -257,7 +264,7 @@ public class CallContact implements Parcelable { dest.writeTypedList(phones); dest.writeString(mEmail); dest.writeByte((byte) (isUser ? 1 : 0)); - + dest.writeByte(stared ? (byte)1 : (byte)0); } private void readFromParcel(Parcel in) { @@ -265,10 +272,11 @@ public class CallContact implements Parcelable { key = in.readString(); mDisplayName = in.readString(); photo_id = in.readLong(); - phones = new ArrayList<>(); + phones.clear(); in.readTypedList(phones, Phone.CREATOR); mEmail = in.readString(); - isUser = in.readByte() == 1; + isUser = in.readByte() != 0; + stared = in.readByte() != 0; } public static final Parcelable.Creator<CallContact> CREATOR = new Parcelable.Creator<CallContact>() { @@ -302,12 +310,6 @@ public class CallContact implements Parcelable { return v; return UNKNOWN; } - /*public static NumberType guess(String num) { - String canon = canonicalNumber(num); - Matcher m = URI_NUMBER_REGEX.matcher(canon); - - return UNKNOWN; - }*/ } public static class Phone implements Parcelable { @@ -392,9 +394,7 @@ public class CallContact implements Parcelable { } public boolean hasPhoto() { - if (contact_photo.get() != null) - return true; - return false; + return contact_photo.get() != null; } public Bitmap getPhoto() { diff --git a/ring-android/app/src/main/java/cx/ring/model/SipUri.java b/ring-android/app/src/main/java/cx/ring/model/SipUri.java new file mode 100644 index 000000000..12a0746c7 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/model/SipUri.java @@ -0,0 +1,87 @@ +package cx.ring.model; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import cx.ring.utils.Utilities; + +public class SipUri { + public String display_name = null; + public String sheme = null; + public String username = null; + public String host = null; + public String port = null; + + public static final Pattern ANGLE_BRACKETS_PATTERN = Pattern.compile("^(?:([^<>]+)\\s*<)?([^<>]+)>?\\s*$"); + public static final Pattern RING_ID_PATTERN = Pattern.compile("^\\p{XDigit}{40}$", Pattern.CASE_INSENSITIVE); + public static final Pattern RING_URI_PATTERN = Pattern.compile("^\\s*(?:ring(?:[\\s\\:]+))?(\\p{XDigit}{40})(?:@ring\\.dht)?\\s*$", Pattern.CASE_INSENSITIVE); + public static final Pattern URI_PATTERN = Pattern.compile("^\\s*(\\w+:)?(?:([\\w.]+)@)?(?:([\\d\\w\\.]+)(?::(\\d+))?)\\s*$", Pattern.CASE_INSENSITIVE); + + public SipUri() {} + + public SipUri(String uri) { + parseUri(uri); + } + + public String getRawUriString() { + if (host != null && RING_ID_PATTERN.matcher(host).find()) + return "ring:" + host; + else if (username != null && RING_ID_PATTERN.matcher(username).find()) + return "ring:" + username; + + StringBuilder builder = new StringBuilder(64); + if (sheme != null) + builder.append(sheme); + if (username != null && !username.isEmpty()) + builder.append(username).append("@"); + if (host != null) + builder.append(host); + if (port != null && !port.isEmpty()) + builder.append(":").append(port); + return builder.toString(); + } + + public String getUriString() { + if (display_name == null || display_name.isEmpty()) + return getRawUriString(); + return display_name+" <"+getRawUriString()+">"; + } + + public String toString() { + return getUriString(); + } + + public boolean isSingleIp() { + return (username == null || username.isEmpty()) && Utilities.isIpAddress(host); + } + + public boolean isRingId() { + if (host != null && RING_ID_PATTERN.matcher(host).find()) + return true; + else if (username != null && RING_ID_PATTERN.matcher(username).find()) + return true; + return false; + } + + private void parseUri(String uri) { + Matcher m = ANGLE_BRACKETS_PATTERN.matcher(uri); + if (m.find()) { + display_name = m.group(1); + parseUriRaw(m.group(2)); + } else { + parseUriRaw(uri); + } + } + + private void parseUriRaw(String uri) { + Matcher m = URI_PATTERN.matcher(uri); + if (m.find()) { + sheme = m.group(1); + username = m.group(2); + host = m.group(3); + port = m.group(4); + } else { + host = uri; + } + } +} diff --git a/ring-android/app/src/main/res/layout/frag_call_list.xml b/ring-android/app/src/main/res/layout/frag_call_list.xml index 91d868629..75c02d9ed 100644 --- a/ring-android/app/src/main/res/layout/frag_call_list.xml +++ b/ring-android/app/src/main/res/layout/frag_call_list.xml @@ -38,13 +38,29 @@ as that of the covered work. android:paddingTop="@dimen/padding_small" android:background="@android:color/white"> - <ListView - android:id="@+id/confs_list" - android:layout_width="match_parent" + <ViewSwitcher + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:divider="@null" - tools:listitem="@layout/item_calllist"> - </ListView> + android:id="@+id/list_switcher" > + + <ListView + android:id="@+id/confs_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:divider="@null" + tools:listitem="@layout/item_calllist"> + </ListView> + + <se.emilsjolander.stickylistheaders.StickyListHeadersListView + android:id="@+id/contacts_stickylv" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:drawSelectorOnTop="true" + android:fastScrollEnabled="true" + android:scrollbarStyle="outsideOverlay" + android:divider="@null" /> + + </ViewSwitcher> <android.support.design.widget.FloatingActionButton android:id="@+id/newconv_fab" -- GitLab