Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fec523bd authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Add ImsExternalCallTracker and support for multiendpoint.

- Adding new ImsExternalCallTracker which is responsible for responding to
dialog event package information received via IMS framework.
- Added TelephonyTester intents so that I can inject fake dialog event
package data into the ImsExternalCallTracker.

Bug: 27458894
Change-Id: Icc7c4f9e25ad553107fc1cb9d5501ba45c5a1e45
parent a7f4d2d6
Loading
Loading
Loading
Loading
+13 −1
Original line number Diff line number Diff line
@@ -66,6 +66,18 @@ public abstract class Connection {
         * For an IMS call, indicates that the peer supports video.
         */
        public static final int SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 0x00000008;

        /**
         * Indicates that the connection is an external connection (e.g. an instance of the class
         * {@link com.android.internal.telephony.imsphone.ImsExternalConnection}.
         */
        public static final int IS_EXTERNAL_CONNECTION = 0x00000010;

        /**
         * Indicates that this external connection can be pulled from the remote device to the
         * local device.
         */
        public static final int IS_PULLABLE = 0x00000020;
    }

    /**
+5 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.ims.ImsManager;
import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
import com.android.internal.telephony.cdma.EriManager;
import com.android.internal.telephony.dataconnection.DcTracker;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
import com.android.internal.telephony.uicc.IccCardProxy;
@@ -115,6 +116,10 @@ public class TelephonyComponentFactory {
        return new ImsPhoneCallTracker(imsPhone);
    }

    public ImsExternalCallTracker makeImsExternalCallTracker(ImsPhone imsPhone) {
        return new ImsExternalCallTracker(imsPhone);
    }

    public CdmaSubscriptionSourceManager
    getCdmaSubscriptionSourceManagerInstance(Context context, CommandsInterface ci, Handler h,
                                             int what, Object obj) {
+60 −0
Original line number Diff line number Diff line
@@ -20,11 +20,15 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.telephony.Rlog;

import com.android.ims.ImsCall;
import com.android.ims.ImsCallProfile;
import com.android.ims.ImsConferenceState;
import com.android.ims.ImsExternalCallState;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCall;
import com.android.internal.telephony.test.TestConferenceEventPackageParser;
@@ -32,6 +36,8 @@ import com.android.internal.telephony.test.TestConferenceEventPackageParser;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;

/**
 * Telephony tester receives the following intents where {name} is the phone name
@@ -50,7 +56,22 @@ public class TelephonyTester {
     */
    private static final String ACTION_TEST_CONFERENCE_EVENT_PACKAGE =
            "com.android.internal.telephony.TestConferenceEventPackage";

    /**
     * Test-only intent used to send a test dialog event package to the IMS framework.
     */
    private static final String ACTION_TEST_DIALOG_EVENT_PACKAGE =
            "com.android.internal.telephony.TestDialogEventPackage";

    private static final String EXTRA_FILENAME = "filename";
    private static final String EXTRA_STARTPACKAGE = "startPackage";
    private static final String EXTRA_SENDPACKAGE = "sendPackage";
    private static final String EXTRA_DIALOGID = "dialogId";
    private static final String EXTRA_NUMBER = "number";
    private static final String EXTRA_STATE = "state";
    private static final String EXTRA_CANPULL = "canPull";

    private static List<ImsExternalCallState> mImsExternalCallStates = null;

    private Phone mPhone;

@@ -70,6 +91,9 @@ public class TelephonyTester {
            } else if (action.equals(ACTION_TEST_CONFERENCE_EVENT_PACKAGE)) {
                log("inject simulated conference event package");
                handleTestConferenceEventPackage(context, intent.getStringExtra(EXTRA_FILENAME));
            } else if (action.equals(ACTION_TEST_DIALOG_EVENT_PACKAGE)) {
                log("handle test dialog event package intent");
                handleTestDialogEventPackageIntent(intent);
            } else {
                if (DBG) log("onReceive: unknown action=" + action);
            }
@@ -91,6 +115,8 @@ public class TelephonyTester {
            if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
                log("register for intent action=" + ACTION_TEST_CONFERENCE_EVENT_PACKAGE);
                filter.addAction(ACTION_TEST_CONFERENCE_EVENT_PACKAGE);
                filter.addAction(ACTION_TEST_DIALOG_EVENT_PACKAGE);
                mImsExternalCallStates = new ArrayList<ImsExternalCallState>();
            }

            phone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone.getHandler());
