/* * Copyright (C) 2006 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; import android.net.Uri; import android.os.SystemClock; import android.telecom.ConferenceParticipant; import android.telephony.Rlog; import android.util.Log; import java.lang.Override; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; /** * {@hide} */ public abstract class Connection { public interface PostDialListener { void onPostDialWait(); void onPostDialChar(char c); } /** * Listener interface for events related to the connection which should be reported to the * {@link android.telecom.Connection}. */ public interface Listener { public void onVideoStateChanged(int videoState); public void onLocalVideoCapabilityChanged(boolean capable); public void onRemoteVideoCapabilityChanged(boolean capable); public void onWifiChanged(boolean isWifi); public void onVideoProviderChanged( android.telecom.Connection.VideoProvider videoProvider); public void onAudioQualityChanged(int audioQuality); public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants); public void onCallSubstateChanged(int callSubstate); public void onMultipartyStateChanged(boolean isMultiParty); public void onConferenceMergedFailed(); } /** * Base listener implementation. */ public abstract static class ListenerBase implements Listener { @Override public void onVideoStateChanged(int videoState) {} @Override public void onLocalVideoCapabilityChanged(boolean capable) {} @Override public void onRemoteVideoCapabilityChanged(boolean capable) {} @Override public void onWifiChanged(boolean isWifi) {} @Override public void onVideoProviderChanged( android.telecom.Connection.VideoProvider videoProvider) {} @Override public void onAudioQualityChanged(int audioQuality) {} @Override public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {} @Override public void onCallSubstateChanged(int callSubstate) {} @Override public void onMultipartyStateChanged(boolean isMultiParty) {} @Override public void onConferenceMergedFailed() {} } public static final int AUDIO_QUALITY_STANDARD = 1; public static final int AUDIO_QUALITY_HIGH_DEFINITION = 2; //Caller Name Display protected String mCnapName; protected int mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; protected String mAddress; // MAY BE NULL!!! protected String mDialString; // outgoing calls only protected int mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; protected boolean mIsIncoming; /* * These time/timespan values are based on System.currentTimeMillis(), * i.e., "wall clock" time. */ protected long mCreateTime; protected long mConnectTime; /* * These time/timespan values are based on SystemClock.elapsedRealTime(), * i.e., time since boot. They are appropriate for comparison and * calculating deltas. */ protected long mConnectTimeReal; protected long mDuration; protected long mHoldingStartTime; // The time when the Connection last transitioned // into HOLDING protected Connection mOrigConnection; private List<PostDialListener> mPostDialListeners = new ArrayList<>(); public Set<Listener> mListeners = new CopyOnWriteArraySet<>(); protected boolean mNumberConverted = false; protected String mConvertedNumber; private static String LOG_TAG = "Connection"; Object mUserData; private int mVideoState; private boolean mLocalVideoCapable; private boolean mRemoteVideoCapable; private boolean mIsWifi; private int mAudioQuality; private int mCallSubstate; private android.telecom.Connection.VideoProvider mVideoProvider; public Call.State mPreHandoverState = Call.State.IDLE; /* Instance Methods */ /** * Gets address (e.g. phone number) associated with connection. * TODO: distinguish reasons for unavailability * * @return address or null if unavailable */ public String getAddress() { return mAddress; } /** * Gets CNAP name associated with connection. * @return cnap name or null if unavailable */ public String getCnapName() { return mCnapName; } /** * Get original dial string. * @return original dial string or null if unavailable */ public String getOrigDialString(){ return null; } /** * Gets CNAP presentation associated with connection. * @return cnap name or null if unavailable */ public int getCnapNamePresentation() { return mCnapNamePresentation; } /** * @return Call that owns this Connection, or null if none */ public abstract Call getCall(); /** * Connection create time in currentTimeMillis() format * Basically, set when object is created. * Effectively, when an incoming call starts ringing or an * outgoing call starts dialing */ public long getCreateTime() { return mCreateTime; } /** * Connection connect time in currentTimeMillis() format. * For outgoing calls: Begins at (DIALING|ALERTING) -> ACTIVE transition. * For incoming calls: Begins at (INCOMING|WAITING) -> ACTIVE transition. * Returns 0 before then. */ public long getConnectTime() { return mConnectTime; } /** * Sets the Connection connect time in currentTimeMillis() format. * * @param connectTime the new connect time. */ public void setConnectTime(long connectTime) { mConnectTime = connectTime; } /** * Connection connect time in elapsedRealtime() format. * For outgoing calls: Begins at (DIALING|ALERTING) -> ACTIVE transition. * For incoming calls: Begins at (INCOMING|WAITING) -> ACTIVE transition. * Returns 0 before then. */ public long getConnectTimeReal() { return mConnectTimeReal; } /** * Disconnect time in currentTimeMillis() format. * The time when this Connection makes a transition into ENDED or FAIL. * Returns 0 before then. */ public abstract long getDisconnectTime(); /** * Returns the number of milliseconds the call has been connected, * or 0 if the call has never connected. * If the call is still connected, then returns the elapsed * time since connect. */ public long getDurationMillis() { if (mConnectTimeReal == 0) { return 0; } else if (mDuration == 0) { return SystemClock.elapsedRealtime() - mConnectTimeReal; } else { return mDuration; } } /** * The time when this Connection last transitioned into HOLDING * in elapsedRealtime() format. * Returns 0, if it has never made a transition into HOLDING. */ public long getHoldingStartTime() { return mHoldingStartTime; } /** * If this connection is HOLDING, return the number of milliseconds * that it has been on hold for (approximately). * If this connection is in any other state, return 0. */ public abstract long getHoldDurationMillis(); /** * Returns call disconnect cause. Values are defined in * {@link android.telephony.DisconnectCause}. If the call is not yet * disconnected, NOT_DISCONNECTED is returned. */ public abstract int getDisconnectCause(); /** * Returns a string disconnect cause which is from vendor. * Vendors may use this string to explain the underline causes of failed calls. * There is no guarantee that it is non-null nor it'll have meaningful stable values. * Only use it when getDisconnectCause() returns a value that is not specific enough, like * ERROR_UNSPECIFIED. */ public abstract String getVendorDisconnectCause(); /** * Returns true of this connection originated elsewhere * ("MT" or mobile terminated; another party called this terminal) * or false if this call originated here (MO or mobile originated). */ public boolean isIncoming() { return mIsIncoming; } /** * If this Connection is connected, then it is associated with * a Call. * * Returns getCall().getState() or Call.State.IDLE if not * connected */ public Call.State getState() { Call c; c = getCall(); if (c == null) { return Call.State.IDLE; } else { return c.getState(); } } /** * If this connection went through handover return the state of the * call that contained this connection before handover. */ public Call.State getStateBeforeHandover() { return mPreHandoverState; } /** * Get the details of conference participants. Expected to be * overwritten by the Connection subclasses. */ public List<ConferenceParticipant> getConferenceParticipants() { Call c; c = getCall(); if (c == null) { return null; } else { return c.getConferenceParticipants(); } } /** * isAlive() * * @return true if the connection isn't disconnected * (could be active, holding, ringing, dialing, etc) */ public boolean isAlive() { return getState().isAlive(); } /** * Returns true if Connection is connected and is INCOMING or WAITING */ public boolean isRinging() { return getState().isRinging(); } /** * * @return the userdata set in setUserData() */ public Object getUserData() { return mUserData; } /** * * @param userdata user can store an any userdata in the Connection object. */ public void setUserData(Object userdata) { mUserData = userdata; } /** * Hangup individual Connection */ public abstract void hangup() throws CallStateException; /** * Separate this call from its owner Call and assigns it to a new Call * (eg if it is currently part of a Conference call * TODO: Throw exception? Does GSM require error display on failure here? */ public abstract void separate() throws CallStateException; public enum PostDialState { NOT_STARTED, /* The post dial string playback hasn't been started, or this call is not yet connected, or this is an incoming call */ STARTED, /* The post dial string playback has begun */ WAIT, /* The post dial string playback is waiting for a call to proceedAfterWaitChar() */ WILD, /* The post dial string playback is waiting for a call to proceedAfterWildChar() */ COMPLETE, /* The post dial string playback is complete */ CANCELLED, /* The post dial string playback was cancelled with cancelPostDial() */ PAUSE /* The post dial string playback is pausing for a call to processNextPostDialChar*/ } public void clearUserData(){ mUserData = null; } public final void addPostDialListener(PostDialListener listener) { if (!mPostDialListeners.contains(listener)) { mPostDialListeners.add(listener); } } public final void removePostDialListener(PostDialListener listener) { mPostDialListeners.remove(listener); } protected final void clearPostDialListeners() { mPostDialListeners.clear(); } protected final void notifyPostDialListeners() { if (getPostDialState() == PostDialState.WAIT) { for (PostDialListener listener : new ArrayList<>(mPostDialListeners)) { listener.onPostDialWait(); } } } protected final void notifyPostDialListenersNextChar(char c) { for (PostDialListener listener : new ArrayList<>(mPostDialListeners)) { listener.onPostDialChar(c); } } public abstract PostDialState getPostDialState(); /** * Returns the portion of the post dial string that has not * yet been dialed, or "" if none */ public abstract String getRemainingPostDialString(); /** * See Phone.setOnPostDialWaitCharacter() */ public abstract void proceedAfterWaitChar(); /** * See Phone.setOnPostDialWildCharacter() */ public abstract void proceedAfterWildChar(String str); /** * Cancel any post */ public abstract void cancelPostDial(); /** * Returns the caller id presentation type for incoming and waiting calls * @return one of PRESENTATION_* */ public abstract int getNumberPresentation(); /** * Returns the User to User Signaling (UUS) information associated with * incoming and waiting calls * @return UUSInfo containing the UUS userdata. */ public abstract UUSInfo getUUSInfo(); /** * Returns the CallFail reason provided by the RIL with the result of * RIL_REQUEST_LAST_CALL_FAIL_CAUSE */ public abstract int getPreciseDisconnectCause(); /** * Returns the original Connection instance associated with * this Connection */ public Connection getOrigConnection() { return mOrigConnection; } /** * Returns whether the original ImsPhoneConnection was a member * of a conference call * @return valid only when getOrigConnection() is not null */ public abstract boolean isMultiparty(); public void migrateFrom(Connection c) { if (c == null) return; mListeners = c.mListeners; mAddress = c.getAddress(); mNumberPresentation = c.getNumberPresentation(); mDialString = c.getOrigDialString(); mCnapName = c.getCnapName(); mCnapNamePresentation = c.getCnapNamePresentation(); mIsIncoming = c.isIncoming(); mCreateTime = c.getCreateTime(); mConnectTime = c.getConnectTime(); mConnectTimeReal = c.getConnectTimeReal(); mHoldingStartTime = c.getHoldingStartTime(); mOrigConnection = c.getOrigConnection(); } /** * Assign a listener to be notified of state changes. * * @param listener A listener. */ public final void addListener(Listener listener) { mListeners.add(listener); } /** * Removes a listener. * * @param listener A listener. */ public final void removeListener(Listener listener) { mListeners.remove(listener); } /** * Returns the current video state of the connection. * * @return The video state of the connection. */ public int getVideoState() { return mVideoState; } /** * Returns the local video capability state for the connection. * * @return {@code True} if the connection has local video capabilities. */ public boolean isLocalVideoCapable() { return mLocalVideoCapable; } /** * Returns the remote video capability state for the connection. * * @return {@code True} if the connection has remote video capabilities. */ public boolean isRemoteVideoCapable() { return mRemoteVideoCapable; } /** * Returns whether the connection is using a wifi network. * * @return {@code True} if the connection is using a wifi network. */ public boolean isWifi() { return mIsWifi; } /** * Returns the {@link android.telecom.Connection.VideoProvider} for the connection. * * @return The {@link android.telecom.Connection.VideoProvider}. */ public android.telecom.Connection.VideoProvider getVideoProvider() { return mVideoProvider; } /** * Returns the audio-quality for the connection. * * @return The audio quality for the connection. */ public int getAudioQuality() { return mAudioQuality; } /** * Returns the current call substate of the connection. * * @return The call substate of the connection. */ public int getCallSubstate() { return mCallSubstate; } /** * Sets the videoState for the current connection and reports the changes to all listeners. * Valid video states are defined in {@link android.telecom.VideoProfile}. * * @return The video state. */ public void setVideoState(int videoState) { mVideoState = videoState; for (Listener l : mListeners) { l.onVideoStateChanged(mVideoState); } } /** * Sets whether video capability is present locally. * * @param capable {@code True} if video capable. */ public void setLocalVideoCapable(boolean capable) { mLocalVideoCapable = capable; for (Listener l : mListeners) { l.onLocalVideoCapabilityChanged(mLocalVideoCapable); } } /** * Sets whether video capability is present remotely. * * @param capable {@code True} if video capable. */ public void setRemoteVideoCapable(boolean capable) { mRemoteVideoCapable = capable; for (Listener l : mListeners) { l.onRemoteVideoCapabilityChanged(mRemoteVideoCapable); } } /** * Sets whether a wifi network is used for the connection. * * @param isWifi {@code True} if wifi is being used. */ public void setWifi(boolean isWifi) { mIsWifi = isWifi; for (Listener l : mListeners) { l.onWifiChanged(mIsWifi); } } /** * Set the audio quality for the connection. * * @param audioQuality The audio quality. */ public void setAudioQuality(int audioQuality) { mAudioQuality = audioQuality; for (Listener l : mListeners) { l.onAudioQualityChanged(mAudioQuality); } } /** * Sets the call substate for the current connection and reports the changes to all listeners. * Valid call substates are defined in {@link android.telecom.Connection}. * * @return The call substate. */ public void setCallSubstate(int callSubstate) { mCallSubstate = callSubstate; for (Listener l : mListeners) { l.onCallSubstateChanged(mCallSubstate); } } /** * Sets the {@link android.telecom.Connection.VideoProvider} for the connection. * * @param videoProvider The video call provider. */ public void setVideoProvider(android.telecom.Connection.VideoProvider videoProvider) { mVideoProvider = videoProvider; for (Listener l : mListeners) { l.onVideoProviderChanged(mVideoProvider); } } public void setConverted(String oriNumber) { mNumberConverted = true; mConvertedNumber = mAddress; mAddress = oriNumber; mDialString = oriNumber; } /** * Notifies listeners of a change to conference participant(s). * * @param conferenceParticipants The participant(s). */ public void updateConferenceParticipants(List<ConferenceParticipant> conferenceParticipants) { for (Listener l : mListeners) { l.onConferenceParticipantsChanged(conferenceParticipants); } } /** * Notifies listeners of a change to the multiparty state of the connection. * * @param isMultiparty The participant(s). */ public void updateMultipartyState(boolean isMultiparty) { for (Listener l : mListeners) { l.onMultipartyStateChanged(isMultiparty); } } /** * Notifies listeners of a failure in merging this connection with the background connection. */ public void onConferenceMergeFailed() { for (Listener l : mListeners) { l.onConferenceMergedFailed(); } } /** * Notifies this Connection of a request to disconnect a participant of the conference managed * by the connection. * * @param endpoint the {@link Uri} of the participant to disconnect. */ public void onDisconnectConferenceParticipant(Uri endpoint) { } /** * 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); if (Rlog.isLoggable(LOG_TAG, Log.DEBUG)) { str.append("addr: " + getAddress()) .append(" pres.: " + getNumberPresentation()) .append(" dial: " + getOrigDialString()) .append(" postdial: " + getRemainingPostDialString()) .append(" cnap name: " + getCnapName()) .append("(" + getCnapNamePresentation() + ")"); } str.append(" incoming: " + isIncoming()) .append(" state: " + getState()) .append(" post dial state: " + getPostDialState()); return str.toString(); } }