1 /* 2 * Copyright (C) 2017 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.call; 18 19 import android.annotation.TargetApi; 20 import android.app.Notification; 21 import android.bluetooth.BluetoothDevice; 22 import android.content.ActivityNotFoundException; 23 import android.content.Intent; 24 import android.os.Looper; 25 import android.support.annotation.MainThread; 26 import android.support.annotation.VisibleForTesting; 27 import android.telecom.InCallService; 28 import com.android.dialer.common.Assert; 29 import com.android.dialer.common.LogUtil; 30 import java.util.List; 31 32 /** Wrapper around Telecom APIs. */ 33 public class TelecomAdapter implements InCallServiceListener { 34 35 private static final String ADD_CALL_MODE_KEY = "add_call_mode"; 36 37 private static TelecomAdapter instance; 38 private InCallService inCallService; 39 TelecomAdapter()40 private TelecomAdapter() {} 41 42 @MainThread getInstance()43 public static TelecomAdapter getInstance() { 44 if (!Looper.getMainLooper().isCurrentThread()) { 45 throw new IllegalStateException(); 46 } 47 if (instance == null) { 48 instance = new TelecomAdapter(); 49 } 50 return instance; 51 } 52 53 @VisibleForTesting(otherwise = VisibleForTesting.NONE) setInstanceForTesting(TelecomAdapter telecomAdapter)54 public static void setInstanceForTesting(TelecomAdapter telecomAdapter) { 55 instance = telecomAdapter; 56 } 57 58 @Override setInCallService(InCallService inCallService)59 public void setInCallService(InCallService inCallService) { 60 this.inCallService = inCallService; 61 } 62 63 @Override clearInCallService()64 public void clearInCallService() { 65 inCallService = null; 66 } 67 getTelecomCallById(String callId)68 private android.telecom.Call getTelecomCallById(String callId) { 69 DialerCall call = CallList.getInstance().getCallById(callId); 70 return call == null ? null : call.getTelecomCall(); 71 } 72 mute(boolean shouldMute)73 public void mute(boolean shouldMute) { 74 if (inCallService != null) { 75 inCallService.setMuted(shouldMute); 76 } else { 77 LogUtil.e("TelecomAdapter.mute", "mInCallService is null"); 78 } 79 } 80 setAudioRoute(int route)81 public void setAudioRoute(int route) { 82 if (inCallService != null) { 83 inCallService.setAudioRoute(route); 84 } else { 85 LogUtil.e("TelecomAdapter.setAudioRoute", "mInCallService is null"); 86 } 87 } 88 merge(String callId)89 public void merge(String callId) { 90 android.telecom.Call call = getTelecomCallById(callId); 91 if (call != null) { 92 List<android.telecom.Call> conferenceable = call.getConferenceableCalls(); 93 if (!conferenceable.isEmpty()) { 94 call.conference(conferenceable.get(0)); 95 // It's safe to clear restrict count for merge action. 96 DialerCall.clearRestrictedCount(); 97 } else { 98 if (call.getDetails().can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE)) { 99 call.mergeConference(); 100 // It's safe to clear restrict count for merge action. 101 DialerCall.clearRestrictedCount(); 102 } 103 } 104 } else { 105 LogUtil.e("TelecomAdapter.merge", "call not in call list " + callId); 106 } 107 } 108 swap(String callId)109 public void swap(String callId) { 110 android.telecom.Call call = getTelecomCallById(callId); 111 if (call != null) { 112 if (call.getDetails().can(android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE)) { 113 call.swapConference(); 114 } 115 } else { 116 LogUtil.e("TelecomAdapter.swap", "call not in call list " + callId); 117 } 118 } 119 addCall()120 public void addCall() { 121 if (inCallService != null) { 122 Intent intent = new Intent(Intent.ACTION_DIAL); 123 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 124 125 // when we request the dialer come up, we also want to inform 126 // it that we're going through the "add call" option from the 127 // InCallScreen / PhoneUtils. 128 intent.putExtra(ADD_CALL_MODE_KEY, true); 129 try { 130 LogUtil.d("TelecomAdapter.addCall", "Sending the add DialerCall intent"); 131 inCallService.startActivity(intent); 132 } catch (ActivityNotFoundException e) { 133 // This is rather rare but possible. 134 // Note: this method is used even when the phone is encrypted. At that moment 135 // the system may not find any Activity which can accept this Intent. 136 LogUtil.e("TelecomAdapter.addCall", "Activity for adding calls isn't found.", e); 137 } 138 } 139 } 140 playDtmfTone(String callId, char digit)141 public void playDtmfTone(String callId, char digit) { 142 android.telecom.Call call = getTelecomCallById(callId); 143 if (call != null) { 144 call.playDtmfTone(digit); 145 } else { 146 LogUtil.e("TelecomAdapter.playDtmfTone", "call not in call list " + callId); 147 } 148 } 149 stopDtmfTone(String callId)150 public void stopDtmfTone(String callId) { 151 android.telecom.Call call = getTelecomCallById(callId); 152 if (call != null) { 153 call.stopDtmfTone(); 154 } else { 155 LogUtil.e("TelecomAdapter.stopDtmfTone", "call not in call list " + callId); 156 } 157 } 158 postDialContinue(String callId, boolean proceed)159 public void postDialContinue(String callId, boolean proceed) { 160 android.telecom.Call call = getTelecomCallById(callId); 161 if (call != null) { 162 call.postDialContinue(proceed); 163 } else { 164 LogUtil.e("TelecomAdapter.postDialContinue", "call not in call list " + callId); 165 } 166 } 167 canAddCall()168 public boolean canAddCall() { 169 if (inCallService != null) { 170 return inCallService.canAddCall(); 171 } 172 return false; 173 } 174 175 /** 176 * Start a foreground notification. Calling it multiple times with the same id only updates the 177 * existing notification. Whoever called this function are responsible for calling {@link 178 * #stopForegroundNotification()} to remove the notification. 179 */ startForegroundNotification(int id, Notification notification)180 public void startForegroundNotification(int id, Notification notification) { 181 Assert.isNotNull( 182 inCallService, "No inCallService available for starting foreground notification"); 183 inCallService.startForeground(id, notification); 184 } 185 186 /** 187 * Stop a started foreground notification. This does not stop {@code mInCallService} from running. 188 */ stopForegroundNotification()189 public void stopForegroundNotification() { 190 if (inCallService != null) { 191 inCallService.stopForeground(true /*removeNotification*/); 192 } else { 193 LogUtil.e( 194 "TelecomAdapter.stopForegroundNotification", 195 "no inCallService available for stopping foreground notification"); 196 } 197 } 198 199 @TargetApi(28) requestBluetoothAudio(BluetoothDevice bluetoothDevice)200 public void requestBluetoothAudio(BluetoothDevice bluetoothDevice) { 201 if (inCallService != null) { 202 inCallService.requestBluetoothAudio(bluetoothDevice); 203 } else { 204 LogUtil.e("TelecomAdapter.requestBluetoothAudio", "inCallService is null"); 205 } 206 } 207 } 208