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 package com.android.car.dialer.ui; 17 18 import android.app.AlertDialog; 19 import android.content.Context; 20 import android.os.Bundle; 21 import android.support.annotation.NonNull; 22 import android.support.annotation.Nullable; 23 import android.support.v4.app.Fragment; 24 import android.support.v7.widget.RecyclerView; 25 import android.telecom.CallAudioState; 26 import android.telecom.CallAudioState.CallAudioRoute; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.ImageView; 31 import android.widget.TextView; 32 33 import androidx.car.widget.PagedListView; 34 35 import com.android.car.apps.common.FabDrawable; 36 import com.android.car.dialer.R; 37 import com.android.car.dialer.log.L; 38 import com.android.car.dialer.telecom.UiCall; 39 import com.android.car.dialer.telecom.UiCallManager; 40 41 import java.util.List; 42 43 /** 44 * A Fragment of the bar which controls on going call. Its host or parent Fragment is expected to 45 * implement {@link OnGoingCallControllerBarCallback}. 46 */ 47 public class OnGoingCallControllerBarFragment extends Fragment { 48 private static String TAG = "CDialer.OngoingCallCtlFrg"; 49 private AlertDialog mAudioRouteSelectionDialog; 50 private ImageView mAudioRouteButton; 51 52 public static OnGoingCallControllerBarFragment newInstance() { 53 return new OnGoingCallControllerBarFragment(); 54 } 55 56 /** 57 * Callback for control bar buttons. 58 */ 59 public interface OnGoingCallControllerBarCallback { 60 void onOpenDialpad(); 61 62 void onCloseDialpad(); 63 } 64 65 private OnGoingCallControllerBarCallback mOnGoingCallControllerBarCallback; 66 67 @Override 68 public void onCreate(@Nullable Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 if (getParentFragment() != null 71 && getParentFragment() instanceof OnGoingCallControllerBarCallback) { 72 mOnGoingCallControllerBarCallback = 73 (OnGoingCallControllerBarCallback) getParentFragment(); 74 } else if (getHost() instanceof OnGoingCallControllerBarCallback) { 75 mOnGoingCallControllerBarCallback = (OnGoingCallControllerBarCallback) getHost(); 76 } 77 78 View dialogView = LayoutInflater.from(getContext()).inflate( 79 R.layout.audio_route_switch_dialog, null, false); 80 PagedListView list = dialogView.findViewById(R.id.list); 81 List<Integer> availableRoutes = UiCallManager.get().getSupportedAudioRoute(); 82 list.setDividerVisibilityManager(position -> position == (availableRoutes.size() - 1)); 83 84 mAudioRouteSelectionDialog = new AlertDialog.Builder(getContext()) 85 .setView(dialogView) 86 .create(); 87 mAudioRouteSelectionDialog.getWindow().setBackgroundDrawableResource( 88 android.R.color.transparent); 89 list.setAdapter(new AudioRouteListAdapter(getContext(), availableRoutes)); 90 } 91 92 @Nullable 93 @Override 94 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 95 @Nullable Bundle savedInstanceState) { 96 View fragmentView = inflater.inflate(R.layout.on_going_call_controller_bar_fragment, 97 container, false); 98 fragmentView.findViewById(R.id.mute_button).setOnClickListener((v) -> { 99 if (mOnGoingCallControllerBarCallback == null) { 100 return; 101 } 102 if (v.isActivated()) { 103 v.setActivated(false); 104 onMuteMic(); 105 } else { 106 v.setActivated(true); 107 onUnmuteMic(); 108 } 109 }); 110 111 fragmentView.findViewById(R.id.toggle_dialpad_button).setOnClickListener((v) -> { 112 if (mOnGoingCallControllerBarCallback == null) { 113 return; 114 } 115 if (v.isActivated()) { 116 v.setActivated(false); 117 mOnGoingCallControllerBarCallback.onCloseDialpad(); 118 } else { 119 v.setActivated(true); 120 mOnGoingCallControllerBarCallback.onOpenDialpad(); 121 } 122 }); 123 124 ImageView endCallButton = fragmentView.findViewById(R.id.end_call_button); 125 FabDrawable answerCallDrawable = new FabDrawable(getContext()); 126 answerCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_end_call)); 127 endCallButton.setBackground(answerCallDrawable); 128 endCallButton.setOnClickListener((v) -> { 129 if (mOnGoingCallControllerBarCallback == null) { 130 return; 131 } 132 onEndCall(); 133 }); 134 135 136 List<Integer> audioRoutes = UiCallManager.get().getSupportedAudioRoute(); 137 mAudioRouteButton = fragmentView.findViewById(R.id.voice_channel_button); 138 if (audioRoutes.size() > 1) { 139 fragmentView.findViewById(R.id.voice_channel_chevron).setVisibility(View.VISIBLE); 140 mAudioRouteButton.setOnClickListener( 141 (v) -> mAudioRouteSelectionDialog.show()); 142 } else { 143 fragmentView.findViewById(R.id.voice_channel_chevron).setVisibility(View.GONE); 144 } 145 146 fragmentView.findViewById(R.id.pause_button).setOnClickListener((v) -> { 147 if (mOnGoingCallControllerBarCallback == null) { 148 return; 149 } 150 if (v.isActivated()) { 151 v.setActivated(false); 152 onHoldCall(); 153 } else { 154 v.setActivated(true); 155 onUnholdCall(); 156 } 157 }); 158 return fragmentView; 159 } 160 161 @Override 162 public void onPause() { 163 super.onPause(); 164 if (mAudioRouteSelectionDialog.isShowing()) { 165 mAudioRouteSelectionDialog.dismiss(); 166 } 167 } 168 169 private void onMuteMic() { 170 UiCallManager.get().setMuted(true); 171 } 172 173 private void onUnmuteMic() { 174 UiCallManager.get().setMuted(false); 175 } 176 177 private void onHoldCall() { 178 UiCallManager uiCallManager = UiCallManager.get(); 179 UiCall primaryCall = UiCallManager.get().getPrimaryCall(); 180 uiCallManager.holdCall(primaryCall); 181 } 182 183 private void onUnholdCall() { 184 UiCallManager uiCallManager = UiCallManager.get(); 185 UiCall primaryCall = UiCallManager.get().getPrimaryCall(); 186 uiCallManager.unholdCall(primaryCall); 187 } 188 189 private void onVoiceOutputChannelChanged(@CallAudioRoute int audioRoute) { 190 UiCallManager.get().setAudioRoute(audioRoute); 191 mAudioRouteSelectionDialog.dismiss(); 192 mAudioRouteButton.setImageResource(getAudioRouteIconRes(audioRoute)); 193 } 194 195 private void onEndCall() { 196 UiCallManager uiCallManager = UiCallManager.get(); 197 UiCall primaryCall = UiCallManager.get().getPrimaryCall(); 198 uiCallManager.disconnectCall(primaryCall); 199 } 200 201 private int getAudioRouteIconRes(@CallAudioRoute int audioRoute) { 202 switch (audioRoute) { 203 case CallAudioState.ROUTE_WIRED_HEADSET: 204 case CallAudioState.ROUTE_EARPIECE: 205 return R.drawable.ic_smartphone; 206 case CallAudioState.ROUTE_BLUETOOTH: 207 return R.drawable.ic_bluetooth; 208 case CallAudioState.ROUTE_SPEAKER: 209 return R.drawable.ic_speaker_phone; 210 default: 211 L.w(TAG, "Unknown audio route: " + audioRoute); 212 return -1; 213 } 214 } 215 216 private int getAudioRouteLabelRes(@CallAudioRoute int audioRoute) { 217 switch (audioRoute) { 218 case CallAudioState.ROUTE_WIRED_HEADSET: 219 case CallAudioState.ROUTE_EARPIECE: 220 return R.string.audio_route_handset; 221 case CallAudioState.ROUTE_BLUETOOTH: 222 return R.string.audio_route_vehicle; 223 case CallAudioState.ROUTE_SPEAKER: 224 return R.string.audio_route_phone_speaker; 225 default: 226 L.w(TAG, "Unknown audio route: " + audioRoute); 227 return -1; 228 } 229 } 230 231 private class AudioRouteListAdapter extends 232 RecyclerView.Adapter<AudioRouteItemViewHolder> { 233 private List<Integer> mSupportedRoutes; 234 private Context mContext; 235 236 public AudioRouteListAdapter(Context context, List<Integer> supportedRoutes) { 237 mSupportedRoutes = supportedRoutes; 238 mContext = context; 239 if (mSupportedRoutes.contains(CallAudioState.ROUTE_EARPIECE) 240 && mSupportedRoutes.contains(CallAudioState.ROUTE_WIRED_HEADSET)) { 241 // Keep either ROUTE_EARPIECE or ROUTE_WIRED_HEADSET, but not both of them. 242 mSupportedRoutes.remove(CallAudioState.ROUTE_WIRED_HEADSET); 243 } 244 } 245 246 @Override 247 public AudioRouteItemViewHolder onCreateViewHolder(ViewGroup container, int position) { 248 View listItemView = LayoutInflater.from(mContext).inflate( 249 R.layout.audio_route_list_item, container, false); 250 return new AudioRouteItemViewHolder(listItemView); 251 } 252 253 @Override 254 public void onBindViewHolder(AudioRouteItemViewHolder viewHolder, int position) { 255 int audioRoute = mSupportedRoutes.get(position); 256 viewHolder.mBody.setText(mContext.getString(getAudioRouteLabelRes(audioRoute))); 257 viewHolder.mIcon.setImageResource(getAudioRouteIconRes(audioRoute)); 258 viewHolder.itemView.setOnClickListener((v) -> onVoiceOutputChannelChanged(audioRoute)); 259 } 260 261 @Override 262 public int getItemCount() { 263 return mSupportedRoutes.size(); 264 } 265 } 266 267 private static class AudioRouteItemViewHolder extends RecyclerView.ViewHolder { 268 public final ImageView mIcon; 269 public final TextView mBody; 270 271 public AudioRouteItemViewHolder(View itemView) { 272 super(itemView); 273 mIcon = itemView.findViewById(R.id.icon); 274 mBody = itemView.findViewById(R.id.body); 275 } 276 } 277 }