blob: 59ac1f61d1b41ab0bfd6770afc98cefe5ccdd1e4 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.internal.telephony.imsphone;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.telecom.PhoneAccount;
import android.telephony.PhoneNumberUtils;
import android.telephony.ims.ImsExternalCallState;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.UUSInfo;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Represents an IMS call external to the device. This class is used to represent a call which
* takes places on a secondary device associated with this one. Originates from a Dialog Event
* Package.
*
* Dialog event package information is received from the IMS framework via
* {@link ImsExternalCallState} instances.
*
* @hide
*/
public class ImsExternalConnection extends Connection {
private static final String CONFERENCE_PREFIX = "conf";
private final Context mContext;
public interface Listener {
void onPullExternalCall(ImsExternalConnection connection);
}
/**
* ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
* load factor before resizing, 1 means we only expect a single thread to
* access the map so make only a single shard
*/
private final Set<Listener> mListeners = Collections.newSetFromMap(
new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
/**
* The unqiue dialog event package specified ID associated with this external connection.
*/
private int mCallId;
/**
* A backing call associated with this external connection.
*/
private ImsExternalCall mCall;
/**
* The original address as contained in the dialog event package.
*/
private Uri mOriginalAddress;
/**
* Determines if the call is pullable.
*/
private boolean mIsPullable;
protected ImsExternalConnection(Phone phone, int callId, Uri address, boolean isPullable) {
super(phone.getPhoneType());
mContext = phone.getContext();
mCall = new ImsExternalCall(phone, this);
mCallId = callId;
setExternalConnectionAddress(address);
mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
mIsPullable = isPullable;
rebuildCapabilities();
setActive();
}
/**
* @return the unique ID of this connection from the dialog event package data.
*/
public int getCallId() {
return mCallId;
}
@Override
public Call getCall() {
return mCall;
}
@Override
public long getDisconnectTime() {
return 0;
}
@Override
public long getHoldDurationMillis() {
return 0;
}
@Override
public String getVendorDisconnectCause() {
return null;
}
@Override
public void hangup() throws CallStateException {
// No-op - Hangup is not supported for external calls.
}
@Override
public void deflect(String number) throws CallStateException {
// Deflect is not supported for external calls.
throw new CallStateException ("Deflect is not supported for external calls");
}
@Override
public void transfer(String number, boolean isConfirmationRequired) throws CallStateException {
// Transfer is not supported for external calls.
throw new CallStateException("Transfer is not supported for external calls");
}
@Override
public void consultativeTransfer(Connection other) throws CallStateException {
// Transfer is not supported for external calls.
throw new CallStateException("Transfer is not supported for external calls");
}
@Override
public void separate() throws CallStateException {
// No-op - Separate is not supported for external calls.
}
@Override
public void proceedAfterWaitChar() {
// No-op - not supported for external calls.
}
@Override
public void proceedAfterWildChar(String str) {
// No-op - not supported for external calls.
}
@Override
public void cancelPostDial() {
// No-op - not supported for external calls.
}
@Override
public int getNumberPresentation() {
return mNumberPresentation;
}
@Override
public UUSInfo getUUSInfo() {
return null;
}
@Override
public int getPreciseDisconnectCause() {
return 0;
}
@Override
public boolean isMultiparty() {
return false;
}
/**
* Called by a {@link android.telecom.Connection} to indicate that this call should be pulled
* to the local device.
*
* Informs all listeners, in this case {@link ImsExternalCallTracker}, of the request to pull
* the call.
*/
@Override
public void pullExternalCall() {
for (Listener listener : mListeners) {
listener.onPullExternalCall(this);
}
}
/**
* Sets this external call as active.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void setActive() {
if (mCall == null) {
return;
}
mCall.setActive();
}
/**
* Sets this external call as terminated.
*/
public void setTerminated() {
if (mCall == null) {
return;
}
mCall.setTerminated();
}
/**
* Changes whether the call can be pulled or not.
*
* @param isPullable {@code true} if the call can be pulled, {@code false} otherwise.
*/
public void setIsPullable(boolean isPullable) {
mIsPullable = isPullable;
rebuildCapabilities();
}
/**
* Sets the address of this external connection. Ensures that dialog event package SIP
* {@link Uri}s are converted to a regular telephone number.
*
* @param address The address from the dialog event package.
*/
public void setExternalConnectionAddress(Uri address) {
mOriginalAddress = address;
if (PhoneAccount.SCHEME_SIP.equals(address.getScheme())) {
if (address.getSchemeSpecificPart().startsWith(CONFERENCE_PREFIX)) {
mCnapName = mContext.getString(com.android.internal.R.string.conference_call);
mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
mAddress = "";
mNumberPresentation = PhoneConstants.PRESENTATION_RESTRICTED;
return;
}
}
Uri telUri = PhoneNumberUtils.convertSipUriToTelUri(address);
mAddress = telUri.getSchemeSpecificPart();
}
public void addListener(Listener listener) {
mListeners.add(listener);
}
public void removeListener(Listener listener) {
mListeners.remove(listener);
}
/**
* Build a human representation of a connection instance, suitable for debugging.
* Don't log personal stuff unless in debug mode.
* @return a string representing the internal state of this connection.
*/
public String toString() {
StringBuilder str = new StringBuilder(128);
str.append("[ImsExternalConnection dialogCallId:");
str.append(mCallId);
str.append(" state:");
if (mCall.getState() == Call.State.ACTIVE) {
str.append("Active");
} else if (mCall.getState() == Call.State.DISCONNECTED) {
str.append("Disconnected");
}
str.append("]");
return str.toString();
}
/**
* Rebuilds the connection capabilities.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void rebuildCapabilities() {
int capabilities = Capability.IS_EXTERNAL_CONNECTION;
if (mIsPullable) {
capabilities |= Capability.IS_PULLABLE;
}
setConnectionCapabilities(capabilities);
}
}