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 com.android.internal.R;
20 import com.android.internal.telephony.Call;
21 import com.android.internal.telephony.CallStateException;
22 import com.android.internal.telephony.Connection;
23 import com.android.internal.telephony.Phone;
24 import com.android.internal.telephony.PhoneConstants;
25 import com.android.internal.telephony.UUSInfo;
26 
27 import android.content.Context;
28 import android.net.Uri;
29 import android.telecom.PhoneAccount;
30 import android.telephony.PhoneNumberUtils;
31 import android.telephony.ims.ImsExternalCallState;
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
separate()136     public void separate() throws CallStateException {
137         // No-op - Separate is not supported for external calls.
138     }
139 
140     @Override
proceedAfterWaitChar()141     public void proceedAfterWaitChar() {
142         // No-op - not supported for external calls.
143     }
144 
145     @Override
proceedAfterWildChar(String str)146     public void proceedAfterWildChar(String str) {
147         // No-op - not supported for external calls.
148     }
149 
150     @Override
cancelPostDial()151     public void cancelPostDial() {
152         // No-op - not supported for external calls.
153     }
154 
155     @Override
getNumberPresentation()156     public int getNumberPresentation() {
157         return mNumberPresentation;
158     }
159 
160     @Override
getUUSInfo()161     public UUSInfo getUUSInfo() {
162         return null;
163     }
164 
165     @Override
getPreciseDisconnectCause()166     public int getPreciseDisconnectCause() {
167         return 0;
168     }
169 
170     @Override
isMultiparty()171     public boolean isMultiparty() {
172         return false;
173     }
174 
175     /**
176      * Called by a {@link android.telecom.Connection} to indicate that this call should be pulled
177      * to the local device.
178      *
179      * Informs all listeners, in this case {@link ImsExternalCallTracker}, of the request to pull
180      * the call.
181      */
182     @Override
pullExternalCall()183     public void pullExternalCall() {
184         for (Listener listener : mListeners) {
185             listener.onPullExternalCall(this);
186         }
187     }
188 
189     /**
190      * Sets this external call as active.
191      */
setActive()192     public void setActive() {
193         if (mCall == null) {
194             return;
195         }
196         mCall.setActive();
197     }
198 
199     /**
200      * Sets this external call as terminated.
201      */
setTerminated()202     public void setTerminated() {
203         if (mCall == null) {
204             return;
205         }
206 
207         mCall.setTerminated();
208     }
209 
210     /**
211      * Changes whether the call can be pulled or not.
212      *
213      * @param isPullable {@code true} if the call can be pulled, {@code false} otherwise.
214      */
setIsPullable(boolean isPullable)215     public void setIsPullable(boolean isPullable) {
216         mIsPullable = isPullable;
217         rebuildCapabilities();
218     }
219 
220     /**
221      * Sets the address of this external connection.  Ensures that dialog event package SIP
222      * {@link Uri}s are converted to a regular telephone number.
223      *
224      * @param address The address from the dialog event package.
225      */
setExternalConnectionAddress(Uri address)226     public void setExternalConnectionAddress(Uri address) {
227         mOriginalAddress = address;
228 
229         if (PhoneAccount.SCHEME_SIP.equals(address.getScheme())) {
230             if (address.getSchemeSpecificPart().startsWith(CONFERENCE_PREFIX)) {
231                 mCnapName = mContext.getString(com.android.internal.R.string.conference_call);
232                 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
233                 mAddress = "";
234                 mNumberPresentation = PhoneConstants.PRESENTATION_RESTRICTED;
235                 return;
236             }
237         }
238         Uri telUri = PhoneNumberUtils.convertSipUriToTelUri(address);
239         mAddress = telUri.getSchemeSpecificPart();
240     }
241 
addListener(Listener listener)242     public void addListener(Listener listener) {
243         mListeners.add(listener);
244     }
245 
removeListener(Listener listener)246     public void removeListener(Listener listener) {
247         mListeners.remove(listener);
248     }
249 
250     /**
251      * Build a human representation of a connection instance, suitable for debugging.
252      * Don't log personal stuff unless in debug mode.
253      * @return a string representing the internal state of this connection.
254      */
toString()255     public String toString() {
256         StringBuilder str = new StringBuilder(128);
257         str.append("[ImsExternalConnection dialogCallId:");
258         str.append(mCallId);
259         str.append(" state:");
260         if (mCall.getState() == Call.State.ACTIVE) {
261             str.append("Active");
262         } else if (mCall.getState() == Call.State.DISCONNECTED) {
263             str.append("Disconnected");
264         }
265         str.append("]");
266         return str.toString();
267     }
268 
269     /**
270      * Rebuilds the connection capabilities.
271      */
rebuildCapabilities()272     private void rebuildCapabilities() {
273         int capabilities = Capability.IS_EXTERNAL_CONNECTION;
274         if (mIsPullable) {
275             capabilities |= Capability.IS_PULLABLE;
276         }
277 
278         setConnectionCapabilities(capabilities);
279     }
280 }
281