1 /*
2  * Copyright (C) 2020 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.bluetooth.telephony;
18 
19 import android.net.Uri;
20 import android.os.Bundle;
21 import android.os.Handler;
22 import android.telecom.Call;
23 import android.telecom.DisconnectCause;
24 import android.telecom.GatewayInfo;
25 import android.telecom.InCallService;
26 import android.telecom.PhoneAccountHandle;
27 
28 import com.android.bluetooth.apishim.BluetoothCallShimImpl;
29 import com.android.internal.annotations.VisibleForTesting;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.UUID;
34 
35 /**
36  * A proxy class of android.telecom.Call that 1) facilitates testing of the BluetoothInCallService
37  * class; We can't mock the final class Call directly; 2) Some helper functions, to let Call have
38  * same methods as com.android.server.telecom.Call
39  *
40  * <p>This is necessary due to the "final" attribute of the Call class. In order to test the correct
41  * functioning of the BluetoothInCallService class, the final class must be put into a container
42  * that can be mocked correctly.
43  */
44 @VisibleForTesting
45 public class BluetoothCall {
46 
47     private Call mCall;
48     private UUID mCallId;
49 
50     // An index used to identify calls for CLCC (C* List Current Calls).
51     int mClccIndex = -1;
52 
getCall()53     public Call getCall() {
54         return mCall;
55     }
56 
isCallNull()57     public boolean isCallNull() {
58         return mCall == null;
59     }
60 
setCall(Call call)61     public void setCall(Call call) {
62         mCall = call;
63     }
64 
BluetoothCall(Call call)65     public BluetoothCall(Call call) {
66         mCall = call;
67         mCallId = UUID.randomUUID();
68     }
69 
BluetoothCall(Call call, UUID callId)70     public BluetoothCall(Call call, UUID callId) {
71         mCall = call;
72         mCallId = callId;
73     }
74 
getTbsCallId()75     public UUID getTbsCallId() {
76         return mCallId;
77     }
78 
setTbsCallId(UUID callId)79     public void setTbsCallId(UUID callId) {
80         mCallId = callId;
81     }
82 
getRemainingPostDialSequence()83     public String getRemainingPostDialSequence() {
84         return mCall.getRemainingPostDialSequence();
85     }
86 
answer(int videoState)87     public void answer(int videoState) {
88         mCall.answer(videoState);
89     }
90 
deflect(Uri address)91     public void deflect(Uri address) {
92         mCall.deflect(address);
93     }
94 
reject(boolean rejectWithMessage, String textMessage)95     public void reject(boolean rejectWithMessage, String textMessage) {
96         mCall.reject(rejectWithMessage, textMessage);
97     }
98 
disconnect()99     public void disconnect() {
100         mCall.disconnect();
101     }
102 
hold()103     public void hold() {
104         mCall.hold();
105     }
106 
unhold()107     public void unhold() {
108         mCall.unhold();
109     }
110 
enterBackgroundAudioProcessing()111     public void enterBackgroundAudioProcessing() {
112         mCall.enterBackgroundAudioProcessing();
113     }
114 
exitBackgroundAudioProcessing(boolean shouldRing)115     public void exitBackgroundAudioProcessing(boolean shouldRing) {
116         mCall.exitBackgroundAudioProcessing(shouldRing);
117     }
118 
playDtmfTone(char digit)119     public void playDtmfTone(char digit) {
120         mCall.playDtmfTone(digit);
121     }
122 
stopDtmfTone()123     public void stopDtmfTone() {
124         mCall.stopDtmfTone();
125     }
126 
postDialContinue(boolean proceed)127     public void postDialContinue(boolean proceed) {
128         mCall.postDialContinue(proceed);
129     }
130 
phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault)131     public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
132         mCall.phoneAccountSelected(accountHandle, setDefault);
133     }
134 
conference(BluetoothCall callToConferenceWith)135     public void conference(BluetoothCall callToConferenceWith) {
136         if (callToConferenceWith != null) {
137             mCall.conference(callToConferenceWith.getCall());
138         }
139     }
140 
splitFromConference()141     public void splitFromConference() {
142         mCall.splitFromConference();
143     }
144 
mergeConference()145     public void mergeConference() {
146         mCall.mergeConference();
147     }
148 
swapConference()149     public void swapConference() {
150         mCall.swapConference();
151     }
152 
pullExternalCall()153     public void pullExternalCall() {
154         mCall.pullExternalCall();
155     }
156 
sendCallEvent(String event, Bundle extras)157     public void sendCallEvent(String event, Bundle extras) {
158         mCall.sendCallEvent(event, extras);
159     }
160 
sendRttRequest()161     public void sendRttRequest() {
162         mCall.sendRttRequest();
163     }
164 
respondToRttRequest(int id, boolean accept)165     public void respondToRttRequest(int id, boolean accept) {
166         mCall.respondToRttRequest(id, accept);
167     }
168 
handoverTo(PhoneAccountHandle toHandle, int videoState, Bundle extras)169     public void handoverTo(PhoneAccountHandle toHandle, int videoState, Bundle extras) {
170         mCall.handoverTo(toHandle, videoState, extras);
171     }
172 
stopRtt()173     public void stopRtt() {
174         mCall.stopRtt();
175     }
176 
removeExtras(List<String> keys)177     public void removeExtras(List<String> keys) {
178         mCall.removeExtras(keys);
179     }
180 
removeExtras(String... keys)181     public void removeExtras(String... keys) {
182         mCall.removeExtras(keys);
183     }
184 
185     /** Returns the parent Call id. */
getParentId()186     public Integer getParentId() {
187         Call parent = mCall.getParent();
188         if (parent != null) {
189             return System.identityHashCode(parent);
190         }
191         return null;
192     }
193 
getChildrenIds()194     public List<Integer> getChildrenIds() {
195         return getIds(mCall.getChildren());
196     }
197 
getConferenceableCalls()198     public List<Integer> getConferenceableCalls() {
199         return getIds(mCall.getConferenceableCalls());
200     }
201 
getState()202     public int getState() {
203         return mCall.getState();
204     }
205 
getCannedTextResponses()206     public List<String> getCannedTextResponses() {
207         return mCall.getCannedTextResponses();
208     }
209 
getVideoCall()210     public InCallService.VideoCall getVideoCall() {
211         return mCall.getVideoCall();
212     }
213 
getDetails()214     public Call.Details getDetails() {
215         return mCall.getDetails();
216     }
217 
getRttCall()218     public Call.RttCall getRttCall() {
219         return mCall.getRttCall();
220     }
221 
isRttActive()222     public boolean isRttActive() {
223         return mCall.isRttActive();
224     }
225 
registerCallback(Call.Callback callback)226     public void registerCallback(Call.Callback callback) {
227         mCall.registerCallback(callback);
228     }
229 
registerCallback(Call.Callback callback, Handler handler)230     public void registerCallback(Call.Callback callback, Handler handler) {
231         mCall.registerCallback(callback, handler);
232     }
233 
unregisterCallback(Call.Callback callback)234     public void unregisterCallback(Call.Callback callback) {
235         mCall.unregisterCallback(callback);
236     }
237 
toString()238     public String toString() {
239         String string = mCall.toString();
240         return string == null ? "" : string;
241     }
242 
addListener(Call.Listener listener)243     public void addListener(Call.Listener listener) {
244         mCall.addListener(listener);
245     }
246 
removeListener(Call.Listener listener)247     public void removeListener(Call.Listener listener) {
248         mCall.removeListener(listener);
249     }
250 
getGenericConferenceActiveChildCallId()251     public Integer getGenericConferenceActiveChildCallId() {
252         return System.identityHashCode(mCall.getGenericConferenceActiveChildCall());
253     }
254 
getContactDisplayName()255     public String getContactDisplayName() {
256         return mCall.getDetails().getContactDisplayName();
257     }
258 
getAccountHandle()259     public PhoneAccountHandle getAccountHandle() {
260         return mCall.getDetails().getAccountHandle();
261     }
262 
getVideoState()263     public int getVideoState() {
264         return mCall.getDetails().getVideoState();
265     }
266 
getCallerDisplayName()267     public String getCallerDisplayName() {
268         return mCall.getDetails().getCallerDisplayName();
269     }
270 
271     @Override
equals(Object o)272     public boolean equals(Object o) {
273         if (o == null) {
274             return getCall() == null;
275         }
276         return o instanceof BluetoothCall && getCall() == ((BluetoothCall) o).getCall();
277     }
278 
279     // helper functions
isSilentRingingRequested()280     public boolean isSilentRingingRequested() {
281         return BluetoothCallShimImpl.newInstance()
282                 .isSilentRingingRequested(getDetails().getExtras());
283     }
284 
isConference()285     public boolean isConference() {
286         return getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE);
287     }
288 
can(int capability)289     public boolean can(int capability) {
290         return getDetails().can(capability);
291     }
292 
getHandle()293     public Uri getHandle() {
294         return getDetails().getHandle();
295     }
296 
getGatewayInfo()297     public GatewayInfo getGatewayInfo() {
298         return getDetails().getGatewayInfo();
299     }
300 
isIncoming()301     public boolean isIncoming() {
302         return getDetails().getCallDirection() == Call.Details.DIRECTION_INCOMING;
303     }
304 
isExternalCall()305     public boolean isExternalCall() {
306         return getDetails().hasProperty(Call.Details.PROPERTY_IS_EXTERNAL_CALL);
307     }
308 
isHighDefAudio()309     public boolean isHighDefAudio() {
310         return getDetails().hasProperty(Call.Details.PROPERTY_HIGH_DEF_AUDIO);
311     }
312 
getId()313     public Integer getId() {
314         return System.identityHashCode(mCall);
315     }
316 
wasConferencePreviouslyMerged()317     public boolean wasConferencePreviouslyMerged() {
318         return can(Call.Details.CAPABILITY_SWAP_CONFERENCE)
319                 && !can(Call.Details.CAPABILITY_MERGE_CONFERENCE);
320     }
321 
getDisconnectCause()322     public DisconnectCause getDisconnectCause() {
323         return getDetails().getDisconnectCause();
324     }
325 
326     /** Returns the list of ids of corresponding Call List. */
getIds(List<Call> calls)327     public static List<Integer> getIds(List<Call> calls) {
328         List<Integer> result = new ArrayList<>();
329         for (Call call : calls) {
330             if (call != null) {
331                 result.add(System.identityHashCode(call));
332             }
333         }
334         return result;
335     }
336 
hasProperty(int property)337     public boolean hasProperty(int property) {
338         return getDetails().hasProperty(property);
339     }
340 }
341