From fdbf2a0d7124af3e3026acbe39873bd2deea13ed Mon Sep 17 00:00:00 2001 From: wangqi Date: Mon, 8 Jan 2018 13:41:40 -0800 Subject: Add RTT call chat window. This change add a mock in simulator with a type bot that's simulating remote typing. The integration into incall UI will be in following changes. Bug: 67596257 Test: RttChatMessageTest PiperOrigin-RevId: 181211591 Change-Id: If6cdcb010afc0c25e90d3a44fe349920d5a856c6 --- .../res/drawable/quantum_ic_done_vd_theme_24.xml | 25 +++ .../drawable/quantum_ic_mic_off_vd_theme_24.xml | 25 +++ .../dialer/simulator/impl/SimulatorMainMenu.java | 6 + .../android/incallui/rtt/impl/AndroidManifest.xml | 26 +++ .../android/incallui/rtt/impl/RttChatActivity.java | 41 ++++ .../android/incallui/rtt/impl/RttChatAdapter.java | 218 +++++++++++++++++++++ .../android/incallui/rtt/impl/RttChatFragment.java | 176 +++++++++++++++++ .../android/incallui/rtt/impl/RttChatMessage.java | 132 +++++++++++++ .../rtt/impl/RttChatMessageViewHolder.java | 65 ++++++ .../rtt/impl/res/color/message_bubble_color.xml | 21 ++ .../res/color/submit_button_background_color.xml | 21 ++ .../rtt/impl/res/color/submit_button_color.xml | 21 ++ .../impl/res/drawable/input_bubble_background.xml | 74 +++++++ .../rtt/impl/res/drawable/message_bubble.xml | 21 ++ .../rtt/impl/res/drawable/pill_background.xml | 22 +++ .../incallui/rtt/impl/res/layout/activity_rtt.xml | 26 +++ .../incallui/rtt/impl/res/layout/frag_rtt_chat.xml | 69 +++++++ .../incallui/rtt/impl/res/layout/rtt_banner.xml | 91 +++++++++ .../rtt/impl/res/layout/rtt_chat_list_item.xml | 48 +++++ .../incallui/rtt/impl/res/values/colors.xml | 19 ++ .../incallui/rtt/impl/res/values/dimens.xml | 20 ++ .../incallui/rtt/impl/res/values/strings.xml | 29 +++ .../incallui/rtt/impl/res/values/styles.xml | 35 ++++ packages.mk | 1 + 24 files changed, 1232 insertions(+) create mode 100644 assets/quantum/res/drawable/quantum_ic_done_vd_theme_24.xml create mode 100644 assets/quantum/res/drawable/quantum_ic_mic_off_vd_theme_24.xml create mode 100644 java/com/android/incallui/rtt/impl/AndroidManifest.xml create mode 100644 java/com/android/incallui/rtt/impl/RttChatActivity.java create mode 100644 java/com/android/incallui/rtt/impl/RttChatAdapter.java create mode 100644 java/com/android/incallui/rtt/impl/RttChatFragment.java create mode 100644 java/com/android/incallui/rtt/impl/RttChatMessage.java create mode 100644 java/com/android/incallui/rtt/impl/RttChatMessageViewHolder.java create mode 100644 java/com/android/incallui/rtt/impl/res/color/message_bubble_color.xml create mode 100644 java/com/android/incallui/rtt/impl/res/color/submit_button_background_color.xml create mode 100644 java/com/android/incallui/rtt/impl/res/color/submit_button_color.xml create mode 100644 java/com/android/incallui/rtt/impl/res/drawable/input_bubble_background.xml create mode 100644 java/com/android/incallui/rtt/impl/res/drawable/message_bubble.xml create mode 100644 java/com/android/incallui/rtt/impl/res/drawable/pill_background.xml create mode 100644 java/com/android/incallui/rtt/impl/res/layout/activity_rtt.xml create mode 100644 java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml create mode 100644 java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml create mode 100644 java/com/android/incallui/rtt/impl/res/layout/rtt_chat_list_item.xml create mode 100644 java/com/android/incallui/rtt/impl/res/values/colors.xml create mode 100644 java/com/android/incallui/rtt/impl/res/values/dimens.xml create mode 100644 java/com/android/incallui/rtt/impl/res/values/strings.xml create mode 100644 java/com/android/incallui/rtt/impl/res/values/styles.xml diff --git a/assets/quantum/res/drawable/quantum_ic_done_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_done_vd_theme_24.xml new file mode 100644 index 000000000..bd1887e5c --- /dev/null +++ b/assets/quantum/res/drawable/quantum_ic_done_vd_theme_24.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/assets/quantum/res/drawable/quantum_ic_mic_off_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_mic_off_vd_theme_24.xml new file mode 100644 index 000000000..0519c555c --- /dev/null +++ b/assets/quantum/res/drawable/quantum_ic_mic_off_vd_theme_24.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java index c48e2836e..4ef579f06 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java +++ b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java @@ -31,6 +31,7 @@ import com.android.dialer.databasepopulator.VoicemailPopulator; import com.android.dialer.enrichedcall.simulator.EnrichedCallSimulatorActivity; import com.android.dialer.persistentlog.PersistentLogger; import com.android.dialer.preferredsim.PreferredSimFallbackContract; +import com.android.incallui.rtt.impl.RttChatActivity; /** Implements the top level simulator menu. */ final class SimulatorMainMenu { @@ -40,6 +41,7 @@ final class SimulatorMainMenu { .addItem("Voice call", SimulatorVoiceCall.getActionProvider(activity)) .addItem( "IMS video", SimulatorVideoCall.getActionProvider(activity.getApplicationContext())) + .addItem("Rtt call mock", () -> simulateRttCallMock(activity.getApplicationContext())) .addItem( "Notifications", SimulatorNotifications.getActionProvider(activity.getApplicationContext())) @@ -61,6 +63,10 @@ final class SimulatorMainMenu { EnrichedCallSimulatorActivity.newIntent(activity.getApplicationContext()))); } + private static void simulateRttCallMock(@NonNull Context context) { + context.startActivity(new Intent(context, RttChatActivity.class)); + } + private static void populateDatabase(@NonNull Context context) { DialerExecutorComponent.get(context) .dialerExecutorFactory() diff --git a/java/com/android/incallui/rtt/impl/AndroidManifest.xml b/java/com/android/incallui/rtt/impl/AndroidManifest.xml new file mode 100644 index 000000000..fc0705d7e --- /dev/null +++ b/java/com/android/incallui/rtt/impl/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/java/com/android/incallui/rtt/impl/RttChatActivity.java b/java/com/android/incallui/rtt/impl/RttChatActivity.java new file mode 100644 index 000000000..96056f746 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/RttChatActivity.java @@ -0,0 +1,41 @@ +/* + * 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.incallui.rtt.impl; + +import android.os.Bundle; +import android.os.SystemClock; +import android.support.annotation.Nullable; +import android.support.v4.app.FragmentActivity; +import android.view.View; + +/** Activity to for RTT chat window. */ +public class RttChatActivity extends FragmentActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_rtt); + getSupportFragmentManager() + .beginTransaction() + .add( + R.id.fragment_rtt, + RttChatFragment.newInstance("", "Jane Williamson", SystemClock.elapsedRealtime())) + .commit(); + getWindow().setStatusBarColor(getColor(R.color.rtt_status_bar_color)); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } +} diff --git a/java/com/android/incallui/rtt/impl/RttChatAdapter.java b/java/com/android/incallui/rtt/impl/RttChatAdapter.java new file mode 100644 index 000000000..1db4c6bad --- /dev/null +++ b/java/com/android/incallui/rtt/impl/RttChatAdapter.java @@ -0,0 +1,218 @@ +/* + * 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.incallui.rtt.impl; + +import android.content.Context; +import android.support.annotation.MainThread; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.ThreadUtil; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +/** Adapter class for holding RTT chat data. */ +public class RttChatAdapter extends RecyclerView.Adapter { + + interface MessageListener { + void newMessageAdded(); + } + + private final Context context; + private final List rttMessages = new ArrayList<>(); + private int lastIndexOfLocalMessage = -1; + private int lastIndexOfRemoteMessage = -1; + private final TypeBot typeBot; + private final MessageListener messageListener; + + RttChatAdapter(Context context, MessageListener listener) { + this.context = context; + this.messageListener = listener; + typeBot = new TypeBot(text -> ThreadUtil.postOnUiThread(() -> addRemoteMessage(text))); + } + + @Override + public RttChatMessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(context); + View view = layoutInflater.inflate(R.layout.rtt_chat_list_item, parent, false); + return new RttChatMessageViewHolder(view); + } + + @Override + public int getItemViewType(int position) { + return super.getItemViewType(position); + } + + @Override + public void onBindViewHolder(RttChatMessageViewHolder rttChatMessageViewHolder, int i) { + boolean isSameGroup = false; + if (i > 0) { + isSameGroup = rttMessages.get(i).isRemote == rttMessages.get(i - 1).isRemote; + } + rttChatMessageViewHolder.setMessage(rttMessages.get(i), isSameGroup); + } + + @Override + public int getItemCount() { + return rttMessages.size(); + } + + private void updateCurrentRemoteMessage(String newText) { + RttChatMessage rttChatMessage = null; + if (lastIndexOfRemoteMessage >= 0) { + rttChatMessage = rttMessages.get(lastIndexOfRemoteMessage); + } + RttChatMessage[] newMessages = RttChatMessage.getRemoteRttChatMessage(rttChatMessage, newText); + + if (rttChatMessage == null) { + lastIndexOfRemoteMessage = rttMessages.size(); + rttMessages.add(lastIndexOfRemoteMessage, newMessages[0]); + rttMessages.addAll(Arrays.asList(newMessages).subList(1, newMessages.length)); + notifyItemRangeInserted(lastIndexOfRemoteMessage, newMessages.length); + lastIndexOfRemoteMessage = rttMessages.size() - 1; + } else { + rttMessages.set(lastIndexOfRemoteMessage, newMessages[0]); + int lastIndex = rttMessages.size(); + rttMessages.addAll(Arrays.asList(newMessages).subList(1, newMessages.length)); + + notifyItemChanged(lastIndexOfRemoteMessage); + notifyItemRangeInserted(lastIndex, newMessages.length); + } + if (rttMessages.get(lastIndexOfRemoteMessage).isFinished()) { + lastIndexOfRemoteMessage = -1; + } + } + + private void updateCurrentLocalMessage(String newMessage) { + RttChatMessage rttChatMessage = null; + if (lastIndexOfLocalMessage >= 0) { + rttChatMessage = rttMessages.get(lastIndexOfLocalMessage); + } + if (rttChatMessage == null || rttChatMessage.isFinished()) { + rttChatMessage = new RttChatMessage(); + rttChatMessage.append(newMessage); + rttMessages.add(rttChatMessage); + lastIndexOfLocalMessage = rttMessages.size() - 1; + notifyItemInserted(lastIndexOfLocalMessage); + } else { + rttChatMessage.append(newMessage); + notifyItemChanged(lastIndexOfLocalMessage); + } + } + + void addLocalMessage(String message) { + LogUtil.enterBlock("RttChatAdapater.addLocalMessage"); + updateCurrentLocalMessage(message); + if (messageListener != null) { + messageListener.newMessageAdded(); + } + } + + void submitLocalMessage() { + LogUtil.enterBlock("RttChatAdapater.submitLocalMessage"); + rttMessages.get(lastIndexOfLocalMessage).finish(); + notifyItemChanged(lastIndexOfLocalMessage); + lastIndexOfLocalMessage = -1; + startChatBot(); + } + + void addRemoteMessage(String message) { + LogUtil.enterBlock("RttChatAdapater.addRemoteMessage"); + if (TextUtils.isEmpty(message)) { + return; + } + updateCurrentRemoteMessage(message); + if (messageListener != null) { + messageListener.newMessageAdded(); + } + } + + private void startChatBot() { + typeBot.scheduleMessage(); + } + + // TODO(wangqi): Move this out of this class once a bug is fixed. + private static class TypeBot { + interface Callback { + void type(String text); + } + + private static final String[] CANDIDATE_MESSAGES = + new String[] { + "To RTT or not to RTT, that is the question...", + "Making TTY great again!", + "I would be more comfortable with real \"Thyme\" chatting." + + " I don't know how to end this pun", + "お疲れ様でした", + "The FCC has mandated that I respond... I will do so begrudgingly", + "😂😂😂💯" + }; + private final Random random = new Random(); + private final Callback callback; + private final List messageQueue = new ArrayList<>(); + private int currentTypingPosition = -1; + private String currentTypingMessage = null; + + TypeBot(Callback callback) { + this.callback = callback; + } + + @MainThread + public void scheduleMessage() { + Assert.isMainThread(); + if (random.nextDouble() < 0.5) { + return; + } + + String text = CANDIDATE_MESSAGES[random.nextInt(CANDIDATE_MESSAGES.length)]; + messageQueue.add(text); + typeMessage(); + } + + @MainThread + private void typeMessage() { + Assert.isMainThread(); + if (currentTypingPosition < 0 || currentTypingMessage == null) { + if (messageQueue.size() <= 0) { + return; + } + currentTypingMessage = messageQueue.remove(0); + currentTypingPosition = 0; + } + if (currentTypingPosition < currentTypingMessage.length()) { + int size = random.nextInt(currentTypingMessage.length() - currentTypingPosition + 1); + callback.type( + currentTypingMessage.substring(currentTypingPosition, currentTypingPosition + size)); + currentTypingPosition = currentTypingPosition + size; + // Wait up to 2s between typing. + ThreadUtil.postDelayedOnUiThread(this::typeMessage, 200 * random.nextInt(10)); + } else { + callback.type(RttChatMessage.BUBBLE_BREAKER); + currentTypingPosition = -1; + currentTypingMessage = null; + // Wait 1-11s between two messages. + ThreadUtil.postDelayedOnUiThread(this::typeMessage, 1000 * (1 + random.nextInt(10))); + } + } + } +} diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java new file mode 100644 index 000000000..0b0ad2a8e --- /dev/null +++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java @@ -0,0 +1,176 @@ +/* + * 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.incallui.rtt.impl; + +import android.os.Bundle; +import android.os.SystemClock; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.OnScrollListener; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Chronometer; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; +import com.android.incallui.rtt.impl.RttChatAdapter.MessageListener; + +/** RTT chat fragment to show chat bubbles. */ +public class RttChatFragment extends Fragment + implements OnClickListener, OnEditorActionListener, TextWatcher, MessageListener { + + private static final String ARG_CALL_ID = "call_id"; + private static final String ARG_NAME_OR_NUMBER = "name_or_number"; + private static final String ARG_SESSION_START_TIME = "session_start_time"; + + private RecyclerView recyclerView; + private RttChatAdapter adapter; + private EditText editText; + private ImageButton submitButton; + private boolean isClearingInput; + + private final OnScrollListener onScrollListener = + new OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (dy < 0) { + hideKeyboard(); + } + } + }; + + /** + * Create a new instance of RttChatFragment. + * + * @param callId call id of the RTT call. + * @param nameOrNumber name or number of the caller to be displayed + * @param sessionStartTimeMillis start time of RTT session in terms of {@link + * SystemClock#elapsedRealtime}. + * @return new RttChatFragment + */ + public static RttChatFragment newInstance( + String callId, String nameOrNumber, long sessionStartTimeMillis) { + Bundle bundle = new Bundle(); + bundle.putString(ARG_CALL_ID, callId); + bundle.putString(ARG_NAME_OR_NUMBER, nameOrNumber); + bundle.putLong(ARG_SESSION_START_TIME, sessionStartTimeMillis); + RttChatFragment instance = new RttChatFragment(); + instance.setArguments(bundle); + return instance; + } + + @Nullable + @Override + public View onCreateView( + LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.frag_rtt_chat, container, false); + editText = view.findViewById(R.id.rtt_chat_input); + editText.setOnEditorActionListener(this); + editText.addTextChangedListener(this); + recyclerView = view.findViewById(R.id.rtt_recycler_view); + LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); + layoutManager.setStackFromEnd(true); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setHasFixedSize(false); + adapter = new RttChatAdapter(getContext(), this); + recyclerView.setAdapter(adapter); + recyclerView.addOnScrollListener(onScrollListener); + submitButton = view.findViewById(R.id.rtt_chat_submit_button); + submitButton.setOnClickListener(this); + submitButton.setEnabled(false); + + String nameOrNumber = null; + Bundle bundle = getArguments(); + if (bundle != null) { + nameOrNumber = bundle.getString(ARG_NAME_OR_NUMBER, getString(R.string.unknown)); + } + TextView nameTextView = view.findViewById(R.id.rtt_name_or_number); + nameTextView.setText(nameOrNumber); + + long sessionStartTime = SystemClock.elapsedRealtime(); + if (bundle != null) { + sessionStartTime = bundle.getLong(ARG_SESSION_START_TIME, sessionStartTime); + } + Chronometer chronometer = view.findViewById(R.id.rtt_timer); + chronometer.setBase(sessionStartTime); + chronometer.start(); + return view; + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.rtt_chat_submit_button) { + adapter.submitLocalMessage(); + isClearingInput = true; + editText.setText(""); + isClearingInput = false; + } + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + submitButton.performClick(); + return true; + } + return false; + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (isClearingInput) { + return; + } + adapter.addLocalMessage(RttChatMessage.getChangedString(s, start, before, count)); + } + + @Override + public void afterTextChanged(Editable s) { + if (TextUtils.isEmpty(s)) { + submitButton.setEnabled(false); + } else { + submitButton.setEnabled(true); + } + } + + @Override + public void newMessageAdded() { + recyclerView.smoothScrollToPosition(adapter.getItemCount()); + } + + private void hideKeyboard() { + InputMethodManager inputMethodManager = getContext().getSystemService(InputMethodManager.class); + if (inputMethodManager.isAcceptingText()) { + inputMethodManager.hideSoftInputFromWindow( + getActivity().getCurrentFocus().getWindowToken(), 0); + } + } +} diff --git a/java/com/android/incallui/rtt/impl/RttChatMessage.java b/java/com/android/incallui/rtt/impl/RttChatMessage.java new file mode 100644 index 000000000..85b045183 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/RttChatMessage.java @@ -0,0 +1,132 @@ +/* + * 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.incallui.rtt.impl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextWatcher; +import com.google.common.base.Splitter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** Message class that holds one RTT chat content. */ +final class RttChatMessage { + + static final String BUBBLE_BREAKER = "\n\n"; + private static final Splitter SPLITTER = Splitter.on(BUBBLE_BREAKER); + + boolean isRemote; + public boolean hasAvatar; + private final StringBuilder content = new StringBuilder(); + private boolean isFinished; + + public boolean isFinished() { + return isFinished; + } + + public void finish() { + isFinished = true; + } + + 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) { + content.deleteCharAt(content.length() - 1); + } + } + } + + public String getContent() { + return content.toString(); + } + + /** + * Generates delta change to a text. + * + *