@@ -148,4 +174,38 @@ public class TelephonyTester {

        imsCall.conferenceStateUpdated(imsConferenceState);
    }

    /**
     * Handles intents containing test dialog event package data.
     *
     * @param intent
     */
    private void handleTestDialogEventPackageIntent(Intent intent) {
        ImsPhone imsPhone = (ImsPhone) mPhone;
        if (imsPhone == null) {
            return;
        }
        ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
        if (externalCallTracker == null) {
            return;
        }

        if (intent.hasExtra(EXTRA_STARTPACKAGE)) {
            mImsExternalCallStates.clear();
        } else if (intent.hasExtra(EXTRA_SENDPACKAGE)) {
            externalCallTracker.refreshExternalCallState(mImsExternalCallStates);
            mImsExternalCallStates.clear();
        } else if (intent.hasExtra(EXTRA_DIALOGID)) {
            ImsExternalCallState state = new ImsExternalCallState(
                    intent.getIntExtra(EXTRA_DIALOGID, 0),
                    Uri.parse(intent.getStringExtra(EXTRA_NUMBER)),
                    intent.getBooleanExtra(EXTRA_CANPULL, true),
                    intent.getIntExtra(EXTRA_STATE,
                            ImsExternalCallState.CALL_STATE_CONFIRMED),
                    ImsCallProfile.CALL_TYPE_VOICE,
                    false /* isHeld */
                    );
            mImsExternalCallStates.add(state);
        }
    }
}
+72 −0
Original line number Diff line number Diff line
/*
 * 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 com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;

import java.util.List;

/**
 * Companion class for {@link ImsExternalConnection}; represents an external call which was
 * received via {@link com.android.ims.ImsExternalCallState} info.
 */
public class ImsExternalCall extends Call {

    private Phone mPhone;

    public ImsExternalCall(Phone phone, ImsExternalConnection connection) {
        mPhone = phone;
        mConnections.add(connection);
    }

    @Override
    public List<Connection> getConnections() {
        return mConnections;
    }

    @Override
    public Phone getPhone() {
        return mPhone;
    }

    @Override
    public boolean isMultiparty() {
        return false;
    }

    @Override
    public void hangup() throws CallStateException {

    }

    /**
     * Sets the call state to active.
     */
    public void setActive() {
        setState(State.ACTIVE);
    }

    /**
     * Sets the call state to terminated.
     */
    public void setTerminated() {
        setState(State.DISCONNECTED);
    }
}
+193 −0
Original line number Diff line number Diff line
/*
 * 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 com.android.ims.ImsExternalCallState;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;

import android.os.Bundle;
import android.telecom.PhoneAccountHandle;
import android.util.ArrayMap;
import android.util.Log;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Responsible for tracking external calls known to the system.
 */
public class ImsExternalCallTracker {

    public final static String TAG = "ImsExternalCallTracker";

    /**
     * Extra key used when informing telecom of a new external call using the
     * {@link android.telecom.TelecomManager#addNewUnknownCall(PhoneAccountHandle, Bundle)} API.
     * Used to ensure that when Telecom requests the {@link android.telecom.ConnectionService} to
     * create the connection for the unknown call that we can determine which
     * {@link ImsExternalConnection} in {@link #mExternalConnections} is the one being requested.
     */
    public final static String EXTRA_IMS_EXTERNAL_CALL_ID =
            "android.telephony.ImsExternalCallTracker.extra.EXTERNAL_CALL_ID";

    /**
     * Contains a list of the external connections known by the ImsPhoneCallTracker.  These are
     * connections which originated from a dialog event package and reside on another device.
     * Used in multi-endpoint (VoLTE for internet connected endpoints) scenarios.
     */
    private Map<Integer, ImsExternalConnection> mExternalConnections =
            new ArrayMap<Integer, ImsExternalConnection>();

    private ImsPhone mPhone;

    public ImsExternalCallTracker(ImsPhone phone) {
        mPhone = phone;
    }

