diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2018-03-27 06:37:08 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-03-27 06:37:08 +0000 |
commit | bc62b7254e513fb6bb190d8838be09780bf89714 (patch) | |
tree | de75b03528aae36b06fc402fa39d081250671048 /java | |
parent | 966800d977660c8ce78d988af5284647d02d6555 (diff) | |
parent | f4b484485a4519a99d797bd9c0c1cc902cfc7414 (diff) |
Merge changes Ic7cb3be7,I412f8fbf,Ifebcbe87
* changes:
Correctly display phone numbers containing whitespaces in RTL context.
Add waiting for join banner to RTT outgoing call.
Allow user to delete previous bubbles.
Diffstat (limited to 'java')
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 |