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