    /**
     * Called when the IMS stack receives a new dialog event package.  Triggers the creation and
     * update of {@link ImsExternalConnection}s to represent the dialogs in the dialog event
     * package data.
     *
     * @param externalCallStates the {@link ImsExternalCallState} information for the dialog event
     *                           package.
     */
    public void refreshExternalCallState(List<ImsExternalCallState> externalCallStates) {
        Log.d(TAG, "refreshExternalCallState: depSize = " + externalCallStates.size());

        // Check to see if any call Ids are no longer present in the external call state.  If they
        // are, the calls are terminated and should be removed.
        Iterator<Map.Entry<Integer, ImsExternalConnection>> connectionIterator =
                mExternalConnections.entrySet().iterator();
        boolean wasCallRemoved = false;
        while (connectionIterator.hasNext()) {
            Map.Entry<Integer, ImsExternalConnection> entry = connectionIterator.next();
            int callId = entry.getKey().intValue();

            if (!containsCallId(externalCallStates, callId)) {
                ImsExternalConnection externalConnection = entry.getValue();
                externalConnection.setTerminated();
                connectionIterator.remove();
                wasCallRemoved = true;
            }
        }
        // If one or more calls were removed, trigger a notification that will cause the
        // TelephonyConnection instancse to refresh their state with Telecom.
        if (wasCallRemoved) {
            mPhone.notifyPreciseCallStateChanged();
        }

        // Check for new calls, and updates to existing ones.
        for (ImsExternalCallState callState : externalCallStates) {
            if (!mExternalConnections.containsKey(callState.getCallId())) {
                Log.d(TAG, "refreshExternalCallState: got = " + callState);
                // If there is a new entry and it is already terminated, don't bother adding it to
                // telecom.
                if (callState.getCallState() != ImsExternalCallState.CALL_STATE_CONFIRMED) {
                    continue;
                }
                createExternalConnection(callState);
            } else{
                updateExistingConnection(mExternalConnections.get(callState.getCallId()),
                        callState);
            }
        }
    }

    /**
     * Finds an external connection given a call Id.
     *
     * @param callId The call Id.
     * @return The {@link Connection}, or {@code null} if no match found.
     */
    public Connection getConnectionById(int callId) {
        return mExternalConnections.get(callId);
    }

    /**
     * Given an {@link ImsExternalCallState} instance obtained from a dialog event package,
     * creates a new instance of {@link ImsExternalConnection} to represent the connection, and
     * initiates the addition of the new call to Telecom as an unknown call.
     *
     * @param state External call state from a dialog event package.
     */
    private void createExternalConnection(ImsExternalCallState state) {
        Log.i(TAG, "createExternalConnection");

        ImsExternalConnection connection = new ImsExternalConnection(mPhone,
                state.getCallId(), /* Dialog event package call id */
                state.getAddress().getSchemeSpecificPart() /* phone number */,
                state.isCallPullable());

        // Add to list of tracked connections.
        mExternalConnections.put(connection.getCallId(), connection);

        // Note: The notification of unknown connection is ultimately handled by
        // PstnIncomingCallNotifier#addNewUnknownCall.  That method will ensure that an extra is set
        // containing the ImsExternalConnection#mCallId so that we have a means of reconciling which
        // unknown call was added.
        mPhone.notifyUnknownConnection(connection);
    }

    /**
     * Given an existing {@link ImsExternalConnection}, applies any changes found found in a
     * {@link ImsExternalCallState} instance received from a dialog event package to the connection.
     *
     * @param connection The connection to apply changes to.
     * @param state The new dialog state for the connection.
     */
    private void updateExistingConnection(ImsExternalConnection connection,
            ImsExternalCallState state) {
        Call.State existingState = connection.getState();
        Call.State newState = state.getCallState() == ImsExternalCallState.CALL_STATE_CONFIRMED ?
                Call.State.ACTIVE : Call.State.DISCONNECTED;

        if (existingState != newState) {
            if (newState == Call.State.ACTIVE) {
                connection.setActive();
            } else {
                connection.setTerminated();
                mExternalConnections.remove(connection);
                mPhone.notifyPreciseCallStateChanged();
            }
        }

        connection.setIsPullable(state.isCallPullable());
    }

    /**
     * Determines if a list of call states obtained from a dialog event package contacts an existing
     * call Id.
     *
     * @param externalCallStates The dialog event package state information.
     * @param callId The call Id.
     * @return {@code true} if the state information contains the call Id, {@code false} otherwise.
     */
    private boolean containsCallId(List<ImsExternalCallState> externalCallStates, int callId) {
        for (ImsExternalCallState state : externalCallStates) {
            if (state.getCallId() == callId) {
                return true;
            }
        }

        return false;
    }
}
Loading