1 /*
2  * Copyright (C) 2016 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.internal.telephony.imsphone;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.content.Context;
21 import android.net.Uri;
22 import android.telecom.PhoneAccount;
23 import android.telephony.PhoneNumberUtils;
24 import android.telephony.ims.ImsExternalCallState;
25 
26 import com.android.internal.telephony.Call;
27 import com.android.internal.telephony.CallStateException;
28 import com.android.internal.telephony.Connection;
29 import com.android.internal.telephony.Phone;
30 import com.android.internal.telephony.PhoneConstants;
31 import com.android.internal.telephony.UUSInfo;
32 
33 import java.util.Collections;
34 import java.util.Set;
35 import java.util.concurrent.ConcurrentHashMap;
36 
37 /**
38  * Represents an IMS call external to the device.  This class is used to represent a call which
39  * takes places on a secondary device associated with this one.  Originates from a Dialog Event
40  * Package.
41  *
42  * Dialog event package information is received from the IMS framework via
43  * {@link ImsExternalCallState} instances.
44  *
45  * @hide
46  */
47 public class ImsExternalConnection extends Connection {
48 
49     private static final String CONFERENCE_PREFIX = "conf";
50     private final Context mContext;
51 
52     public interface Listener {
onPullExternalCall(ImsExternalConnection connection)53         void onPullExternalCall(ImsExternalConnection connection);
54     }
55 
56     /**
57      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
58      * load factor before resizing, 1 means we only expect a single thread to
59      * access the map so make only a single shard
60      */
61     private final Set<Listener> mListeners = Collections.newSetFromMap(
62             new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
63 
64     /**
65      * The unqiue dialog event package specified ID associated with this external connection.
66      */
67     private int mCallId;
68 
69     /**
70      * A backing call associated with this external connection.
71      */
72     private ImsExternalCall mCall;
73 
74     /**
75      * The original address as contained in the dialog event package.
76      */
77     private Uri mOriginalAddress;
78 
79     /**
80      * Determines if the call is pullable.
81      */
82     private boolean mIsPullable;
83 
ImsExternalConnection(Phone phone, int callId, Uri address, boolean isPullable)84     protected ImsExternalConnection(Phone phone, int callId, Uri address, boolean isPullable) {
85         super(phone.getPhoneType());
86         mContext = phone.getContext();
87         mCall = new ImsExternalCall(phone, this);
88         mCallId = callId;
89         setExternalConnectionAddress(address);
90         mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
91         mIsPullable = isPullable;
92 
93         rebuildCapabilities();
94         setActive();
95     }
96 
97     /**
98      * @return the unique ID of this connection from the dialog event package data.
99      */
getCallId()100     public int getCallId() {
101         return mCallId;
102     }
103 
104     @Override
getCall()105     public Call getCall() {
106         return mCall;
107     }
108 
109     @Override
getDisconnectTime()110     public long getDisconnectTime() {
111         return 0;
112     }
113 
114     @Override
getHoldDurationMillis()115     public long getHoldDurationMillis() {
116         return 0;
117     }
118 
119     @Override
getVendorDisconnectCause()120     public String getVendorDisconnectCause() {
121         return null;
122     }
123 
124     @Override
hangup()125     public void hangup() throws CallStateException {
126         // No-op - Hangup is not supported for external calls.
127     }
128 
129     @Override
deflect(String number)130     public void deflect(String number) throws CallStateException {
131         // Deflect is not supported for external calls.
132         throw new CallStateException ("Deflect is not supported for external calls");
133     }
134 
135     @Override
transfer(String number, boolean isConfirmationRequired)136     public void transfer(String number, boolean isConfirmationRequired) throws CallStateException {
137         // Transfer is not supported for external calls.
138         throw new CallStateException("Transfer is not supported for external calls");
139     }
140 
141     @Override
consultativeTransfer(Connection other)142     public void consultativeTransfer(Connection other) throws CallStateException {
143         // Transfer is not supported for external calls.
144         throw new CallStateException("Transfer is not supported for external calls");
145     }
146 
147     @Override
separate()148     public void separate() throws CallStateException {
149         // No-op - Separate is not supported for external calls.
150     }
151 
152     @Override
proceedAfterWaitChar()153     public void proceedAfterWaitChar() {
154         // No-op - not supported for external calls.
155     }
156 
157     @Override
proceedAfterWildChar(String str)158     public void proceedAfterWildChar(String str) {
159         // No-op - not supported for external calls.
160     }
161 
162     @Override
cancelPostDial()163     public void cancelPostDial() {
164         // No-op - not supported for external calls.
165     }
166 
167     @Override
getNumberPresentation()168     public int getNumberPresentation() {
169         return mNumberPresentation;
170     }
171 
172     @Override
getUUSInfo()173     public UUSInfo getUUSInfo() {
174         return null;
175     }
176 
177     @Override
getPreciseDisconnectCause()178     public int getPreciseDisconnectCause() {
179         return 0;
180     }
181 
182     @Override
isMultiparty()183     public boolean isMultiparty() {
184         return false;
185     }
186 
187     /**
188      * Called by a {@link android.telecom.Connection} to indicate that this call should be pulled
189      * to the local device.
190      *
191      * Informs all listeners, in this case {@link ImsExternalCallTracker}, of the request to pull
192      * the call.
193      */
194     @Override
pullExternalCall()195     public void pullExternalCall() {
196         for (Listener listener : mListeners) {
197             listener.onPullExternalCall(this);
198         }
199     }
200 
201     /**
202      * Sets this external call as active.
203      */
204     @UnsupportedAppUsage
setActive()205     public void setActive() {
206         if (mCall == null) {
207             return;
208         }
209         mCall.setActive();
210     }
211 
212     /**
213      * Sets this external call as terminated.
214      */
setTerminated()215     public void setTerminated() {
216         if (mCall == null) {
217             return;
218         }
219 
220         mCall.setTerminated();
221     }
222 
223     /**
224      * Changes whether the call can be pulled or not.
225      *
226      * @param isPullable {@code true} if the call can be pulled, {@code false} otherwise.
227      */
setIsPullable(boolean isPullable)228     public void setIsPullable(boolean isPullable) {
229         mIsPullable = isPullable;
230         rebuildCapabilities();
231     }
232 
233     /**
234      * Sets the address of this external connection.  Ensures that dialog event package SIP
235      * {@link Uri}s are converted to a regular telephone number.
236      *
237      * @param address The address from the dialog event package.
238      */
setExternalConnectionAddress(Uri address)239     public void setExternalConnectionAddress(Uri address) {
240         mOriginalAddress = address;
241 
242         if (PhoneAccount.SCHEME_SIP.equals(address.getScheme())) {
243             if (address.getSchemeSpecificPart().startsWith(CONFERENCE_PREFIX)) {
244                 mCnapName = mContext.getString(com.android.internal.R.string.conference_call);
245                 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
246                 mAddress = "";
247                 mNumberPresentation = PhoneConstants.PRESENTATION_RESTRICTED;
248                 return;
249             }
250         }
251         Uri telUri = PhoneNumberUtils.convertSipUriToTelUri(address);
252         mAddress = telUri.getSchemeSpecificPart();
253     }
254 
addListener(Listener listener)255     public void addListener(Listener listener) {
256         mListeners.add(listener);
257     }
258 
removeListener(Listener listener)259     public void removeListener(Listener listener) {
260         mListeners.remove(listener);
261     }
262 
263     /**
264      * Build a human representation of a connection instance, suitable for debugging.
265      * Don't log personal stuff unless in debug mode.
266      * @return a string representing the internal state of this connection.
267      */
toString()268     public String toString() {
269         StringBuilder str = new StringBuilder(128);
270         str.append("[ImsExternalConnection dialogCallId:");
271         str.append(mCallId);
272         str.append(" state:");
273         if (mCall.getState() == Call.State.ACTIVE) {
274             str.append("Active");
275         } else if (mCall.getState() == Call.State.DISCONNECTED) {
276             str.append("Disconnected");
277         }
278         str.append("]");
279         return str.toString();
280     }
281 
282     /**
283      * Rebuilds the connection capabilities.
284      */
285     @UnsupportedAppUsage
rebuildCapabilities()286     private void rebuildCapabilities() {
287         int capabilities = Capability.IS_EXTERNAL_CONNECTION;
288         if (mIsPullable) {
289             capabilities |= Capability.IS_PULLABLE;
290         }
291 
292         setConnectionCapabilities(capabilities);
293     }
294 }
295