• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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