diff options
18 files changed, 352 insertions, 91 deletions
diff --git a/java/com/android/contacts/common/list/ContactTileView.java b/java/com/android/contacts/common/list/ContactTileView.java index cfd52f385..072e07dd2 100644 --- a/java/com/android/contacts/common/list/ContactTileView.java +++ b/java/com/android/contacts/common/list/ContactTileView.java @@ -22,7 +22,6 @@ import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.TextView; import com.android.contacts.common.MoreContactUtils; import com.android.contacts.common.R; import com.android.dialer.callintent.CallInitiationType; @@ -30,6 +29,7 @@ import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.common.LogUtil; import com.android.dialer.contactphoto.ContactPhotoManager; import com.android.dialer.contactphoto.ContactPhotoManager.DefaultImageRequest; +import com.android.dialer.widget.BidiTextView; /** A ContactTile displays a contact's picture and name */ public abstract class ContactTileView extends FrameLayout { @@ -38,7 +38,7 @@ public abstract class ContactTileView extends FrameLayout { protected Listener mListener; private Uri mLookupUri; private ImageView mPhoto; - private TextView mName; + private BidiTextView mName; private ContactPhotoManager mPhotoManager = null; public ContactTileView(Context context, AttributeSet attrs) { @@ -48,7 +48,7 @@ public abstract class ContactTileView extends FrameLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - mName = (TextView) findViewById(R.id.contact_tile_name); + mName = (BidiTextView) findViewById(R.id.contact_tile_name); mPhoto = (ImageView) findViewById(R.id.contact_tile_image); OnClickListener listener = createClickListener(); diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java index 8b7a92bd4..71cbc8c12 100644 --- a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java +++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java @@ -21,11 +21,12 @@ import android.view.View; import android.widget.TextView; import com.android.dialer.app.R; import com.android.dialer.calllogutils.CallTypeIconsView; +import com.android.dialer.widget.BidiTextView; /** Encapsulates the views that are used to display the details of a phone call in the call log. */ public final class PhoneCallDetailsViews { - public final TextView nameView; + public final BidiTextView nameView; public final View callTypeView; public final CallTypeIconsView callTypeIcons; public final TextView callLocationAndDate; @@ -36,7 +37,7 @@ public final class PhoneCallDetailsViews { public final TextView callAccountLabel; private PhoneCallDetailsViews( - TextView nameView, + BidiTextView nameView, View callTypeView, CallTypeIconsView callTypeIcons, TextView callLocationAndDate, @@ -65,7 +66,7 @@ public final class PhoneCallDetailsViews { */ public static PhoneCallDetailsViews fromView(View view) { return new PhoneCallDetailsViews( - (TextView) view.findViewById(R.id.name), + (BidiTextView) view.findViewById(R.id.name), view.findViewById(R.id.call_type), (CallTypeIconsView) view.findViewById(R.id.call_type_icons), (TextView) view.findViewById(R.id.call_location_and_date), @@ -78,7 +79,7 @@ public final class PhoneCallDetailsViews { public static PhoneCallDetailsViews createForTest(Context context) { return new PhoneCallDetailsViews( - new TextView(context), + new BidiTextView(context), new View(context), new CallTypeIconsView(context), new TextView(context), diff --git a/java/com/android/dialer/app/list/PhoneFavoriteSquareTileView.java b/java/com/android/dialer/app/list/PhoneFavoriteSquareTileView.java index 330a3614c..6096ca872 100644 --- a/java/com/android/dialer/app/list/PhoneFavoriteSquareTileView.java +++ b/java/com/android/dialer/app/list/PhoneFavoriteSquareTileView.java @@ -28,12 +28,11 @@ import com.android.dialer.app.R; import com.android.dialer.compat.CompatUtils; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; +import com.android.dialer.widget.BidiTextView; /** Displays the contact's picture overlaid with their name and number type in a tile. */ public class PhoneFavoriteSquareTileView extends PhoneFavoriteTileView { - private static final String TAG = PhoneFavoriteSquareTileView.class.getSimpleName(); - private final float heightToWidthRatio; private ImageButton secondaryButton; @@ -50,11 +49,12 @@ public class PhoneFavoriteSquareTileView extends PhoneFavoriteTileView { @Override protected void onFinishInflate() { super.onFinishInflate(); - final TextView nameView = (TextView) findViewById(R.id.contact_tile_name); + BidiTextView nameView = findViewById(R.id.contact_tile_name); nameView.setElegantTextHeight(false); - final TextView phoneTypeView = (TextView) findViewById(R.id.contact_tile_phone_type); + + TextView phoneTypeView = findViewById(R.id.contact_tile_phone_type); phoneTypeView.setElegantTextHeight(false); - secondaryButton = (ImageButton) findViewById(R.id.contact_tile_secondary_button); + secondaryButton = findViewById(R.id.contact_tile_secondary_button); } @Override diff --git a/java/com/android/dialer/app/res/layout/call_log_list_item.xml b/java/com/android/dialer/app/res/layout/call_log_list_item.xml index acaa82085..d1111103e 100644 --- a/java/com/android/dialer/app/res/layout/call_log_list_item.xml +++ b/java/com/android/dialer/app/res/layout/call_log_list_item.xml @@ -93,7 +93,7 @@ android:gravity="center_vertical" android:layout_marginStart="@dimen/call_log_list_item_info_margin_start"> - <TextView + <com.android.dialer.widget.BidiTextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/java/com/android/dialer/app/res/layout/phone_favorite_tile_view.xml b/java/com/android/dialer/app/res/layout/phone_favorite_tile_view.xml index d2712e9fe..3aeba98a7 100644 --- a/java/com/android/dialer/app/res/layout/phone_favorite_tile_view.xml +++ b/java/com/android/dialer/app/res/layout/phone_favorite_tile_view.xml @@ -63,7 +63,7 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal"> - <TextView + <com.android.dialer.widget.BidiTextView android:id="@+id/contact_tile_name" android:layout_width="0dp" android:layout_height="wrap_content" diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java index e4fded173..cb84a28c2 100644 --- a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java +++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java @@ -40,6 +40,7 @@ import com.android.dialer.dialercontact.DialerContact; import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; +import com.android.dialer.widget.BidiTextView; /** * ViewHolder for the header in {@link OldCallDetailsActivity} or {@link CallDetailsActivity}. @@ -51,7 +52,7 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder private final CallDetailsHeaderListener callDetailsHeaderListener; private final ImageView callbackButton; - private final TextView nameView; + private final BidiTextView nameView; private final TextView numberView; private final TextView networkView; private final QuickContactBadge contactPhoto; diff --git a/java/com/android/dialer/calldetails/res/layout/contact_container.xml b/java/com/android/dialer/calldetails/res/layout/contact_container.xml index 5f531ab43..9506183a0 100644 --- a/java/com/android/dialer/calldetails/res/layout/contact_container.xml +++ b/java/com/android/dialer/calldetails/res/layout/contact_container.xml @@ -44,7 +44,7 @@ android:minHeight="@dimen/call_details_contact_photo_size" android:orientation="vertical"> - <TextView + <com.android.dialer.widget.BidiTextView android:id="@+id/contact_name" style="@style/PrimaryText" android:layout_width="wrap_content" diff --git a/java/com/android/dialer/contactsfragment/ContactViewHolder.java b/java/com/android/dialer/contactsfragment/ContactViewHolder.java index 2730c0d38..e1883322c 100644 --- a/java/com/android/dialer/contactsfragment/ContactViewHolder.java +++ b/java/com/android/dialer/contactsfragment/ContactViewHolder.java @@ -28,12 +28,13 @@ import com.android.dialer.common.Assert; import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; +import com.android.dialer.widget.BidiTextView; /** View holder for a contact. */ final class ContactViewHolder extends RecyclerView.ViewHolder implements OnClickListener { private final TextView header; - private final TextView name; + private final BidiTextView name; private final QuickContactBadge photo; private final Context context; private final OnContactSelectedListener onContactSelectedListener; diff --git a/java/com/android/dialer/contactsfragment/res/layout/contact_row.xml b/java/com/android/dialer/contactsfragment/res/layout/contact_row.xml index 9e829fee4..b65a8c87f 100644 --- a/java/com/android/dialer/contactsfragment/res/layout/contact_row.xml +++ b/java/com/android/dialer/contactsfragment/res/layout/contact_row.xml @@ -41,7 +41,7 @@ android:layout_height="@dimen/photo_size" android:clickable="false"/> - <TextView + <com.android.dialer.widget.BidiTextView android:id="@+id/contact_name" android:layout_width="wrap_content" android:layout_height="match_parent" diff --git a/java/com/android/dialer/i18n/DialerBidiFormatter.java b/java/com/android/dialer/i18n/DialerBidiFormatter.java new file mode 100644 index 000000000..4ebaa666c --- /dev/null +++ b/java/com/android/dialer/i18n/DialerBidiFormatter.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.i18n; + +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.support.v4.text.BidiFormatter; +import android.text.TextUtils; +import android.util.Patterns; +import com.android.dialer.common.Assert; +import com.google.auto.value.AutoValue; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; + +/** + * An enhanced version of {@link BidiFormatter} that can recognize a formatted phone number + * containing whitespaces. + * + * <p>Formatted phone numbers usually contain one or more whitespaces (e.g., "+1 650-253-0000", + * "(650) 253-0000", etc). {@link BidiFormatter} mistakes such a number for tokens separated by + * whitespaces. Therefore, these numbers can't be correctly shown in a RTL context (e.g., "+1 + * 650-253-0000" would be shown as "650-253-0000 1+".) + */ +public final class DialerBidiFormatter { + + private DialerBidiFormatter() {} + + /** + * Divides the given text into segments, applies {@link BidiFormatter#unicodeWrap(CharSequence)} + * to each segment, and then reassembles the text. + * + * <p>A segment of the text is either a substring matching {@link Patterns#PHONE} or one that does + * not. + * + * @see BidiFormatter#unicodeWrap(CharSequence) + */ + public static CharSequence unicodeWrap(@Nullable CharSequence text) { + if (TextUtils.isEmpty(text)) { + return text; + } + + List<CharSequence> segments = segmentText(text); + + StringBuilder formattedText = new StringBuilder(); + for (CharSequence segment : segments) { + formattedText.append(BidiFormatter.getInstance().unicodeWrap(segment)); + } + + return formattedText.toString(); + } + + /** + * Segments the given text using {@link Patterns#PHONE}. + * + * <p>For example, "Mobile, +1 650-253-0000, 20 seconds" will be segmented into {"Mobile, ", "+1 + * 650-253-0000", ", 20 seconds"}. + */ + @VisibleForTesting + static List<CharSequence> segmentText(CharSequence text) { + Assert.checkArgument(!TextUtils.isEmpty(text)); + + List<CharSequence> segments = new ArrayList<>(); + + // Find the start index and the end index of each segment matching the phone number pattern. + Matcher matcher = Patterns.PHONE.matcher(text.toString()); + List<Range> segmentRanges = new ArrayList<>(); + while (matcher.find()) { + segmentRanges.add(Range.newBuilder().setStart(matcher.start()).setEnd(matcher.end()).build()); + } + + // Segment the text. + int currIndex = 0; + for (Range segmentRange : segmentRanges) { + if (currIndex < segmentRange.getStart()) { + segments.add(text.subSequence(currIndex, segmentRange.getStart())); + } + + segments.add(text.subSequence(segmentRange.getStart(), segmentRange.getEnd())); + currIndex = segmentRange.getEnd(); + } + if (currIndex < text.length()) { + segments.add(text.subSequence(currIndex, text.length())); + } + + return segments; + } + + /** Represents the start index (inclusive) and the end index (exclusive) of a text segment. */ + @AutoValue + abstract static class Range { + static Builder newBuilder() { + return new AutoValue_DialerBidiFormatter_Range.Builder(); + } + + abstract int getStart(); + + abstract int getEnd(); + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setStart(int start); + + abstract Builder setEnd(int end); + + abstract Range build(); + } + } +} diff --git a/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml b/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml index 9be7fa046..a0d9dd274 100644 --- a/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml +++ b/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml @@ -39,7 +39,7 @@ android:layout_centerVertical="true" android:layout_marginStart="8dp"> - <TextView + <com.android.dialer.widget.BidiTextView android:id="@+id/primary" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -47,7 +47,7 @@ android:fontFamily="sans-serif" style="@style/PrimaryText"/> - <TextView + <com.android.dialer.widget.BidiTextView android:id="@+id/secondary" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java index e36df4bf7..9d18e07b1 100644 --- a/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/cp2/SearchContactViewHolder.java @@ -30,7 +30,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.QuickContactBadge; -import android.widget.TextView; import com.android.dialer.common.Assert; import com.android.dialer.contactphoto.ContactPhotoManager; import com.android.dialer.dialercontact.DialerContact; @@ -44,6 +43,7 @@ import com.android.dialer.searchfragment.common.QueryBoldingUtil; import com.android.dialer.searchfragment.common.R; import com.android.dialer.searchfragment.common.RowClickListener; import com.android.dialer.searchfragment.common.SearchCursor; +import com.android.dialer.widget.BidiTextView; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -67,8 +67,8 @@ public final class SearchContactViewHolder extends ViewHolder implements OnClick private final RowClickListener listener; private final QuickContactBadge photo; - private final TextView nameOrNumberView; - private final TextView numberView; + private final BidiTextView nameOrNumberView; + private final BidiTextView numberView; private final ImageView callToActionView; private final Context context; diff --git a/java/com/android/dialer/widget/BidiTextView.java b/java/com/android/dialer/widget/BidiTextView.java new file mode 100644 index 000000000..6cf1aaedf --- /dev/null +++ b/java/com/android/dialer/widget/BidiTextView.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.widget; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.widget.TextView; +import com.android.dialer.i18n.DialerBidiFormatter; + +/** A {@link TextView} that applies bidirectional formatting to its text. */ +public final class BidiTextView extends TextView { + + public BidiTextView(Context context) { + super(context); + } + + public BidiTextView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setText(CharSequence text, BufferType type) { + super.setText(DialerBidiFormatter.unicodeWrap(text), type); + } +} diff --git a/java/com/android/incallui/rtt/impl/RttChatAdapter.java b/java/com/android/incallui/rtt/impl/RttChatAdapter.java index 7e2b571c1..8d924c9f8 100644 --- a/java/com/android/incallui/rtt/impl/RttChatAdapter.java +++ b/java/com/android/incallui/rtt/impl/RttChatAdapter.java @@ -37,13 +37,11 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde } private static final String KEY_MESSAGE_DATA = "key_message_data"; - private static final String KEY_LAST_REMOTE_MESSAGE = "key_last_remote_message"; private static final String KEY_LAST_LOCAL_MESSAGE = "key_last_local_message"; private final Context context; private final List<RttChatMessage> rttMessages; private int lastIndexOfLocalMessage = -1; - private int lastIndexOfRemoteMessage = -1; private final MessageListener messageListener; RttChatAdapter(Context context, MessageListener listener, @Nullable Bundle savedInstanceState) { @@ -53,7 +51,6 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde rttMessages = new ArrayList<>(); } else { rttMessages = savedInstanceState.getParcelableArrayList(KEY_MESSAGE_DATA); - lastIndexOfRemoteMessage = savedInstanceState.getInt(KEY_LAST_REMOTE_MESSAGE); lastIndexOfLocalMessage = savedInstanceState.getInt(KEY_LAST_LOCAL_MESSAGE); } } @@ -84,33 +81,6 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde return rttMessages.size(); } - private void updateCurrentRemoteMessage(String newText) { - RttChatMessage rttChatMessage = null; - if (lastIndexOfRemoteMessage >= 0) { - rttChatMessage = rttMessages.get(lastIndexOfRemoteMessage); - } - List<RttChatMessage> newMessages = - RttChatMessage.getRemoteRttChatMessage(rttChatMessage, newText); - - if (rttChatMessage == null) { - lastIndexOfRemoteMessage = rttMessages.size(); - rttMessages.add(lastIndexOfRemoteMessage, newMessages.get(0)); - rttMessages.addAll(newMessages.subList(1, newMessages.size())); - notifyItemRangeInserted(lastIndexOfRemoteMessage, newMessages.size()); - lastIndexOfRemoteMessage = rttMessages.size() - 1; - } else { - rttMessages.set(lastIndexOfRemoteMessage, newMessages.get(0)); - int lastIndex = rttMessages.size(); - rttMessages.addAll(newMessages.subList(1, newMessages.size())); - - notifyItemChanged(lastIndexOfRemoteMessage); - notifyItemRangeInserted(lastIndex, newMessages.size()); - } - if (rttMessages.get(lastIndexOfRemoteMessage).isFinished()) { - lastIndexOfRemoteMessage = -1; - } - } - private void updateCurrentLocalMessage(String newMessage) { RttChatMessage rttChatMessage = null; if (lastIndexOfLocalMessage >= 0) { @@ -128,9 +98,6 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde if (TextUtils.isEmpty(rttChatMessage.getContent())) { rttMessages.remove(lastIndexOfLocalMessage); notifyItemRemoved(lastIndexOfLocalMessage); - if (lastIndexOfRemoteMessage > lastIndexOfLocalMessage) { - lastIndexOfRemoteMessage -= 1; - } lastIndexOfLocalMessage = -1; } else { notifyItemChanged(lastIndexOfLocalMessage); @@ -138,8 +105,13 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde } } + private void updateCurrentRemoteMessage(String newMessage) { + RttChatMessage.updateRemoteRttChatMessage(rttMessages, newMessage); + lastIndexOfLocalMessage = RttChatMessage.getLastIndexLocalMessage(rttMessages); + notifyDataSetChanged(); + } + void addLocalMessage(String message) { - LogUtil.enterBlock("RttChatAdapater.addLocalMessage"); updateCurrentLocalMessage(message); if (messageListener != null) { messageListener.newMessageAdded(); @@ -166,7 +138,6 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde } void addRemoteMessage(String message) { - LogUtil.enterBlock("RttChatAdapater.addRemoteMessage"); if (TextUtils.isEmpty(message)) { return; } @@ -176,9 +147,24 @@ public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolde } } + /** + * Retrieve last local message and update the index. This is used when deleting to previous + * message bubble. + */ + @Nullable + String retrieveLastLocalMessage() { + lastIndexOfLocalMessage = RttChatMessage.getLastIndexLocalMessage(rttMessages); + if (lastIndexOfLocalMessage >= 0) { + RttChatMessage rttChatMessage = rttMessages.get(lastIndexOfLocalMessage); + rttChatMessage.unfinish(); + return rttChatMessage.getContent(); + } else { + return null; + } + } + void onSaveInstanceState(@NonNull Bundle bundle) { bundle.putParcelableArrayList(KEY_MESSAGE_DATA, (ArrayList<RttChatMessage>) rttMessages); - bundle.putInt(KEY_LAST_REMOTE_MESSAGE, lastIndexOfRemoteMessage); bundle.putInt(KEY_LAST_LOCAL_MESSAGE, lastIndexOfLocalMessage); } } diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java index 90bf199b2..56ac2429c 100644 --- a/java/com/android/incallui/rtt/impl/RttChatFragment.java +++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java @@ -103,6 +103,8 @@ public class RttChatFragment extends Fragment private boolean isTimerStarted; private RttOverflowMenu overflowMenu; private SecondaryInfo savedSecondaryInfo; + private TextView statusBanner; + private PrimaryInfo primaryInfo; /** * Create a new instance of RttChatFragment. @@ -164,6 +166,26 @@ public class RttChatFragment extends Fragment editText = view.findViewById(R.id.rtt_chat_input); editText.setOnEditorActionListener(this); editText.addTextChangedListener(this); + + editText.setOnKeyListener( + (v, keyCode, event) -> { + // This is only triggered when input method doesn't handle delete key, which means the + // current + // input box is empty. + if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) { + String lastMessage = adapter.retrieveLastLocalMessage(); + if (lastMessage != null) { + isClearingInput = true; + editText.setText(lastMessage); + editText.setSelection(lastMessage.length()); + isClearingInput = false; + rttCallScreenDelegate.onLocalMessage("\b"); + return true; + } + return false; + } + return false; + }); recyclerView = view.findViewById(R.id.rtt_recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); layoutManager.setStackFromEnd(true); @@ -201,13 +223,16 @@ public class RttChatFragment extends Fragment nameTextView = view.findViewById(R.id.rtt_name_or_number); chronometer = view.findViewById(R.id.rtt_timer); + statusBanner = view.findViewById(R.id.rtt_status_banner); return view; } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEND) { - submitButton.performClick(); + if (!TextUtils.isEmpty(editText.getText())) { + submitButton.performClick(); + } return true; } return false; @@ -312,6 +337,7 @@ public class RttChatFragment extends Fragment public void setPrimary(@NonNull PrimaryInfo primaryInfo) { LogUtil.i("RttChatFragment.setPrimary", primaryInfo.toString()); nameTextView.setText(primaryInfo.name()); + this.primaryInfo = primaryInfo; } @Override @@ -359,6 +385,20 @@ public class RttChatFragment extends Fragment chronometer.start(); isTimerStarted = true; } + if (primaryCallState.state() == State.DIALING) { + showWaitingForJoinBanner(); + } else { + hideWaitingForJoinBanner(); + } + } + + private void showWaitingForJoinBanner() { + statusBanner.setText(getString(R.string.rtt_status_banner_text, primaryInfo.name())); + statusBanner.setVisibility(View.VISIBLE); + } + + private void hideWaitingForJoinBanner() { + statusBanner.setVisibility(View.GONE); } @Override diff --git a/java/com/android/incallui/rtt/impl/RttChatMessage.java b/java/com/android/incallui/rtt/impl/RttChatMessage.java index fd83fb82f..cbc53ef15 100644 --- a/java/com/android/incallui/rtt/impl/RttChatMessage.java +++ b/java/com/android/incallui/rtt/impl/RttChatMessage.java @@ -19,10 +19,9 @@ package com.android.incallui.rtt.impl; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import com.android.dialer.common.Assert; import com.android.incallui.rtt.protocol.Constants; import com.google.common.base.Splitter; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -44,13 +43,17 @@ final class RttChatMessage implements Parcelable { isFinished = true; } + void unfinish() { + isFinished = false; + } + public void append(String text) { for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); - if (c != '\b') { - content.append(c); - } else if (content.length() > 0) { + if (c == '\b' && content.length() > 0 && content.charAt(content.length() - 1) != '\b') { content.deleteCharAt(content.length() - 1); + } else { + content.append(c); } } } @@ -87,39 +90,92 @@ final class RttChatMessage implements Parcelable { return modify.toString(); } - /** Convert remote input text into an array of {@code RttChatMessage}. */ - static List<RttChatMessage> getRemoteRttChatMessage( - @Nullable RttChatMessage currentMessage, @NonNull String text) { + /** Update list of {@code RttChatMessage} based on given remote text. */ + static void updateRemoteRttChatMessage(List<RttChatMessage> messageList, @NonNull String text) { + Assert.isNotNull(messageList); Iterator<String> splitText = SPLITTER.split(text).iterator(); - List<RttChatMessage> messageList = new ArrayList<>(); - - String firstMessageContent = splitText.next(); - RttChatMessage firstMessage = currentMessage; - if (firstMessage == null) { - firstMessage = new RttChatMessage(); - firstMessage.isRemote = true; - } - firstMessage.append(firstMessageContent); - if (splitText.hasNext() || text.endsWith(Constants.BUBBLE_BREAKER)) { - firstMessage.finish(); - } - messageList.add(firstMessage); while (splitText.hasNext()) { String singleMessageContent = splitText.next(); - if (singleMessageContent.isEmpty()) { - continue; + RttChatMessage message; + int index = getLastIndexUnfinishedRemoteMessage(messageList); + if (index < 0) { + message = new RttChatMessage(); + message.append(singleMessageContent); + message.isRemote = true; + if (splitText.hasNext()) { + message.finish(); + } + if (message.content.length() != 0) { + messageList.add(message); + } + } else { + message = messageList.get(index); + message.append(singleMessageContent); + if (splitText.hasNext()) { + message.finish(); + } + if (message.content.length() == 0) { + messageList.remove(index); + } } - RttChatMessage message = new RttChatMessage(); - message.append(singleMessageContent); - message.isRemote = true; - if (splitText.hasNext()) { - message.finish(); + StringBuilder content = message.content; + // Delete previous messages. + while (content.length() > 0 && content.charAt(0) == '\b') { + messageList.remove(message); + content.delete(0, 1); + int previous = getLastIndexRemoteMessage(messageList); + // There are more backspaces than existing characters. + if (previous < 0) { + while (content.length() > 0 && content.charAt(0) == '\b') { + content.deleteCharAt(0); + } + // Add message if there are still characters after backspaces. + if (content.length() > 0) { + message = new RttChatMessage(); + message.append(content.toString()); + message.isRemote = true; + if (splitText.hasNext()) { + message.finish(); + } + messageList.add(message); + } + break; + } + message = messageList.get(previous); + message.unfinish(); + message.append(content.toString()); + content = message.content; } - messageList.add(message); } + if (text.endsWith(Constants.BUBBLE_BREAKER)) { + int lastIndexRemoteMessage = getLastIndexRemoteMessage(messageList); + messageList.get(lastIndexRemoteMessage).finish(); + } + } + + private static int getLastIndexUnfinishedRemoteMessage(List<RttChatMessage> messageList) { + int i = messageList.size() - 1; + while (i >= 0 && (!messageList.get(i).isRemote || messageList.get(i).isFinished)) { + i--; + } + return i; + } - return messageList; + private static int getLastIndexRemoteMessage(List<RttChatMessage> messageList) { + int i = messageList.size() - 1; + while (i >= 0 && !messageList.get(i).isRemote) { + i--; + } + return i; + } + + static int getLastIndexLocalMessage(List<RttChatMessage> messageList) { + int i = messageList.size() - 1; + while (i >= 0 && messageList.get(i).isRemote) { + i--; + } + return i; } @Override diff --git a/java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml b/java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml index c4d3a4290..d62cf1849 100644 --- a/java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml +++ b/java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml @@ -83,5 +83,15 @@ android:id="@id/rtt_on_hold_banner" android:layout_width="match_parent" android:layout_height="wrap_content"/> + <TextView + android:id="@+id/rtt_status_banner" + android:layout_width="match_parent" + android:layout_height="48dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:background="@android:color/white" + android:gravity="center_vertical" + android:textColor="#DD000000" + android:textSize="14sp"/> </LinearLayout>
\ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/values/strings.xml b/java/com/android/incallui/rtt/impl/res/values/strings.xml index dce16fdda..b0ac2057e 100644 --- a/java/com/android/incallui/rtt/impl/res/values/strings.xml +++ b/java/com/android/incallui/rtt/impl/res/values/strings.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<resources> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Content description for submit chat input button. [CHAR LIMIT=NONE] --> <string name="content_description_rtt_check_button">Go ahead</string> @@ -27,4 +27,7 @@ <!-- Text for back button. [CHAR LIMIT=20] --> <string name="rtt_button_back">Back</string> + <!-- Text for status banner. [CHAT LIMIT=100] --> + <string name="rtt_status_banner_text">Waiting for <xliff:g id="name">%s</xliff:g> to join RTT call…</string> + </resources>
\ No newline at end of file |