Home | History | Annotate | Download | only in impl
      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    */
    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
    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
    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
    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
    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
    200   public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
    201 
    202   @Override
    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
    215   public void onRemoteMessage(String message) {
    216     adapter.addRemoteMessage(message);
    217   }
    218 
    219   @Override
    220   public void onDestroyView() {
    221     super.onDestroyView();
    222     LogUtil.enterBlock("RttChatFragment.onDestroyView");
    223     inCallButtonUiDelegate.onInCallButtonUiUnready();
    224     inCallScreenDelegate.onInCallScreenUnready();
    225   }
    226 
    227   @Override
    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
    237   public void newMessageAdded() {
    238     recyclerView.smoothScrollToPosition(adapter.getItemCount());
    239   }
    240 
    241   @Override
    242   public void onStart() {
    243     LogUtil.enterBlock("RttChatFragment.onStart");
    244     super.onStart();
    245     onRttScreenStart();
    246   }
    247 
    248   @Override
    249   public void onStop() {
    250     LogUtil.enterBlock("RttChatFragment.onStop");
    251     super.onStop();
    252     if (overflowMenu.isShowing()) {
    253       overflowMenu.dismiss();
    254     }
    255     onRttScreenStop();
    256   }
    257 
    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
    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
    277   public void onRttScreenStop() {
    278     rttCallScreenDelegate.onRttCallScreenUiUnready();
    279   }
    280 
    281   @Override
    282   public Fragment getRttCallScreenFragment() {
    283     return this;
    284   }
    285 
    286   @Override
    287   public String getCallId() {
    288     return Assert.isNotNull(getArguments().getString(ARG_CALL_ID));
    289   }
    290 
    291   @Override
    292   public void setPrimary(@NonNull PrimaryInfo primaryInfo) {
    293     LogUtil.i("RttChatFragment.setPrimary", primaryInfo.toString());
    294     nameTextView.setText(primaryInfo.name());
    295   }
    296 
    297   @Override
    298   public void setSecondary(@NonNull SecondaryInfo secondaryInfo) {}
    299 
    300   @Override
    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
    316   public void setEndCallButtonEnabled(boolean enabled, boolean animate) {}
    317 
    318   @Override
    319   public void showManageConferenceCallButton(boolean visible) {}
    320 
    321   @Override
    322   public boolean isManageConferenceVisible() {
    323     return false;
    324   }
    325 
    326   @Override
    327   public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {}
    328 
    329   @Override
    330   public void showNoteSentToast() {}
    331 
    332   @Override
    333   public void updateInCallScreenColors() {}
    334 
    335   @Override
    336   public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {}
    337 
    338   @Override
    339   public int getAnswerAndDialpadContainerResourceId() {
    340     return 0;
    341   }
    342 
    343   @Override
    344   public void showLocationUi(Fragment locationUi) {}
    345 
    346   @Override
    347   public boolean isShowingLocationUi() {
    348     return false;
    349   }
    350 
    351   @Override
    352   public Fragment getInCallScreenFragment() {
    353     return this;
    354   }
    355 
    356   @Override
    357   public void showButton(int buttonId, boolean show) {}
    358 
    359   @Override
    360   public void enableButton(int buttonId, boolean enable) {}
    361 
    362   @Override
    363   public void setEnabled(boolean on) {}
    364 
    365   @Override
    366   public void setHold(boolean on) {}
    367 
    368   @Override
    369   public void setCameraSwitched(boolean isBackFacingCamera) {}
    370 
    371   @Override
    372   public void setVideoPaused(boolean isPaused) {}
    373 
    374   @Override
    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
    382   public void updateButtonStates() {}
    383 
    384   @Override
    385   public void updateInCallButtonUiColors(int color) {}
    386 
    387   @Override
    388   public Fragment getInCallButtonUiFragment() {
    389     return this;
    390   }
    391 
    392   @Override
    393   public void showAudioRouteSelector() {
    394     AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState())
    395         .show(getChildFragmentManager(), null);
    396   }
    397 
    398   @Override
    399   public void onAudioRouteSelected(int audioRoute) {
    400     inCallButtonUiDelegate.setAudioRoute(audioRoute);
    401   }
    402 
    403   @Override
    404   public void onAudioRouteSelectorDismiss() {}
    405 }
    406