• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.incallui.rtt.impl;
18 
19 import android.app.Activity;
20 import android.os.Bundle;
21 import android.os.SystemClock;
22 import android.support.annotation.NonNull;
23 import android.support.annotation.Nullable;
24 import android.support.v4.app.Fragment;
25 import android.support.v7.widget.LinearLayoutManager;
26 import android.support.v7.widget.RecyclerView;
27 import android.support.v7.widget.RecyclerView.OnScrollListener;
28 import android.telecom.CallAudioState;
29 import android.text.Editable;
30 import android.text.TextUtils;
31 import android.text.TextWatcher;
32 import android.view.Gravity;
33 import android.view.KeyEvent;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.view.Window;
38 import android.view.accessibility.AccessibilityEvent;
39 import android.view.inputmethod.EditorInfo;
40 import android.view.inputmethod.InputMethodManager;
41 import android.widget.Chronometer;
42 import android.widget.EditText;
43 import android.widget.ImageButton;
44 import android.widget.TextView;
45 import android.widget.TextView.OnEditorActionListener;
46 import com.android.dialer.common.Assert;
47 import com.android.dialer.common.FragmentUtils;
48 import com.android.dialer.common.LogUtil;
49 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment;
50 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter;
51 import com.android.incallui.call.DialerCall.State;
52 import com.android.incallui.incall.protocol.InCallButtonUi;
53 import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
54 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
55 import com.android.incallui.incall.protocol.InCallScreen;
56 import com.android.incallui.incall.protocol.InCallScreenDelegate;
57 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
58 import com.android.incallui.incall.protocol.PrimaryCallState;
59 import com.android.incallui.incall.protocol.PrimaryInfo;
60 import com.android.incallui.incall.protocol.SecondaryInfo;
61 import com.android.incallui.rtt.impl.RttChatAdapter.MessageListener;
62 import com.android.incallui.rtt.protocol.Constants;
63 import com.android.incallui.rtt.protocol.RttCallScreen;
64 import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
65 import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory;
66 
67 /** RTT chat fragment to show chat bubbles. */
68 public class RttChatFragment extends Fragment
69     implements OnEditorActionListener,
70         TextWatcher,
71         MessageListener,
72         RttCallScreen,
73         InCallScreen,
74         InCallButtonUi,
75         AudioRouteSelectorPresenter {
76 
77   private static final String ARG_CALL_ID = "call_id";
78 
79   private RecyclerView recyclerView;
80   private RttChatAdapter adapter;
81   private EditText editText;
82   private ImageButton submitButton;
83   private boolean isClearingInput;
84 
85   private final OnScrollListener onScrollListener =
86       new OnScrollListener() {
87         @Override
88         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
89           if (dy < 0) {
90             hideKeyboard();
91           }
92         }
93       };
94   private InCallScreenDelegate inCallScreenDelegate;
95   private RttCallScreenDelegate rttCallScreenDelegate;
96   private InCallButtonUiDelegate inCallButtonUiDelegate;
97   private View endCallButton;
98   private TextView nameTextView;
99   private Chronometer chronometer;
100   private boolean isTimerStarted;
101   private RttOverflowMenu overflowMenu;
102 
103   /**
104    * Create a new instance of RttChatFragment.
105    *
106    * @param callId call id of the RTT call.
107    * @return new RttChatFragment
108    */
newInstance(String callId)109   public static RttChatFragment newInstance(String callId) {
110     Bundle bundle = new Bundle();
111     bundle.putString(ARG_CALL_ID, callId);
112     RttChatFragment instance = new RttChatFragment();
113     instance.setArguments(bundle);
114     return instance;
115   }
116 
117   @Override
onCreate(@ullable Bundle savedInstanceState)118   public void onCreate(@Nullable Bundle savedInstanceState) {
119     super.onCreate(savedInstanceState);
120     LogUtil.i("RttChatFragment.onCreate", null);
121     inCallButtonUiDelegate =
122         FragmentUtils.getParent(this, InCallButtonUiDelegateFactory.class)
123             .newInCallButtonUiDelegate();
124     if (savedInstanceState != null) {
125       inCallButtonUiDelegate.onRestoreInstanceState(savedInstanceState);
126     }
127   }
128 
129   @Override
onViewCreated(@onNull View view, @Nullable Bundle bundle)130   public void onViewCreated(@NonNull View view, @Nullable Bundle bundle) {
131     super.onViewCreated(view, bundle);
132     LogUtil.i("RttChatFragment.onViewCreated", null);
133 
134     inCallScreenDelegate =
135         FragmentUtils.getParentUnsafe(this, InCallScreenDelegateFactory.class)
136             .newInCallScreenDelegate();
137     rttCallScreenDelegate =
138         FragmentUtils.getParentUnsafe(this, RttCallScreenDelegateFactory.class)
139             .newRttCallScreenDelegate(this);
140 
141     rttCallScreenDelegate.initRttCallScreenDelegate(this);
142 
143     inCallScreenDelegate.onInCallScreenDelegateInit(this);
144     inCallScreenDelegate.onInCallScreenReady();
145     inCallButtonUiDelegate.onInCallButtonUiReady(this);
146   }
147 
148   @Nullable
149   @Override
onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)150   public View onCreateView(
151       LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
152     View view = inflater.inflate(R.layout.frag_rtt_chat, container, false);
153     editText = view.findViewById(R.id.rtt_chat_input);
154     editText.setOnEditorActionListener(this);
155     editText.addTextChangedListener(this);
156     recyclerView = view.findViewById(R.id.rtt_recycler_view);
157     LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
158     layoutManager.setStackFromEnd(true);
159     recyclerView.setLayoutManager(layoutManager);
160     recyclerView.setHasFixedSize(false);
161     adapter = new RttChatAdapter(getContext(), this);
162     recyclerView.setAdapter(adapter);
163     recyclerView.addOnScrollListener(onScrollListener);
164     submitButton = view.findViewById(R.id.rtt_chat_submit_button);
165     submitButton.setOnClickListener(
166         v -> {
167           adapter.submitLocalMessage();
168           isClearingInput = true;
169           editText.setText("");
170           isClearingInput = false;
171           rttCallScreenDelegate.onLocalMessage(Constants.BUBBLE_BREAKER);
172         });
173     submitButton.setEnabled(false);
174     endCallButton = view.findViewById(R.id.rtt_end_call_button);
175     endCallButton.setOnClickListener(
176         v -> {
177           LogUtil.i("RttChatFragment.onClick", "end call button clicked");
178           inCallButtonUiDelegate.onEndCallClicked();
179         });
180 
181     overflowMenu = new RttOverflowMenu(getContext(), inCallButtonUiDelegate);
182     view.findViewById(R.id.rtt_overflow_button)
183         .setOnClickListener(v -> overflowMenu.showAtLocation(v, Gravity.TOP | Gravity.RIGHT, 0, 0));
184 
185     nameTextView = view.findViewById(R.id.rtt_name_or_number);
186     chronometer = view.findViewById(R.id.rtt_timer);
187     return view;
188   }
189 
190   @Override
onEditorAction(TextView v, int actionId, KeyEvent event)191   public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
192     if (actionId == EditorInfo.IME_ACTION_DONE) {
193       submitButton.performClick();
194       return true;
195     }
196     return false;
197   }
198 
199   @Override
beforeTextChanged(CharSequence s, int start, int count, int after)200   public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
201 
202   @Override
onTextChanged(CharSequence s, int start, int before, int count)203   public void onTextChanged(CharSequence s, int start, int before, int count) {
204     if (isClearingInput) {
205       return;
206     }
207     String messageToAppend = RttChatMessage.getChangedString(s, start, before, count);
208     if (!TextUtils.isEmpty(messageToAppend)) {
209       adapter.addLocalMessage(messageToAppend);
210       rttCallScreenDelegate.onLocalMessage(messageToAppend);
211     }
212   }
213 
214   @Override
onRemoteMessage(String message)215   public void onRemoteMessage(String message) {
216     adapter.addRemoteMessage(message);
217   }
218 
219   @Override
onDestroyView()220   public void onDestroyView() {
221     super.onDestroyView();
222     LogUtil.enterBlock("RttChatFragment.onDestroyView");
223     inCallButtonUiDelegate.onInCallButtonUiUnready();
224     inCallScreenDelegate.onInCallScreenUnready();
225   }
226 
227   @Override
afterTextChanged(Editable s)228   public void afterTextChanged(Editable s) {
229     if (TextUtils.isEmpty(s)) {
230       submitButton.setEnabled(false);
231     } else {
232       submitButton.setEnabled(true);
233     }
234   }
235 
236   @Override
newMessageAdded()237   public void newMessageAdded() {
238     recyclerView.smoothScrollToPosition(adapter.getItemCount());
239   }
240 
241   @Override
onStart()242   public void onStart() {
243     LogUtil.enterBlock("RttChatFragment.onStart");
244     super.onStart();
245     onRttScreenStart();
246   }
247 
248   @Override
onStop()249   public void onStop() {
250     LogUtil.enterBlock("RttChatFragment.onStop");
251     super.onStop();
252     if (overflowMenu.isShowing()) {
253       overflowMenu.dismiss();
254     }
255     onRttScreenStop();
256   }
257 
hideKeyboard()258   private void hideKeyboard() {
259     InputMethodManager inputMethodManager = getContext().getSystemService(InputMethodManager.class);
260     if (inputMethodManager.isAcceptingText()) {
261       inputMethodManager.hideSoftInputFromWindow(
262           getActivity().getCurrentFocus().getWindowToken(), 0);
263     }
264   }
265 
266   @Override
onRttScreenStart()267   public void onRttScreenStart() {
268     rttCallScreenDelegate.onRttCallScreenUiReady();
269     Activity activity = getActivity();
270     Window window = getActivity().getWindow();
271     window.setStatusBarColor(activity.getColor(R.color.rtt_status_bar_color));
272     window.setNavigationBarColor(activity.getColor(R.color.rtt_navigation_bar_color));
273     window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
274   }
275 
276   @Override
onRttScreenStop()277   public void onRttScreenStop() {
278     rttCallScreenDelegate.onRttCallScreenUiUnready();
279   }
280 
281   @Override
getRttCallScreenFragment()282   public Fragment getRttCallScreenFragment() {
283     return this;
284   }
285 
286   @Override
getCallId()287   public String getCallId() {
288     return Assert.isNotNull(getArguments().getString(ARG_CALL_ID));
289   }
290 
291   @Override
setPrimary(@onNull PrimaryInfo primaryInfo)292   public void setPrimary(@NonNull PrimaryInfo primaryInfo) {
293     LogUtil.i("RttChatFragment.setPrimary", primaryInfo.toString());
294     nameTextView.setText(primaryInfo.name());
295   }
296 
297   @Override
setSecondary(@onNull SecondaryInfo secondaryInfo)298   public void setSecondary(@NonNull SecondaryInfo secondaryInfo) {}
299 
300   @Override
setCallState(@onNull PrimaryCallState primaryCallState)301   public void setCallState(@NonNull PrimaryCallState primaryCallState) {
302     LogUtil.i("RttChatFragment.setCallState", primaryCallState.toString());
303     if (!isTimerStarted && primaryCallState.state() == State.ACTIVE) {
304       LogUtil.i(
305           "RttChatFragment.setCallState", "starting timer with base: %d", chronometer.getBase());
306       chronometer.setBase(
307           primaryCallState.connectTimeMillis()
308               - System.currentTimeMillis()
309               + SystemClock.elapsedRealtime());
310       chronometer.start();
311       isTimerStarted = true;
312     }
313   }
314 
315   @Override
setEndCallButtonEnabled(boolean enabled, boolean animate)316   public void setEndCallButtonEnabled(boolean enabled, boolean animate) {}
317 
318   @Override
showManageConferenceCallButton(boolean visible)319   public void showManageConferenceCallButton(boolean visible) {}
320 
321   @Override
isManageConferenceVisible()322   public boolean isManageConferenceVisible() {
323     return false;
324   }
325 
326   @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)327   public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {}
328 
329   @Override
showNoteSentToast()330   public void showNoteSentToast() {}
331 
332   @Override
updateInCallScreenColors()333   public void updateInCallScreenColors() {}
334 
335   @Override
onInCallScreenDialpadVisibilityChange(boolean isShowing)336   public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {}
337 
338   @Override
getAnswerAndDialpadContainerResourceId()339   public int getAnswerAndDialpadContainerResourceId() {
340     return 0;
341   }
342 
343   @Override
showLocationUi(Fragment locationUi)344   public void showLocationUi(Fragment locationUi) {}
345 
346   @Override
isShowingLocationUi()347   public boolean isShowingLocationUi() {
348     return false;
349   }
350 
351   @Override
getInCallScreenFragment()352   public Fragment getInCallScreenFragment() {
353     return this;
354   }
355 
356   @Override
showButton(int buttonId, boolean show)357   public void showButton(int buttonId, boolean show) {}
358 
359   @Override
enableButton(int buttonId, boolean enable)360   public void enableButton(int buttonId, boolean enable) {}
361 
362   @Override
setEnabled(boolean on)363   public void setEnabled(boolean on) {}
364 
365   @Override
setHold(boolean on)366   public void setHold(boolean on) {}
367 
368   @Override
setCameraSwitched(boolean isBackFacingCamera)369   public void setCameraSwitched(boolean isBackFacingCamera) {}
370 
371   @Override
setVideoPaused(boolean isPaused)372   public void setVideoPaused(boolean isPaused) {}
373 
374   @Override
setAudioState(CallAudioState audioState)375   public void setAudioState(CallAudioState audioState) {
376     LogUtil.i("RttChatFragment.setAudioState", "audioState: " + audioState);
377     overflowMenu.setMuteButtonChecked(audioState.isMuted());
378     overflowMenu.setAudioState(audioState);
379   }
380 
381   @Override
updateButtonStates()382   public void updateButtonStates() {}
383 
384   @Override
updateInCallButtonUiColors(int color)385   public void updateInCallButtonUiColors(int color) {}
386 
387   @Override
getInCallButtonUiFragment()388   public Fragment getInCallButtonUiFragment() {
389     return this;
390   }
391 
392   @Override
showAudioRouteSelector()393   public void showAudioRouteSelector() {
394     AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState())
395         .show(getChildFragmentManager(), null);
396   }
397 
398   @Override
onAudioRouteSelected(int audioRoute)399   public void onAudioRouteSelected(int audioRoute) {
400     inCallButtonUiDelegate.setAudioRoute(audioRoute);
401   }
402 
403   @Override
onAudioRouteSelectorDismiss()404   public void onAudioRouteSelectorDismiss() {}
405 }
406