/* * 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.app.Activity; import android.os.Bundle; import android.os.SystemClock; import android.support.annotation.NonNull; 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.telecom.CallAudioState; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.accessibility.AccessibilityEvent; 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.dialer.common.Assert; import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment; import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter; import com.android.incallui.call.DialerCall.State; import com.android.incallui.incall.protocol.InCallButtonUi; import com.android.incallui.incall.protocol.InCallButtonUiDelegate; import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory; import com.android.incallui.incall.protocol.InCallScreen; import com.android.incallui.incall.protocol.InCallScreenDelegate; import com.android.incallui.incall.protocol.InCallScreenDelegateFactory; import com.android.incallui.incall.protocol.PrimaryCallState; import com.android.incallui.incall.protocol.PrimaryInfo; import com.android.incallui.incall.protocol.SecondaryInfo; import com.android.incallui.rtt.impl.RttChatAdapter.MessageListener; import com.android.incallui.rtt.protocol.Constants; import com.android.incallui.rtt.protocol.RttCallScreen; import com.android.incallui.rtt.protocol.RttCallScreenDelegate; import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory; /** RTT chat fragment to show chat bubbles. */ public class RttChatFragment extends Fragment implements OnEditorActionListener, TextWatcher, MessageListener, RttCallScreen, InCallScreen, InCallButtonUi, AudioRouteSelectorPresenter { private static final String ARG_CALL_ID = "call_id"; 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(); } } }; private InCallScreenDelegate inCallScreenDelegate; private RttCallScreenDelegate rttCallScreenDelegate; private InCallButtonUiDelegate inCallButtonUiDelegate; private View endCallButton; private TextView nameTextView; private Chronometer chronometer; private boolean isTimerStarted; private RttOverflowMenu overflowMenu; /** * Create a new instance of RttChatFragment. * * @param callId call id of the RTT call. * @return new RttChatFragment */ public static RttChatFragment newInstance(String callId) { Bundle bundle = new Bundle(); bundle.putString(ARG_CALL_ID, callId); RttChatFragment instance = new RttChatFragment(); instance.setArguments(bundle); return instance; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); LogUtil.i("RttChatFragment.onCreate", null); inCallButtonUiDelegate = FragmentUtils.getParent(this, InCallButtonUiDelegateFactory.class) .newInCallButtonUiDelegate(); if (savedInstanceState != null) { inCallButtonUiDelegate.onRestoreInstanceState(savedInstanceState); } } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle bundle) { super.onViewCreated(view, bundle); LogUtil.i("RttChatFragment.onViewCreated", null); inCallScreenDelegate = FragmentUtils.getParentUnsafe(this, InCallScreenDelegateFactory.class) .newInCallScreenDelegate(); rttCallScreenDelegate = FragmentUtils.getParentUnsafe(this, RttCallScreenDelegateFactory.class) .newRttCallScreenDelegate(this); rttCallScreenDelegate.initRttCallScreenDelegate(this); inCallScreenDelegate.onInCallScreenDelegateInit(this); inCallScreenDelegate.onInCallScreenReady(); inCallButtonUiDelegate.onInCallButtonUiReady(this); } @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( v -> { adapter.submitLocalMessage(); isClearingInput = true; editText.setText(""); isClearingInput = false; rttCallScreenDelegate.onLocalMessage(Constants.BUBBLE_BREAKER); }); submitButton.setEnabled(false); endCallButton = view.findViewById(R.id.rtt_end_call_button); endCallButton.setOnClickListener( v -> { LogUtil.i("RttChatFragment.onClick", "end call button clicked"); inCallButtonUiDelegate.onEndCallClicked(); }); overflowMenu = new RttOverflowMenu(getContext(), inCallButtonUiDelegate); view.findViewById(R.id.rtt_overflow_button) .setOnClickListener(v -> overflowMenu.showAtLocation(v, Gravity.TOP | Gravity.RIGHT, 0, 0)); nameTextView = view.findViewById(R.id.rtt_name_or_number); chronometer = view.findViewById(R.id.rtt_timer); return view; } @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; } String messageToAppend = RttChatMessage.getChangedString(s, start, before, count); if (!TextUtils.isEmpty(messageToAppend)) { adapter.addLocalMessage(messageToAppend); rttCallScreenDelegate.onLocalMessage(messageToAppend); } } @Override public void onRemoteMessage(String message) { adapter.addRemoteMessage(message); } @Override public void onDestroyView() { super.onDestroyView(); LogUtil.enterBlock("RttChatFragment.onDestroyView"); inCallButtonUiDelegate.onInCallButtonUiUnready(); inCallScreenDelegate.onInCallScreenUnready(); } @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()); } @Override public void onStart() { LogUtil.enterBlock("RttChatFragment.onStart"); super.onStart(); onRttScreenStart(); } @Override public void onStop() { LogUtil.enterBlock("RttChatFragment.onStop"); super.onStop(); if (overflowMenu.isShowing()) { overflowMenu.dismiss(); } onRttScreenStop(); } private void hideKeyboard() { InputMethodManager inputMethodManager = getContext().getSystemService(InputMethodManager.class); if (inputMethodManager.isAcceptingText()) { inputMethodManager.hideSoftInputFromWindow( getActivity().getCurrentFocus().getWindowToken(), 0); } } @Override public void onRttScreenStart() { rttCallScreenDelegate.onRttCallScreenUiReady(); Activity activity = getActivity(); Window window = getActivity().getWindow(); window.setStatusBarColor(activity.getColor(R.color.rtt_status_bar_color)); window.setNavigationBarColor(activity.getColor(R.color.rtt_navigation_bar_color)); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); } @Override public void onRttScreenStop() { rttCallScreenDelegate.onRttCallScreenUiUnready(); } @Override public Fragment getRttCallScreenFragment() { return this; } @Override public String getCallId() { return Assert.isNotNull(getArguments().getString(ARG_CALL_ID)); } @Override public void setPrimary(@NonNull PrimaryInfo primaryInfo) { LogUtil.i("RttChatFragment.setPrimary", primaryInfo.toString()); nameTextView.setText(primaryInfo.name()); } @Override public void setSecondary(@NonNull SecondaryInfo secondaryInfo) {} @Override public void setCallState(@NonNull PrimaryCallState primaryCallState) { LogUtil.i("RttChatFragment.setCallState", primaryCallState.toString()); if (!isTimerStarted && primaryCallState.state() == State.ACTIVE) { LogUtil.i( "RttChatFragment.setCallState", "starting timer with base: %d", chronometer.getBase()); chronometer.setBase( primaryCallState.connectTimeMillis() - System.currentTimeMillis() + SystemClock.elapsedRealtime()); chronometer.start(); isTimerStarted = true; } } @Override public void setEndCallButtonEnabled(boolean enabled, boolean animate) {} @Override public void showManageConferenceCallButton(boolean visible) {} @Override public boolean isManageConferenceVisible() { return false; } @Override public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {} @Override public void showNoteSentToast() {} @Override public void updateInCallScreenColors() {} @Override public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {} @Override public int getAnswerAndDialpadContainerResourceId() { return 0; } @Override public void showLocationUi(Fragment locationUi) {} @Override public boolean isShowingLocationUi() { return false; } @Override public Fragment getInCallScreenFragment() { return this; } @Override public void showButton(int buttonId, boolean show) {} @Override public void enableButton(int buttonId, boolean enable) {} @Override public void setEnabled(boolean on) {} @Override public void setHold(boolean on) {} @Override public void setCameraSwitched(boolean isBackFacingCamera) {} @Override public void setVideoPaused(boolean isPaused) {} @Override public void setAudioState(CallAudioState audioState) { LogUtil.i("RttChatFragment.setAudioState", "audioState: " + audioState); overflowMenu.setMuteButtonChecked(audioState.isMuted()); overflowMenu.setAudioState(audioState); } @Override public void updateButtonStates() {} @Override public void updateInCallButtonUiColors(int color) {} @Override public Fragment getInCallButtonUiFragment() { return this; } @Override public void showAudioRouteSelector() { AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState()) .show(getChildFragmentManager(), null); } @Override public void onAudioRouteSelected(int audioRoute) { inCallButtonUiDelegate.setAudioRoute(audioRoute); } @Override public void onAudioRouteSelectorDismiss() {} }