1 /* 2 * Copyright (C) 2015 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.telecom; 17 18 import android.content.Context; 19 import android.database.Cursor; 20 import android.provider.CallLog; 21 import android.telecom.Call; 22 import android.telephony.TelephonyManager; 23 import android.text.TextUtils; 24 import android.util.Log; 25 import android.view.KeyEvent; 26 import com.android.car.dialer.ClassFactory; 27 import com.android.car.dialer.R; 28 29 import java.util.ArrayList; 30 import java.util.Calendar; 31 import java.util.Collections; 32 import java.util.Comparator; 33 import java.util.List; 34 35 /** 36 * The entry point for all interactions between UI and telecom. 37 */ 38 public abstract class UiCallManager { 39 private static String TAG = "Em.TelecomMgr"; 40 41 private static Object sInstanceLock = new Object(); 42 private static UiCallManager sInstance; 43 44 private long mLastPlacedCallTimeMs = 0; 45 46 // Rate limit how often you can place outgoing calls. 47 private static final long MIN_TIME_BETWEEN_CALLS_MS = 3000; 48 49 private static final List<Integer> sCallStateRank = new ArrayList<>(); 50 51 protected Context mContext; 52 53 protected TelephonyManager mTelephonyManager; 54 55 static { 56 // States should be added from lowest rank to highest 57 sCallStateRank.add(Call.STATE_DISCONNECTED); 58 sCallStateRank.add(Call.STATE_DISCONNECTING); 59 sCallStateRank.add(Call.STATE_NEW); 60 sCallStateRank.add(Call.STATE_CONNECTING); 61 sCallStateRank.add(Call.STATE_SELECT_PHONE_ACCOUNT); 62 sCallStateRank.add(Call.STATE_HOLDING); 63 sCallStateRank.add(Call.STATE_ACTIVE); 64 sCallStateRank.add(Call.STATE_DIALING); 65 sCallStateRank.add(Call.STATE_RINGING); 66 } 67 getInstance(Context context)68 public static UiCallManager getInstance(Context context) { 69 synchronized (sInstanceLock) { 70 if (sInstance == null) { 71 if (Log.isLoggable(TAG, Log.DEBUG)) { 72 Log.d(TAG, "Creating an instance of CarTelecomManager"); 73 } 74 sInstance = ClassFactory.getFactory().createCarTelecomManager(); 75 sInstance.setUp(context.getApplicationContext()); 76 } 77 } 78 return sInstance; 79 } 80 UiCallManager()81 protected UiCallManager() {} 82 setUp(Context context)83 protected void setUp(Context context) { 84 if (Log.isLoggable(TAG, Log.DEBUG)) { 85 Log.d(TAG, "SetUp"); 86 } 87 88 mContext = context; 89 mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 90 } 91 tearDown()92 public abstract void tearDown(); 93 addListener(CallListener listener)94 public abstract void addListener(CallListener listener); 95 removeListener(CallListener listener)96 public abstract void removeListener(CallListener listener); 97 placeCall(String number)98 protected abstract void placeCall(String number); 99 answerCall(UiCall call)100 public abstract void answerCall(UiCall call); 101 rejectCall(UiCall call, boolean rejectWithMessage, String textMessage)102 public abstract void rejectCall(UiCall call, boolean rejectWithMessage, String textMessage); 103 disconnectCall(UiCall call)104 public abstract void disconnectCall(UiCall call); 105 getCalls()106 public abstract List<UiCall> getCalls(); 107 getMuted()108 public abstract boolean getMuted(); 109 setMuted(boolean muted)110 public abstract void setMuted(boolean muted); 111 getSupportedAudioRouteMask()112 public abstract int getSupportedAudioRouteMask(); 113 getAudioRoute()114 public abstract int getAudioRoute(); 115 setAudioRoute(int audioRoute)116 public abstract void setAudioRoute(int audioRoute); 117 holdCall(UiCall call)118 public abstract void holdCall(UiCall call); 119 unholdCall(UiCall call)120 public abstract void unholdCall(UiCall call); 121 playDtmfTone(UiCall call, char digit)122 public abstract void playDtmfTone(UiCall call, char digit); 123 stopDtmfTone(UiCall call)124 public abstract void stopDtmfTone(UiCall call); 125 postDialContinue(UiCall call, boolean proceed)126 public abstract void postDialContinue(UiCall call, boolean proceed); 127 conference(UiCall call, UiCall otherCall)128 public abstract void conference(UiCall call, UiCall otherCall); 129 splitFromConference(UiCall call)130 public abstract void splitFromConference(UiCall call); 131 132 public static class CallListener { 133 @SuppressWarnings("unused") dispatchPhoneKeyEvent(KeyEvent event)134 public void dispatchPhoneKeyEvent(KeyEvent event) {} 135 @SuppressWarnings("unused") onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask)136 public void onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask) {} 137 @SuppressWarnings("unused") onCallAdded(UiCall call)138 public void onCallAdded(UiCall call) {} 139 @SuppressWarnings("unused") onStateChanged(UiCall call, int state)140 public void onStateChanged(UiCall call, int state) {} 141 @SuppressWarnings("unused") onCallUpdated(UiCall call)142 public void onCallUpdated(UiCall call) {} 143 @SuppressWarnings("unused") onCallRemoved(UiCall call)144 public void onCallRemoved(UiCall call) {} 145 } 146 147 /** Returns a first call that matches at least one provided call state */ getCallWithState(int... callStates)148 public UiCall getCallWithState(int... callStates) { 149 if (Log.isLoggable(TAG, Log.DEBUG)) { 150 Log.d(TAG, "getCallWithState: " + callStates); 151 } 152 for (UiCall call : getCalls()) { 153 for (int callState : callStates) { 154 if (call.getState() == callState) { 155 return call; 156 } 157 } 158 } 159 return null; 160 } 161 getPrimaryCall()162 public UiCall getPrimaryCall() { 163 if (Log.isLoggable(TAG, Log.DEBUG)) { 164 Log.d(TAG, "getPrimaryCall"); 165 } 166 List<UiCall> calls = getCalls(); 167 if (calls.isEmpty()) { 168 return null; 169 } 170 171 Collections.sort(calls, getCallComparator()); 172 UiCall uiCall = calls.get(0); 173 if (uiCall.hasParent()) { 174 return null; 175 } 176 return uiCall; 177 } 178 getSecondaryCall()179 public UiCall getSecondaryCall() { 180 if (Log.isLoggable(TAG, Log.DEBUG)) { 181 Log.d(TAG, "getSecondaryCall"); 182 } 183 List<UiCall> calls = getCalls(); 184 if (calls.size() < 2) { 185 return null; 186 } 187 188 Collections.sort(calls, getCallComparator()); 189 UiCall uiCall = calls.get(1); 190 if (uiCall.hasParent()) { 191 return null; 192 } 193 return uiCall; 194 } 195 196 public static final int CAN_PLACE_CALL_RESULT_OK = 0; 197 public static final int CAN_PLACE_CALL_RESULT_NETWORK_UNAVAILABLE = 1; 198 public static final int CAN_PLACE_CALL_RESULT_HFP_UNAVAILABLE = 2; 199 public static final int CAN_PLACE_CALL_RESULT_AIRPLANE_MODE = 3; 200 getCanPlaceCallStatus(String number, boolean bluetoothRequired)201 public int getCanPlaceCallStatus(String number, boolean bluetoothRequired) { 202 // TODO(b/26191392): figure out the logic for projected and embedded modes 203 return CAN_PLACE_CALL_RESULT_OK; 204 } 205 getFailToPlaceCallMessage(int canPlaceCallResult)206 public String getFailToPlaceCallMessage(int canPlaceCallResult) { 207 switch (canPlaceCallResult) { 208 case CAN_PLACE_CALL_RESULT_OK: 209 return ""; 210 case CAN_PLACE_CALL_RESULT_HFP_UNAVAILABLE: 211 return mContext.getString(R.string.error_no_hfp); 212 case CAN_PLACE_CALL_RESULT_AIRPLANE_MODE: 213 return mContext.getString(R.string.error_airplane_mode); 214 case CAN_PLACE_CALL_RESULT_NETWORK_UNAVAILABLE: 215 default: 216 return mContext.getString(R.string.error_network_not_available); 217 } 218 } 219 220 /** Places call only if there's no outgoing call right now */ safePlaceCall(String number, boolean bluetoothRequired)221 public void safePlaceCall(String number, boolean bluetoothRequired) { 222 if (Log.isLoggable(TAG, Log.DEBUG)) { 223 Log.d(TAG, "safePlaceCall: " + number); 224 } 225 226 int placeCallStatus = getCanPlaceCallStatus(number, bluetoothRequired); 227 if (placeCallStatus != CAN_PLACE_CALL_RESULT_OK) { 228 if (Log.isLoggable(TAG, Log.DEBUG)) { 229 Log.d(TAG, "Unable to place a call: " + placeCallStatus); 230 } 231 return; 232 } 233 234 UiCall outgoingCall = getCallWithState( 235 Call.STATE_CONNECTING, Call.STATE_NEW, Call.STATE_DIALING); 236 if (outgoingCall == null) { 237 long now = Calendar.getInstance().getTimeInMillis(); 238 if (now - mLastPlacedCallTimeMs > MIN_TIME_BETWEEN_CALLS_MS) { 239 placeCall(number); 240 mLastPlacedCallTimeMs = now; 241 } else { 242 if (Log.isLoggable(TAG, Log.INFO)) { 243 Log.i(TAG, "You have to wait " + MIN_TIME_BETWEEN_CALLS_MS 244 + "ms between making calls"); 245 } 246 } 247 } 248 } 249 callVoicemail()250 public void callVoicemail() { 251 if (Log.isLoggable(TAG, Log.DEBUG)) { 252 Log.d(TAG, "callVoicemail"); 253 } 254 255 String voicemailNumber = TelecomUtils.getVoicemailNumber(mContext); 256 if (TextUtils.isEmpty(voicemailNumber)) { 257 Log.w(TAG, "Unable to get voicemail number."); 258 return; 259 } 260 safePlaceCall(voicemailNumber, false); 261 } 262 263 /** 264 * Returns the call types for the given number of items in the cursor. 265 * <p/> 266 * It uses the next {@code count} rows in the cursor to extract the types. 267 * <p/> 268 * Its position in the cursor is unchanged by this function. 269 */ getCallTypes(Cursor cursor, int count)270 public int[] getCallTypes(Cursor cursor, int count) { 271 if (Log.isLoggable(TAG, Log.DEBUG)) { 272 Log.d(TAG, "getCallTypes: cursor: " + cursor + ", count: " + count); 273 } 274 275 int position = cursor.getPosition(); 276 int[] callTypes = new int[count]; 277 String voicemailNumber = mTelephonyManager.getVoiceMailNumber(); 278 int column; 279 for (int index = 0; index < count; ++index) { 280 column = cursor.getColumnIndex(CallLog.Calls.NUMBER); 281 String phoneNumber = cursor.getString(column); 282 if (phoneNumber != null && phoneNumber.equals(voicemailNumber)) { 283 callTypes[index] = PhoneLoader.VOICEMAIL_TYPE; 284 } else { 285 column = cursor.getColumnIndex(CallLog.Calls.TYPE); 286 callTypes[index] = cursor.getInt(column); 287 } 288 cursor.moveToNext(); 289 } 290 cursor.moveToPosition(position); 291 return callTypes; 292 } 293 getCallComparator()294 private static Comparator<UiCall> getCallComparator() { 295 return new Comparator<UiCall>() { 296 @Override 297 public int compare(UiCall call, UiCall otherCall) { 298 if (call.hasParent() && !otherCall.hasParent()) { 299 return 1; 300 } else if (!call.hasParent() && otherCall.hasParent()) { 301 return -1; 302 } 303 int carCallRank = sCallStateRank.indexOf(call.getState()); 304 int otherCarCallRank = sCallStateRank.indexOf(otherCall.getState()); 305 306 return otherCarCallRank - carCallRank; 307 } 308 }; 309 } 310 } 311