This is used to track text change of input. See more details in {@link + * TextWatcher#onTextChanged} + * + *

e.g. "hello world" -> "hello" : "\b\b\b\b\b\b" + * + *

"hello world" -> "hello mom!" : "\b\b\b\b\bmom!" + * + *

"hello world" -> "hello d" : "\b\b\b\b\bd" + * + *

"hello world" -> "hello new world" : "\b\b\b\b\bnew world" + */ + static String getChangedString(CharSequence s, int start, int before, int count) { + StringBuilder modify = new StringBuilder(); + if (before > count) { + int deleteStart = start + count; + int deleted = before - count; + int numberUnModifiedCharsAfterDeleted = s.length() - start - count; + char c = '\b'; + for (int i = 0; i < deleted + numberUnModifiedCharsAfterDeleted; i++) { + modify.append(c); + } + modify.append(s, deleteStart, s.length()); + } else { + int insertStart = start + before; + int numberUnModifiedCharsAfterInserted = s.length() - start - count; + char c = '\b'; + for (int i = 0; i < numberUnModifiedCharsAfterInserted; i++) { + modify.append(c); + } + modify.append(s, insertStart, s.length()); + } + return modify.toString(); + } + + /** Convert remote input text into an array of {@code RttChatMessage}. */ + static RttChatMessage[] getRemoteRttChatMessage( + @Nullable RttChatMessage currentMessage, @NonNull String text) { + Iterator splitText = SPLITTER.split(text).iterator(); + List 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(BUBBLE_BREAKER)) { + firstMessage.finish(); + } + messageList.add(firstMessage); + + while (splitText.hasNext()) { + String singleMessageContent = splitText.next(); + if (singleMessageContent.isEmpty()) { + continue; + } + RttChatMessage message = new RttChatMessage(); + message.append(singleMessageContent); + message.isRemote = true; + if (splitText.hasNext()) { + message.finish(); + } + messageList.add(message); + } + + return messageList.toArray(new RttChatMessage[0]); + } +} diff --git a/java/com/android/incallui/rtt/impl/RttChatMessageViewHolder.java b/java/com/android/incallui/rtt/impl/RttChatMessageViewHolder.java new file mode 100644 index 000000000..c88786aa4 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/RttChatMessageViewHolder.java @@ -0,0 +1,65 @@ +/* + * 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.incallui.rtt.impl; + +import android.content.res.Resources; +import android.support.v7.widget.RecyclerView.ViewHolder; +import android.view.Gravity; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.TextView; + +/** ViewHolder class for RTT chat message bubble. */ +public class RttChatMessageViewHolder extends ViewHolder { + + private final TextView messageTextView; + private final Resources resources; + private final ImageView avatarImageView; + private final View container; + + RttChatMessageViewHolder(View view) { + super(view); + container = view.findViewById(R.id.rtt_chat_message_container); + messageTextView = view.findViewById(R.id.rtt_chat_message); + avatarImageView = view.findViewById(R.id.rtt_chat_avatar); + resources = view.getResources(); + } + + void setMessage(RttChatMessage message, boolean isSameGroup) { + messageTextView.setText(message.getContent()); + LinearLayout.LayoutParams params = (LayoutParams) container.getLayoutParams(); + params.gravity = message.isRemote ? Gravity.START : Gravity.END; + params.topMargin = + isSameGroup + ? resources.getDimensionPixelSize(R.dimen.rtt_same_group_message_margin_top) + : resources.getDimensionPixelSize(R.dimen.rtt_message_margin_top); + container.setLayoutParams(params); + messageTextView.setEnabled(message.isRemote); + if (message.isRemote) { + if (isSameGroup) { + avatarImageView.setVisibility(View.INVISIBLE); + } else { + avatarImageView.setVisibility(View.VISIBLE); + avatarImageView.setImageResource(R.drawable.product_logo_avatar_anonymous_white_color_120); + } + } else { + avatarImageView.setVisibility(View.GONE); + } + } +} diff --git a/java/com/android/incallui/rtt/impl/res/color/message_bubble_color.xml b/java/com/android/incallui/rtt/impl/res/color/message_bubble_color.xml new file mode 100644 index 000000000..b3729ee20 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/color/message_bubble_color.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/color/submit_button_background_color.xml b/java/com/android/incallui/rtt/impl/res/color/submit_button_background_color.xml new file mode 100644 index 000000000..0da2c374a --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/color/submit_button_background_color.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/color/submit_button_color.xml b/java/com/android/incallui/rtt/impl/res/color/submit_button_color.xml new file mode 100644 index 000000000..2fe748f77 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/color/submit_button_color.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/drawable/input_bubble_background.xml b/java/com/android/incallui/rtt/impl/res/drawable/input_bubble_background.xml new file mode 100644 index 000000000..ae372332e --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/drawable/input_bubble_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/drawable/message_bubble.xml b/java/com/android/incallui/rtt/impl/res/drawable/message_bubble.xml new file mode 100644 index 000000000..2b01f62f9 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/drawable/message_bubble.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/drawable/pill_background.xml b/java/com/android/incallui/rtt/impl/res/drawable/pill_background.xml new file mode 100644 index 000000000..cfad8df57 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/drawable/pill_background.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/layout/activity_rtt.xml b/java/com/android/incallui/rtt/impl/res/layout/activity_rtt.xml new file mode 100644 index 000000000..b48e8d43f --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/layout/activity_rtt.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml b/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml new file mode 100644 index 000000000..7ba6a09e3 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + \ No newline at end of file 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 new file mode 100644 index 000000000..7d9cd6fc8 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/layout/rtt_chat_list_item.xml b/java/com/android/incallui/rtt/impl/res/layout/rtt_chat_list_item.xml new file mode 100644 index 000000000..54b0f4f6a --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/layout/rtt_chat_list_item.xml @@ -0,0 +1,48 @@ + + + + + + + + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/values/colors.xml b/java/com/android/incallui/rtt/impl/res/values/colors.xml new file mode 100644 index 000000000..402cac4a0 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/values/colors.xml @@ -0,0 +1,19 @@ + + + + #E0E0E0 + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/values/dimens.xml b/java/com/android/incallui/rtt/impl/res/values/dimens.xml new file mode 100644 index 000000000..a3f230c08 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/values/dimens.xml @@ -0,0 +1,20 @@ + + + + 16dp + 2dp + \ 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 new file mode 100644 index 000000000..79377acda --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/values/strings.xml @@ -0,0 +1,29 @@ + + + + + Go ahead + + + Close RTT chat + + + Type a message + + + Avatar + \ No newline at end of file diff --git a/java/com/android/incallui/rtt/impl/res/values/styles.xml b/java/com/android/incallui/rtt/impl/res/values/styles.xml new file mode 100644 index 000000000..b4bb91474 --- /dev/null +++ b/java/com/android/incallui/rtt/impl/res/values/styles.xml @@ -0,0 +1,35 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages.mk b/packages.mk index 03268fd57..d01b3f15a 100644 --- a/packages.mk +++ b/packages.mk @@ -75,6 +75,7 @@ LOCAL_AAPT_FLAGS := \ com.android.incallui.hold \ com.android.incallui.incall.impl \ com.android.incallui.maps.impl \ + com.android.incallui.rtt.impl \ com.android.incallui.sessiondata \ com.android.incallui.spam \ com.android.incallui.speakerbuttonlogic \ -- cgit v1.2.3