blob: b35635c5c56616aea3aa9a6422bde3c19c6e4e7a [file] [log] [blame]
/*
* 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.car.dialer.ui.activecall;
import android.app.AlertDialog;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.telecom.Call;
import android.telecom.CallAudioState;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.util.Pair;
import androidx.core.util.Preconditions;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProviders;
import com.android.car.apps.common.util.ViewUtils;
import com.android.car.dialer.R;
import com.android.car.dialer.log.L;
import com.android.car.dialer.telecom.UiCallManager;
import com.android.car.ui.AlertDialogBuilder;
import com.android.car.ui.recyclerview.CarUiContentListItem;
import com.android.car.ui.recyclerview.CarUiListItem;
import com.android.car.ui.recyclerview.CarUiListItemAdapter;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.List;
/** A Fragment of the bar which controls on going call. */
public class OnGoingCallControllerBarFragment extends Fragment {
private static final String TAG = "CD.OngoingCallCtlFrg";
private static final ImmutableMap<Integer, AudioRouteInfo> AUDIO_ROUTES =
ImmutableMap.<Integer, AudioRouteInfo>builder()
.put(CallAudioState.ROUTE_WIRED_HEADSET, new AudioRouteInfo(
R.string.audio_route_handset,
R.drawable.ic_audio_route_headset,
R.drawable.ic_audio_route_headset_activatable))
.put(CallAudioState.ROUTE_EARPIECE, new AudioRouteInfo(
R.string.audio_route_handset,
R.drawable.ic_audio_route_earpiece,
R.drawable.ic_audio_route_earpiece_activatable))
.put(CallAudioState.ROUTE_BLUETOOTH, new AudioRouteInfo(
R.string.audio_route_vehicle,
R.drawable.ic_audio_route_vehicle,
R.drawable.ic_audio_route_vehicle_activatable))
.put(CallAudioState.ROUTE_SPEAKER, new AudioRouteInfo(
R.string.audio_route_phone_speaker,
R.drawable.ic_audio_route_speaker,
R.drawable.ic_audio_route_speaker_activatable))
.build();
private InCallViewModel mInCallViewModel;
private AlertDialog mAudioRouteSelectionDialog;
private List<CarUiListItem> mAudioRouteListItems;
private List<Integer> mAvailableRoutes;
private CarUiListItemAdapter mAudioRouteAdapter;
private View mMuteButton;
private View mAudioRouteView;
private ImageView mAudioRouteButton;
private TextView mAudioRouteText;
private View mMergeButton;
private View mPauseButton;
private LiveData<Call> mPrimaryCallLiveData;
private LiveData<List<Call>> mOngoingCallListLiveData;
private LiveData<Pair<Call, Call>> mOngoingCallPairLiveData;
private MutableLiveData<Boolean> mDialpadState;
private LiveData<List<Call>> mCallListLiveData;
private int mPrimaryCallState;
private int mActiveRoute;
private MutableLiveData<CallAudioState> mCallAudioState;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAvailableRoutes = UiCallManager.get().getSupportedAudioRoute();
mActiveRoute = UiCallManager.get().getAudioRoute();
if (mAvailableRoutes.contains(CallAudioState.ROUTE_EARPIECE)
&& mAvailableRoutes.contains(CallAudioState.ROUTE_WIRED_HEADSET)) {
// Keep either ROUTE_EARPIECE or ROUTE_WIRED_HEADSET, but not both of them.
mAvailableRoutes.remove(CallAudioState.ROUTE_WIRED_HEADSET);
}
mAudioRouteListItems = new ArrayList<>();
mAudioRouteAdapter = new CarUiListItemAdapter(mAudioRouteListItems);
for (Integer audioRoute : mAvailableRoutes) {
CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
AudioRouteInfo routeInfo = getAudioRouteInfo(audioRoute);
Drawable drawable = getResources().getDrawable(routeInfo.mIcon, null);
drawable.setTintList(
getResources().getColorStateList(R.color.icon_accent_activatable, null));
item.setIcon(drawable);
item.setOnItemClickedListener((i) -> {
onSetAudioRoute(audioRoute);
});
String routeTitle = getString(routeInfo.mLabel);
item.setTitle(mActiveRoute == audioRoute ? withAccentColor(routeTitle) : routeTitle);
item.setActivated(mActiveRoute == audioRoute);
mAudioRouteListItems.add(item);
}
AlertDialogBuilder audioRouteSelectionDialogBuilder = new AlertDialogBuilder(getContext())
.setAdapter(mAudioRouteAdapter)
.setTitle(getString(R.string.audio_route_dialog_title));
String subtitle = getString(R.string.audio_route_dialog_subtitle);
if (!subtitle.isEmpty()) {
audioRouteSelectionDialogBuilder.setSubtitle(subtitle);
}
mAudioRouteSelectionDialog = audioRouteSelectionDialogBuilder.create();
mInCallViewModel = ViewModelProviders.of(getActivity()).get(InCallViewModel.class);
mInCallViewModel.getPrimaryCallState().observe(this, this::setCallState);
mPrimaryCallLiveData = mInCallViewModel.getPrimaryCall();
mOngoingCallPairLiveData = mInCallViewModel.getOngoingCallPair();
mOngoingCallListLiveData = mInCallViewModel.getOngoingCallList();
mInCallViewModel.getAudioRoute().observe(this, this::updateViewBasedOnAudioRoute);
mDialpadState = mInCallViewModel.getDialpadOpenState();
mCallAudioState = mInCallViewModel.getCallAudioState();
mCallListLiveData = mInCallViewModel.getAllCallList();
mCallListLiveData.observe(this, v -> updatePauseButtonEnabledState());
mOngoingCallPairLiveData.observe(this, pair -> {
boolean isPrimaryCallConference = pair.first != null
&& pair.first.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE);
if (!isPrimaryCallConference && pair.second != null) {
mPauseButton.setVisibility(View.GONE);
mMergeButton.setVisibility(View.VISIBLE);
} else {
mPauseButton.setVisibility(View.VISIBLE);
mMergeButton.setVisibility(View.GONE);
}
});
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View fragmentView = inflater.inflate(R.layout.on_going_call_controller_bar_fragment,
container, false);
mMuteButton = fragmentView.findViewById(R.id.mute_button);
mMuteButton.setOnClickListener((v) -> {
if (v.isActivated()) {
v.setActivated(false);
onUnmuteMic();
} else {
v.setActivated(true);
onMuteMic();
}
});
mCallAudioState.observe(this, state -> mMuteButton.setActivated(state.isMuted()));
View dialPadButton = fragmentView.findViewById(R.id.toggle_dialpad_button);
dialPadButton.setOnClickListener(v -> mDialpadState.setValue(!mDialpadState.getValue()));
mDialpadState.observe(this, activated -> dialPadButton.setActivated(activated));
View endCallButton = fragmentView.findViewById(R.id.end_call_button);
endCallButton.setOnClickListener(v -> onEndCall());
List<Integer> audioRoutes = UiCallManager.get().getSupportedAudioRoute();
mAudioRouteView = fragmentView.findViewById(R.id.voice_channel_view);
mAudioRouteButton = fragmentView.findViewById(R.id.voice_channel_button);
mAudioRouteText = fragmentView.findViewById(R.id.voice_channel_text);
if (audioRoutes.size() > 1) {
mAudioRouteView.setOnClickListener((v) -> {
mAudioRouteView.setActivated(true);
mAudioRouteSelectionDialog.show();
});
}
mAudioRouteSelectionDialog.setOnDismissListener(
(dialog) -> mAudioRouteView.setActivated(false));
mMergeButton = fragmentView.findViewById(R.id.merge_button);
mMergeButton.setOnClickListener((v) -> {
mInCallViewModel.mergeConference();
});
mPauseButton = fragmentView.findViewById(R.id.pause_button);
mPauseButton.setOnClickListener((v) -> {
if (mPrimaryCallState == Call.STATE_ACTIVE) {
onHoldCall();
} else if (mPrimaryCallState == Call.STATE_HOLDING) {
onUnholdCall();
} else {
L.i(TAG, "Pause button is clicked while call in %s state", mPrimaryCallState);
}
});
updatePauseButtonEnabledState();
return fragmentView;
}
@Override
public void onPause() {
super.onPause();
L.i(TAG, "onPause");
if (mAudioRouteSelectionDialog.isShowing()) {
mAudioRouteSelectionDialog.dismiss();
}
}
private void updateAudioRouteListItems() {
for (int i = 0; i < mAvailableRoutes.size(); i++) {
int audioRoute = mAvailableRoutes.get(i);
CarUiContentListItem item = (CarUiContentListItem) mAudioRouteListItems.get(i);
boolean isActiveRoute = audioRoute == mActiveRoute;
String routeTitle = item.getTitle().toString();
item.setActivated(isActiveRoute);
item.setTitle(isActiveRoute ? withAccentColor(routeTitle) : routeTitle);
}
mAudioRouteAdapter.notifyDataSetChanged();
}
private CharSequence withAccentColor(String routeTitle) {
ForegroundColorSpan activeRouteSpan = new ForegroundColorSpan(
getResources().getColor(R.color.audio_output_accent, null));
SpannableString spannableTitle = new SpannableString(routeTitle);
spannableTitle.setSpan(activeRouteSpan, 0, routeTitle.length(), 0);
return spannableTitle;
}
/** Set the call state and change the view for the pause button accordingly */
private void setCallState(int callState) {
L.d(TAG, "Call State: %s", callState);
mPrimaryCallState = callState;
updatePauseButtonEnabledState();
}
private void updatePauseButtonEnabledState() {
boolean hasOnlyOneCall = mOngoingCallListLiveData.getValue() != null
&& mOngoingCallListLiveData.getValue().size() == 1;
boolean shouldEnablePauseButton = hasOnlyOneCall && (mPrimaryCallState == Call.STATE_HOLDING
|| mPrimaryCallState == Call.STATE_ACTIVE);
mPauseButton.setEnabled(shouldEnablePauseButton);
mPauseButton.setActivated(mPrimaryCallState == Call.STATE_HOLDING);
}
private void onMuteMic() {
UiCallManager.get().setMuted(true);
}
private void onUnmuteMic() {
UiCallManager.get().setMuted(false);
}
private void onHoldCall() {
if (mPrimaryCallLiveData.getValue() != null) {
mPrimaryCallLiveData.getValue().hold();
}
}
private void onUnholdCall() {
if (mPrimaryCallLiveData.getValue() != null) {
mPrimaryCallLiveData.getValue().unhold();
}
}
private void onEndCall() {
if (mPrimaryCallLiveData.getValue() != null) {
mPrimaryCallLiveData.getValue().disconnect();
}
}
private void onSetAudioRoute(int audioRoute) {
UiCallManager.get().setAudioRoute(audioRoute);
mActiveRoute = audioRoute;
updateAudioRouteListItems();
mAudioRouteSelectionDialog.dismiss();
}
private void updateViewBasedOnAudioRoute(@Nullable Integer audioRoute) {
if (audioRoute == null || audioRoute.intValue() == 0) {
return;
}
L.i(TAG, "Audio Route State: " + audioRoute);
mActiveRoute = audioRoute;
updateAudioRouteListItems();
AudioRouteInfo audioRouteInfo = getAudioRouteInfo(audioRoute);
if (mAudioRouteButton != null) {
mAudioRouteButton.setImageResource(audioRouteInfo.mIconActivatable);
}
ViewUtils.setText(mAudioRouteText, audioRouteInfo.mLabel);
updateMuteButtonEnabledState(audioRoute);
}
private void updateMuteButtonEnabledState(Integer audioRoute) {
if (audioRoute == CallAudioState.ROUTE_BLUETOOTH) {
mMuteButton.setEnabled(true);
mMuteButton.setActivated(UiCallManager.get().getMuted());
} else {
mMuteButton.setEnabled(false);
}
}
@NonNull
private static AudioRouteInfo getAudioRouteInfo(int route) {
Preconditions.checkArgument(route != 0, "Unknown audio route: 0");
AudioRouteInfo routeInfo = AUDIO_ROUTES.get(route);
return routeInfo;
}
private static final class AudioRouteInfo {
private final int mLabel;
private final int mIcon;
private final int mIconActivatable;
private AudioRouteInfo(@StringRes int label,
@DrawableRes int icon,
@DrawableRes int iconActivatable) {
mLabel = label;
mIcon = icon;
mIconActivatable = iconActivatable;
}
}
}