diff options
Diffstat (limited to 'telecomm')
53 files changed, 11461 insertions, 0 deletions
diff --git a/telecomm/java/android/telecom/AudioState.aidl b/telecomm/java/android/telecom/AudioState.aidl new file mode 100644 index 0000000..b36e238 --- /dev/null +++ b/telecomm/java/android/telecom/AudioState.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 2014, 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 android.telecom; + +/** + * {@hide} + */ +parcelable AudioState; diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java new file mode 100644 index 0000000..d0e2860 --- /dev/null +++ b/telecomm/java/android/telecom/AudioState.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Locale; + +/** + * Encapsulates all audio states during a call. + */ +public final class AudioState implements Parcelable { + /** Direct the audio stream through the device's earpiece. */ + public static final int ROUTE_EARPIECE = 0x00000001; + + /** Direct the audio stream through Bluetooth. */ + public static final int ROUTE_BLUETOOTH = 0x00000002; + + /** Direct the audio stream through a wired headset. */ + public static final int ROUTE_WIRED_HEADSET = 0x00000004; + + /** Direct the audio stream through the device's speakerphone. */ + public static final int ROUTE_SPEAKER = 0x00000008; + + /** + * Direct the audio stream through the device's earpiece or wired headset if one is + * connected. + */ + public static final int ROUTE_WIRED_OR_EARPIECE = ROUTE_EARPIECE | ROUTE_WIRED_HEADSET; + + /** Bit mask of all possible audio routes. + * + * @hide + */ + public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET | + ROUTE_SPEAKER; + + /** True if the call is muted, false otherwise. */ + public final boolean isMuted; + + /** The route to use for the audio stream. */ + public final int route; + + /** Bit vector of all routes supported by this call. */ + public final int supportedRouteMask; + + public AudioState(boolean isMuted, int route, int supportedRouteMask) { + this.isMuted = isMuted; + this.route = route; + this.supportedRouteMask = supportedRouteMask; + } + + public AudioState(AudioState state) { + isMuted = state.isMuted; + route = state.route; + supportedRouteMask = state.supportedRouteMask; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof AudioState)) { + return false; + } + AudioState state = (AudioState) obj; + return isMuted == state.isMuted && route == state.route && + supportedRouteMask == state.supportedRouteMask; + } + + @Override + public String toString() { + return String.format(Locale.US, + "[AudioState isMuted: %b, route; %s, supportedRouteMask: %s]", + isMuted, audioRouteToString(route), audioRouteToString(supportedRouteMask)); + } + + /** @hide */ + public static String audioRouteToString(int route) { + if (route == 0 || (route & ~ROUTE_ALL) != 0x0) { + return "UNKNOWN"; + } + + StringBuffer buffer = new StringBuffer(); + if ((route & ROUTE_EARPIECE) == ROUTE_EARPIECE) { + listAppend(buffer, "EARPIECE"); + } + if ((route & ROUTE_BLUETOOTH) == ROUTE_BLUETOOTH) { + listAppend(buffer, "BLUETOOTH"); + } + if ((route & ROUTE_WIRED_HEADSET) == ROUTE_WIRED_HEADSET) { + listAppend(buffer, "WIRED_HEADSET"); + } + if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) { + listAppend(buffer, "SPEAKER"); + } + + return buffer.toString(); + } + + private static void listAppend(StringBuffer buffer, String str) { + if (buffer.length() > 0) { + buffer.append(", "); + } + buffer.append(str); + } + + /** + * Responsible for creating AudioState objects for deserialized Parcels. + */ + public static final Parcelable.Creator<AudioState> CREATOR = + new Parcelable.Creator<AudioState> () { + + @Override + public AudioState createFromParcel(Parcel source) { + boolean isMuted = source.readByte() == 0 ? false : true; + int route = source.readInt(); + int supportedRouteMask = source.readInt(); + return new AudioState(isMuted, route, supportedRouteMask); + } + + @Override + public AudioState[] newArray(int size) { + return new AudioState[size]; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Writes AudioState object into a serializeable Parcel. + */ + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeByte((byte) (isMuted ? 1 : 0)); + destination.writeInt(route); + destination.writeInt(supportedRouteMask); + } +} diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java new file mode 100644 index 0000000..1a6c52f --- /dev/null +++ b/telecomm/java/android/telecom/Call.java @@ -0,0 +1,827 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.annotation.SystemApi; +import android.net.Uri; +import android.os.Bundle; + +import java.lang.String; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Represents an ongoing phone call that the in-call app should present to the user. + * + * {@hide} + */ +@SystemApi +public final class Call { + /** + * The state of a {@code Call} when newly created. + */ + public static final int STATE_NEW = 0; + + /** + * The state of an outgoing {@code Call} when dialing the remote number, but not yet connected. + */ + public static final int STATE_DIALING = 1; + + /** + * The state of an incoming {@code Call} when ringing locally, but not yet connected. + */ + public static final int STATE_RINGING = 2; + + /** + * The state of a {@code Call} when in a holding state. + */ + public static final int STATE_HOLDING = 3; + + /** + * The state of a {@code Call} when actively supporting conversation. + */ + public static final int STATE_ACTIVE = 4; + + /** + * The state of a {@code Call} when no further voice or other communication is being + * transmitted, the remote side has been or will inevitably be informed that the {@code Call} + * is no longer active, and the local data transport has or inevitably will release resources + * associated with this {@code Call}. + */ + public static final int STATE_DISCONNECTED = 7; + + /** + * The state of an outgoing {@code Call}, but waiting for user input before proceeding. + */ + public static final int STATE_PRE_DIAL_WAIT = 8; + + /** + * The initial state of an outgoing {@code Call}. + * Common transitions are to {@link #STATE_DIALING} state for a successful call or + * {@link #STATE_DISCONNECTED} if it failed. + */ + public static final int STATE_CONNECTING = 9; + + public static class Details { + private final Uri mHandle; + private final int mHandlePresentation; + private final String mCallerDisplayName; + private final int mCallerDisplayNamePresentation; + private final PhoneAccountHandle mAccountHandle; + private final int mCallCapabilities; + private final int mCallProperties; + private final DisconnectCause mDisconnectCause; + private final long mConnectTimeMillis; + private final GatewayInfo mGatewayInfo; + private final int mVideoState; + private final StatusHints mStatusHints; + private final Bundle mExtras; + + /** + * @return The handle (e.g., phone number) to which the {@code Call} is currently + * connected. + */ + public Uri getHandle() { + return mHandle; + } + + /** + * @return The presentation requirements for the handle. See + * {@link TelecomManager} for valid values. + */ + public int getHandlePresentation() { + return mHandlePresentation; + } + + /** + * @return The display name for the caller. + */ + public String getCallerDisplayName() { + return mCallerDisplayName; + } + + /** + * @return The presentation requirements for the caller display name. See + * {@link TelecomManager} for valid values. + */ + public int getCallerDisplayNamePresentation() { + return mCallerDisplayNamePresentation; + } + + /** + * @return The {@code PhoneAccountHandle} whereby the {@code Call} is currently being + * routed. + */ + public PhoneAccountHandle getAccountHandle() { + return mAccountHandle; + } + + /** + * @return A bitmask of the capabilities of the {@code Call}, as defined in + * {@link PhoneCapabilities}. + */ + public int getCallCapabilities() { + return mCallCapabilities; + } + + /** + * @return A bitmask of the properties of the {@code Call}, as defined in + * {@link CallProperties}. + */ + public int getCallProperties() { + return mCallProperties; + } + + /** + * @return For a {@link #STATE_DISCONNECTED} {@code Call}, the disconnect cause expressed + * by {@link android.telecomm.DisconnectCause}. + */ + public DisconnectCause getDisconnectCause() { + return mDisconnectCause; + } + + /** + * @return The time the {@code Call} has been connected. This information is updated + * periodically, but user interfaces should not rely on this to display any "call time + * clock". + */ + public long getConnectTimeMillis() { + return mConnectTimeMillis; + } + + /** + * @return Information about any calling gateway the {@code Call} may be using. + */ + public GatewayInfo getGatewayInfo() { + return mGatewayInfo; + } + + /** + * @return The video state of the {@code Call}. + */ + public int getVideoState() { + return mVideoState; + } + + /** + * @return The current {@link android.telecom.StatusHints}, or {@code null} if none + * have been set. + */ + public StatusHints getStatusHints() { + return mStatusHints; + } + + /** + * @return A bundle extras to pass with the call + */ + public Bundle getExtras() { + return mExtras; + } + + @Override + public boolean equals(Object o) { + if (o instanceof Details) { + Details d = (Details) o; + return + Objects.equals(mHandle, d.mHandle) && + Objects.equals(mHandlePresentation, d.mHandlePresentation) && + Objects.equals(mCallerDisplayName, d.mCallerDisplayName) && + Objects.equals(mCallerDisplayNamePresentation, + d.mCallerDisplayNamePresentation) && + Objects.equals(mAccountHandle, d.mAccountHandle) && + Objects.equals(mCallCapabilities, d.mCallCapabilities) && + Objects.equals(mCallProperties, d.mCallProperties) && + Objects.equals(mDisconnectCause, d.mDisconnectCause) && + Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) && + Objects.equals(mGatewayInfo, d.mGatewayInfo) && + Objects.equals(mVideoState, d.mVideoState) && + Objects.equals(mStatusHints, d.mStatusHints) && + Objects.equals(mExtras, d.mExtras); + } + return false; + } + + @Override + public int hashCode() { + return + Objects.hashCode(mHandle) + + Objects.hashCode(mHandlePresentation) + + Objects.hashCode(mCallerDisplayName) + + Objects.hashCode(mCallerDisplayNamePresentation) + + Objects.hashCode(mAccountHandle) + + Objects.hashCode(mCallCapabilities) + + Objects.hashCode(mCallProperties) + + Objects.hashCode(mDisconnectCause) + + Objects.hashCode(mConnectTimeMillis) + + Objects.hashCode(mGatewayInfo) + + Objects.hashCode(mVideoState) + + Objects.hashCode(mStatusHints) + + Objects.hashCode(mExtras); + } + + /** {@hide} */ + public Details( + Uri handle, + int handlePresentation, + String callerDisplayName, + int callerDisplayNamePresentation, + PhoneAccountHandle accountHandle, + int capabilities, + int properties, + DisconnectCause disconnectCause, + long connectTimeMillis, + GatewayInfo gatewayInfo, + int videoState, + StatusHints statusHints, + Bundle extras) { + mHandle = handle; + mHandlePresentation = handlePresentation; + mCallerDisplayName = callerDisplayName; + mCallerDisplayNamePresentation = callerDisplayNamePresentation; + mAccountHandle = accountHandle; + mCallCapabilities = capabilities; + mCallProperties = properties; + mDisconnectCause = disconnectCause; + mConnectTimeMillis = connectTimeMillis; + mGatewayInfo = gatewayInfo; + mVideoState = videoState; + mStatusHints = statusHints; + mExtras = extras; + } + } + + public static abstract class Listener { + /** + * Invoked when the state of this {@code Call} has changed. See {@link #getState()}. + * + * @param call The {@code Call} invoking this method. + * @param state The new state of the {@code Call}. + */ + public void onStateChanged(Call call, int state) {} + + /** + * Invoked when the parent of this {@code Call} has changed. See {@link #getParent()}. + * + * @param call The {@code Call} invoking this method. + * @param parent The new parent of the {@code Call}. + */ + public void onParentChanged(Call call, Call parent) {} + + /** + * Invoked when the children of this {@code Call} have changed. See {@link #getChildren()}. + * + * @param call The {@code Call} invoking this method. + * @param children The new children of the {@code Call}. + */ + public void onChildrenChanged(Call call, List<Call> children) {} + + /** + * Invoked when the details of this {@code Call} have changed. See {@link #getDetails()}. + * + * @param call The {@code Call} invoking this method. + * @param details A {@code Details} object describing the {@code Call}. + */ + public void onDetailsChanged(Call call, Details details) {} + + /** + * Invoked when the text messages that can be used as responses to the incoming + * {@code Call} are loaded from the relevant database. + * See {@link #getCannedTextResponses()}. + * + * @param call The {@code Call} invoking this method. + * @param cannedTextResponses The text messages useable as responses. + */ + public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {} + + /** + * Invoked when the post-dial sequence in the outgoing {@code Call} has reached a pause + * character. This causes the post-dial signals to stop pending user confirmation. An + * implementation should present this choice to the user and invoke + * {@link #postDialContinue(boolean)} when the user makes the choice. + * + * @param call The {@code Call} invoking this method. + * @param remainingPostDialSequence The post-dial characters that remain to be sent. + */ + public void onPostDialWait(Call call, String remainingPostDialSequence) {} + + /** + * Invoked when the {@code Call.VideoCall} of the {@code Call} has changed. + * + * @param call The {@code Call} invoking this method. + * @param videoCall The {@code Call.VideoCall} associated with the {@code Call}. + * @hide + */ + public void onVideoCallChanged(Call call, InCallService.VideoCall videoCall) {} + + /** + * Invoked when the {@code Call} is destroyed. Clients should refrain from cleaning + * up their UI for the {@code Call} in response to state transitions. Specifically, + * clients should not assume that a {@link #onStateChanged(Call, int)} with a state of + * {@link #STATE_DISCONNECTED} is the final notification the {@code Call} will send. Rather, + * clients should wait for this method to be invoked. + * + * @param call The {@code Call} being destroyed. + */ + public void onCallDestroyed(Call call) {} + + /** + * Invoked upon changes to the set of {@code Call}s with which this {@code Call} can be + * conferenced. + * + * @param call The {@code Call} being updated. + * @param conferenceableCalls The {@code Call}s with which this {@code Call} can be + * conferenced. + */ + public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {} + } + + private final Phone mPhone; + private final String mTelecomCallId; + private final InCallAdapter mInCallAdapter; + private final List<String> mChildrenIds = new ArrayList<>(); + private final List<Call> mChildren = new ArrayList<>(); + private final List<Call> mUnmodifiableChildren = Collections.unmodifiableList(mChildren); + private final List<Listener> mListeners = new CopyOnWriteArrayList<>(); + private final List<Call> mConferenceableCalls = new ArrayList<>(); + private final List<Call> mUnmodifiableConferenceableCalls = + Collections.unmodifiableList(mConferenceableCalls); + + private boolean mChildrenCached; + private String mParentId = null; + private int mState; + private List<String> mCannedTextResponses = null; + private String mRemainingPostDialSequence; + private InCallService.VideoCall mVideoCall; + private Details mDetails; + + /** + * Obtains the post-dial sequence remaining to be emitted by this {@code Call}, if any. + * + * @return The remaining post-dial sequence, or {@code null} if there is no post-dial sequence + * remaining or this {@code Call} is not in a post-dial state. + */ + public String getRemainingPostDialSequence() { + return mRemainingPostDialSequence; + } + + /** + * Instructs this {@link #STATE_RINGING} {@code Call} to answer. + * @param videoState The video state in which to answer the call. + */ + public void answer(int videoState) { + mInCallAdapter.answerCall(mTelecomCallId, videoState); + } + + /** + * Instructs this {@link #STATE_RINGING} {@code Call} to reject. + * + * @param rejectWithMessage Whether to reject with a text message. + * @param textMessage An optional text message with which to respond. + */ + public void reject(boolean rejectWithMessage, String textMessage) { + mInCallAdapter.rejectCall(mTelecomCallId, rejectWithMessage, textMessage); + } + + /** + * Instructs this {@code Call} to disconnect. + */ + public void disconnect() { + mInCallAdapter.disconnectCall(mTelecomCallId); + } + + /** + * Instructs this {@code Call} to go on hold. + */ + public void hold() { + mInCallAdapter.holdCall(mTelecomCallId); + } + + /** + * Instructs this {@link #STATE_HOLDING} call to release from hold. + */ + public void unhold() { + mInCallAdapter.unholdCall(mTelecomCallId); + } + + /** + * Instructs this {@code Call} to play a dual-tone multi-frequency signaling (DTMF) tone. + * + * Any other currently playing DTMF tone in the specified call is immediately stopped. + * + * @param digit A character representing the DTMF digit for which to play the tone. This + * value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}. + */ + public void playDtmfTone(char digit) { + mInCallAdapter.playDtmfTone(mTelecomCallId, digit); + } + + /** + * Instructs this {@code Call} to stop any dual-tone multi-frequency signaling (DTMF) tone + * currently playing. + * + * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is + * currently playing, this method will do nothing. + */ + public void stopDtmfTone() { + mInCallAdapter.stopDtmfTone(mTelecomCallId); + } + + /** + * Instructs this {@code Call} to continue playing a post-dial DTMF string. + * + * A post-dial DTMF string is a string of digits entered after a phone number, when dialed, + * that are immediately sent as DTMF tones to the recipient as soon as the connection is made. + * + * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_PAUSE} symbol, this + * {@code Call} will temporarily pause playing the tones for a pre-defined period of time. + * + * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, this + * {@code Call} will pause playing the tones and notify listeners via + * {@link Listener#onPostDialWait(Call, String)}. At this point, the in-call app + * should display to the user an indication of this state and an affordance to continue + * the postdial sequence. When the user decides to continue the postdial sequence, the in-call + * app should invoke the {@link #postDialContinue(boolean)} method. + * + * @param proceed Whether or not to continue with the post-dial sequence. + */ + public void postDialContinue(boolean proceed) { + mInCallAdapter.postDialContinue(mTelecomCallId, proceed); + } + + /** + * Notifies this {@code Call} that an account has been selected and to proceed with placing + * an outgoing call. + */ + public void phoneAccountSelected(PhoneAccountHandle accountHandle) { + mInCallAdapter.phoneAccountSelected(mTelecomCallId, accountHandle); + + } + + /** + * Instructs this {@code Call} to enter a conference. + * + * @param callToConferenceWith The other call with which to conference. + */ + public void conference(Call callToConferenceWith) { + if (callToConferenceWith != null) { + mInCallAdapter.conference(mTelecomCallId, callToConferenceWith.mTelecomCallId); + } + } + + /** + * Instructs this {@code Call} to split from any conference call with which it may be + * connected. + */ + public void splitFromConference() { + mInCallAdapter.splitFromConference(mTelecomCallId); + } + + /** + * Merges the calls within this conference. See {@link PhoneCapabilities#MERGE_CONFERENCE}. + */ + public void mergeConference() { + mInCallAdapter.mergeConference(mTelecomCallId); + } + + /** + * Swaps the calls within this conference. See {@link PhoneCapabilities#SWAP_CONFERENCE}. + */ + public void swapConference() { + mInCallAdapter.swapConference(mTelecomCallId); + } + + /** + * Obtains the parent of this {@code Call} in a conference, if any. + * + * @return The parent {@code Call}, or {@code null} if this {@code Call} is not a + * child of any conference {@code Call}s. + */ + public Call getParent() { + if (mParentId != null) { + return mPhone.internalGetCallByTelecomId(mParentId); + } + return null; + } + + /** + * Obtains the children of this conference {@code Call}, if any. + * + * @return The children of this {@code Call} if this {@code Call} is a conference, or an empty + * {@code List} otherwise. + */ + public List<Call> getChildren() { + if (!mChildrenCached) { + mChildrenCached = true; + mChildren.clear(); + + for(String id : mChildrenIds) { + Call call = mPhone.internalGetCallByTelecomId(id); + if (call == null) { + // At least one child was still not found, so do not save true for "cached" + mChildrenCached = false; + } else { + mChildren.add(call); + } + } + } + + return mUnmodifiableChildren; + } + + /** + * Returns the list of {@code Call}s with which this {@code Call} is allowed to conference. + * + * @return The list of conferenceable {@code Call}s. + */ + public List<Call> getConferenceableCalls() { + return mUnmodifiableConferenceableCalls; + } + + /** + * Obtains the state of this {@code Call}. + * + * @return A state value, chosen from the {@code STATE_*} constants. + */ + public int getState() { + return mState; + } + + /** + * Obtains a list of canned, pre-configured message responses to present to the user as + * ways of rejecting this {@code Call} using via a text message. + * + * @see #reject(boolean, String) + * + * @return A list of canned text message responses. + */ + public List<String> getCannedTextResponses() { + return mCannedTextResponses; + } + + /** + * Obtains an object that can be used to display video from this {@code Call}. + * + * @return An {@code Call.VideoCall}. + * @hide + */ + public InCallService.VideoCall getVideoCall() { + return mVideoCall; + } + + /** + * Obtains an object containing call details. + * + * @return A {@link Details} object. Depending on the state of the {@code Call}, the + * result may be {@code null}. + */ + public Details getDetails() { + return mDetails; + } + + /** + * Adds a listener to this {@code Call}. + * + * @param listener A {@code Listener}. + */ + public void addListener(Listener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener from this {@code Call}. + * + * @param listener A {@code Listener}. + */ + public void removeListener(Listener listener) { + if (listener != null) { + mListeners.remove(listener); + } + } + + /** {@hide} */ + Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter) { + mPhone = phone; + mTelecomCallId = telecomCallId; + mInCallAdapter = inCallAdapter; + mState = STATE_NEW; + } + + /** {@hide} */ + final String internalGetCallId() { + return mTelecomCallId; + } + + /** {@hide} */ + final void internalUpdate(ParcelableCall parcelableCall, Map<String, Call> callIdMap) { + // First, we update the internal state as far as possible before firing any updates. + Details details = new Details( + parcelableCall.getHandle(), + parcelableCall.getHandlePresentation(), + parcelableCall.getCallerDisplayName(), + parcelableCall.getCallerDisplayNamePresentation(), + parcelableCall.getAccountHandle(), + parcelableCall.getCapabilities(), + parcelableCall.getProperties(), + parcelableCall.getDisconnectCause(), + parcelableCall.getConnectTimeMillis(), + parcelableCall.getGatewayInfo(), + parcelableCall.getVideoState(), + parcelableCall.getStatusHints(), + parcelableCall.getExtras()); + boolean detailsChanged = !Objects.equals(mDetails, details); + if (detailsChanged) { + mDetails = details; + } + + boolean cannedTextResponsesChanged = false; + if (mCannedTextResponses == null && parcelableCall.getCannedSmsResponses() != null + && !parcelableCall.getCannedSmsResponses().isEmpty()) { + mCannedTextResponses = + Collections.unmodifiableList(parcelableCall.getCannedSmsResponses()); + } + + boolean videoCallChanged = !Objects.equals(mVideoCall, parcelableCall.getVideoCall()); + if (videoCallChanged) { + mVideoCall = parcelableCall.getVideoCall(); + } + + int state = stateFromParcelableCallState(parcelableCall.getState()); + boolean stateChanged = mState != state; + if (stateChanged) { + mState = state; + } + + String parentId = parcelableCall.getParentCallId(); + boolean parentChanged = !Objects.equals(mParentId, parentId); + if (parentChanged) { + mParentId = parentId; + } + + List<String> childCallIds = parcelableCall.getChildCallIds(); + boolean childrenChanged = !Objects.equals(childCallIds, mChildrenIds); + if (childrenChanged) { + mChildrenIds.clear(); + mChildrenIds.addAll(parcelableCall.getChildCallIds()); + mChildrenCached = false; + } + + List<String> conferenceableCallIds = parcelableCall.getConferenceableCallIds(); + List<Call> conferenceableCalls = new ArrayList<Call>(conferenceableCallIds.size()); + for (String otherId : conferenceableCallIds) { + if (callIdMap.containsKey(otherId)) { + conferenceableCalls.add(callIdMap.get(otherId)); + } + } + + if (!Objects.equals(mConferenceableCalls, conferenceableCalls)) { + mConferenceableCalls.clear(); + mConferenceableCalls.addAll(conferenceableCalls); + fireConferenceableCallsChanged(); + } + + // Now we fire updates, ensuring that any client who listens to any of these notifications + // gets the most up-to-date state. + + if (stateChanged) { + fireStateChanged(mState); + } + if (detailsChanged) { + fireDetailsChanged(mDetails); + } + if (cannedTextResponsesChanged) { + fireCannedTextResponsesLoaded(mCannedTextResponses); + } + if (videoCallChanged) { + fireVideoCallChanged(mVideoCall); + } + if (parentChanged) { + fireParentChanged(getParent()); + } + if (childrenChanged) { + fireChildrenChanged(getChildren()); + } + + // If we have transitioned to DISCONNECTED, that means we need to notify clients and + // remove ourselves from the Phone. Note that we do this after completing all state updates + // so a client can cleanly transition all their UI to the state appropriate for a + // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list. + if (mState == STATE_DISCONNECTED) { + fireCallDestroyed(); + mPhone.internalRemoveCall(this); + } + } + + /** {@hide} */ + final void internalSetPostDialWait(String remaining) { + mRemainingPostDialSequence = remaining; + firePostDialWait(mRemainingPostDialSequence); + } + + /** {@hide} */ + final void internalSetDisconnected() { + if (mState != Call.STATE_DISCONNECTED) { + mState = Call.STATE_DISCONNECTED; + fireStateChanged(mState); + fireCallDestroyed(); + mPhone.internalRemoveCall(this); + } + } + + private void fireStateChanged(int newState) { + for (Listener listener : mListeners) { + listener.onStateChanged(this, newState); + } + } + + private void fireParentChanged(Call newParent) { + for (Listener listener : mListeners) { + listener.onParentChanged(this, newParent); + } + } + + private void fireChildrenChanged(List<Call> children) { + for (Listener listener : mListeners) { + listener.onChildrenChanged(this, children); + } + } + + private void fireDetailsChanged(Details details) { + for (Listener listener : mListeners) { + listener.onDetailsChanged(this, details); + } + } + + private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) { + for (Listener listener : mListeners) { + listener.onCannedTextResponsesLoaded(this, cannedTextResponses); + } + } + + private void fireVideoCallChanged(InCallService.VideoCall videoCall) { + for (Listener listener : mListeners) { + listener.onVideoCallChanged(this, videoCall); + } + } + + private void firePostDialWait(String remainingPostDialSequence) { + for (Listener listener : mListeners) { + listener.onPostDialWait(this, remainingPostDialSequence); + } + } + + private void fireCallDestroyed() { + for (Listener listener : mListeners) { + listener.onCallDestroyed(this); + } + } + + private void fireConferenceableCallsChanged() { + for (Listener listener : mListeners) { + listener.onConferenceableCallsChanged(this, mUnmodifiableConferenceableCalls); + } + } + + private int stateFromParcelableCallState(int parcelableCallState) { + switch (parcelableCallState) { + case CallState.NEW: + return STATE_NEW; + case CallState.CONNECTING: + return STATE_CONNECTING; + case CallState.PRE_DIAL_WAIT: + return STATE_PRE_DIAL_WAIT; + case CallState.DIALING: + return STATE_DIALING; + case CallState.RINGING: + return STATE_RINGING; + case CallState.ACTIVE: + return STATE_ACTIVE; + case CallState.ON_HOLD: + return STATE_HOLDING; + case CallState.DISCONNECTED: + return STATE_DISCONNECTED; + case CallState.ABORTED: + return STATE_DISCONNECTED; + default: + Log.wtf(this, "Unrecognized CallState %s", parcelableCallState); + return STATE_NEW; + } + } +} diff --git a/telecomm/java/android/telecom/CallProperties.java b/telecomm/java/android/telecom/CallProperties.java new file mode 100644 index 0000000..b1b82e2 --- /dev/null +++ b/telecomm/java/android/telecom/CallProperties.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +/** + * Defines properties of a phone call which may be affected by changes to the call. + * @hide + */ +public class CallProperties { + /** Call is currently in a conference call. */ + public static final int CONFERENCE = 0x00000001; +} diff --git a/telecomm/java/android/telecom/CallState.java b/telecomm/java/android/telecom/CallState.java new file mode 100644 index 0000000..7690847 --- /dev/null +++ b/telecomm/java/android/telecom/CallState.java @@ -0,0 +1,127 @@ +/* + * Copyright 2014, 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 android.telecom; + +import android.annotation.SystemApi; + +/** + * Defines call-state constants of the different states in which a call can exist. Although states + * have the notion of normal transitions, due to the volatile nature of telephony systems, code + * that uses these states should be resilient to unexpected state changes outside of what is + * considered traditional. + * + * {@hide} + */ +@SystemApi +public final class CallState { + + private CallState() {} + + /** + * Indicates that a call is new and not connected. This is used as the default state internally + * within Telecom and should not be used between Telecom and call services. Call services are + * not expected to ever interact with NEW calls, but {@link InCallService}s will see calls in + * this state. + */ + public static final int NEW = 0; + + /** + * The initial state of an outgoing {@code Call}. + * Common transitions are to {@link #DIALING} state for a successful call or + * {@link #DISCONNECTED} if it failed. + */ + public static final int CONNECTING = 1; + + /** + * Indicates that the call is about to go into the outgoing and dialing state but is waiting for + * user input before it proceeds. For example, where no default {@link PhoneAccount} is set, + * this is the state where the InCallUI is waiting for the user to select a + * {@link PhoneAccount} to call from. + */ + public static final int PRE_DIAL_WAIT = 2; + + /** + * Indicates that a call is outgoing and in the dialing state. A call transitions to this state + * once an outgoing call has begun (e.g., user presses the dial button in Dialer). Calls in this + * state usually transition to {@link #ACTIVE} if the call was answered or {@link #DISCONNECTED} + * if the call was disconnected somehow (e.g., failure or cancellation of the call by the user). + */ + public static final int DIALING = 3; + + /** + * Indicates that a call is incoming and the user still has the option of answering, rejecting, + * or doing nothing with the call. This state is usually associated with some type of audible + * ringtone. Normal transitions are to {@link #ACTIVE} if answered or {@link #DISCONNECTED} + * otherwise. + */ + public static final int RINGING = 4; + + /** + * Indicates that a call is currently connected to another party and a communication channel is + * open between them. The normal transition to this state is by the user answering a + * {@link #DIALING} call or a {@link #RINGING} call being answered by the other party. + */ + public static final int ACTIVE = 5; + + /** + * Indicates that the call is currently on hold. In this state, the call is not terminated + * but no communication is allowed until the call is no longer on hold. The typical transition + * to this state is by the user putting an {@link #ACTIVE} call on hold by explicitly performing + * an action, such as clicking the hold button. + */ + public static final int ON_HOLD = 6; + + /** + * Indicates that a call is currently disconnected. All states can transition to this state + * by the call service giving notice that the connection has been severed. When the user + * explicitly ends a call, it will not transition to this state until the call service confirms + * the disconnection or communication was lost to the call service currently responsible for + * this call (e.g., call service crashes). + */ + public static final int DISCONNECTED = 7; + + /** + * Indicates that the call was attempted (mostly in the context of outgoing, at least at the + * time of writing) but cancelled before it was successfully connected. + */ + public static final int ABORTED = 8; + + public static String toString(int callState) { + switch (callState) { + case NEW: + return "NEW"; + case CONNECTING: + return "CONNECTING"; + case PRE_DIAL_WAIT: + return "PRE_DIAL_WAIT"; + case DIALING: + return "DIALING"; + case RINGING: + return "RINGING"; + case ACTIVE: + return "ACTIVE"; + case ON_HOLD: + return "ON_HOLD"; + case DISCONNECTED: + return "DISCONNECTED"; + case ABORTED: + return "ABORTED"; + default: + return "UNKNOWN"; + } + } +} diff --git a/telecomm/java/android/telecom/CameraCapabilities.aidl b/telecomm/java/android/telecom/CameraCapabilities.aidl new file mode 100644 index 0000000..c8e0c5e --- /dev/null +++ b/telecomm/java/android/telecom/CameraCapabilities.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +/** + * {@hide} + */ +parcelable CameraCapabilities; diff --git a/telecomm/java/android/telecom/CameraCapabilities.java b/telecomm/java/android/telecom/CameraCapabilities.java new file mode 100644 index 0000000..f968c13 --- /dev/null +++ b/telecomm/java/android/telecom/CameraCapabilities.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents the camera capabilities important to a Video Telephony provider. + * @hide + */ +public final class CameraCapabilities implements Parcelable { + + /** + * Whether the camera supports zoom. + */ + private final boolean mZoomSupported; + + /** + * The maximum zoom supported by the camera. + */ + private final float mMaxZoom; + + /** + * The width of the camera video in pixels. + */ + private final int mWidth; + + /** + * The height of the camera video in pixels. + */ + private final int mHeight; + + /** + * Create a call camera capabilities instance. + * + * @param zoomSupported True when camera supports zoom. + * @param maxZoom Maximum zoom supported by camera. + * @param width The width of the camera video (in pixels). + * @param height The height of the camera video (in pixels). + */ + public CameraCapabilities(boolean zoomSupported, float maxZoom, int width, int height) { + mZoomSupported = zoomSupported; + mMaxZoom = maxZoom; + mWidth = width; + mHeight = height; + } + + /** + * Responsible for creating CallCameraCapabilities objects from deserialized Parcels. + **/ + public static final Parcelable.Creator<CameraCapabilities> CREATOR = + new Parcelable.Creator<CameraCapabilities> () { + /** + * Creates a CallCameraCapabilities instances from a parcel. + * + * @param source The parcel. + * @return The CallCameraCapabilities. + */ + @Override + public CameraCapabilities createFromParcel(Parcel source) { + boolean supportsZoom = source.readByte() != 0; + float maxZoom = source.readFloat(); + int width = source.readInt(); + int height = source.readInt(); + + return new CameraCapabilities(supportsZoom, maxZoom, width, height); + } + + @Override + public CameraCapabilities[] newArray(int size) { + return new CameraCapabilities[size]; + } + }; + + /** + * Describe the kinds of special objects contained in this Parcelable's + * marshalled representation. + * + * @return a bitmask indicating the set of special object types marshalled + * by the Parcelable. + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (isZoomSupported() ? 1 : 0)); + dest.writeFloat(getMaxZoom()); + dest.writeInt(getWidth()); + dest.writeInt(getHeight()); + } + + /** + * Whether the camera supports zoom. + */ + public boolean isZoomSupported() { + return mZoomSupported; + } + + /** + * The maximum zoom supported by the camera. + */ + public float getMaxZoom() { + return mMaxZoom; + } + + /** + * The width of the camera video in pixels. + */ + public int getWidth() { + return mWidth; + } + + /** + * The height of the camera video in pixels. + */ + public int getHeight() { + return mHeight; + } +} diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java new file mode 100644 index 0000000..9b350c1 --- /dev/null +++ b/telecomm/java/android/telecom/Conference.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Represents a conference call which can contain any number of {@link Connection} objects. + */ +public abstract class Conference { + + /** @hide */ + public abstract static class Listener { + public void onStateChanged(Conference conference, int oldState, int newState) {} + public void onDisconnected(Conference conference, DisconnectCause disconnectCause) {} + public void onConnectionAdded(Conference conference, Connection connection) {} + public void onConnectionRemoved(Conference conference, Connection connection) {} + public void onDestroyed(Conference conference) {} + public void onCapabilitiesChanged(Conference conference, int capabilities) {} + } + + private final Set<Listener> mListeners = new CopyOnWriteArraySet<>(); + private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>(); + private final List<Connection> mUnmodifiableChildConnections = + Collections.unmodifiableList(mChildConnections); + + private PhoneAccountHandle mPhoneAccount; + private AudioState mAudioState; + private int mState = Connection.STATE_NEW; + private DisconnectCause mDisconnectCause; + private int mCapabilities; + private String mDisconnectMessage; + + /** + * Constructs a new Conference with a mandatory {@link PhoneAccountHandle} + * + * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference. + */ + public Conference(PhoneAccountHandle phoneAccount) { + mPhoneAccount = phoneAccount; + } + + /** + * Returns the {@link PhoneAccountHandle} the conference call is being placed through. + * + * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference. + */ + public final PhoneAccountHandle getPhoneAccountHandle() { + return mPhoneAccount; + } + + /** + * Returns the list of connections currently associated with the conference call. + * + * @return A list of {@code Connection} objects which represent the children of the conference. + */ + public final List<Connection> getConnections() { + return mUnmodifiableChildConnections; + } + + /** + * Gets the state of the conference call. See {@link Connection} for valid values. + * + * @return A constant representing the state the conference call is currently in. + */ + public final int getState() { + return mState; + } + + /** + * Returns the capabilities of a conference. See {@link PhoneCapabilities} for valid values. + * + * @return A bitmask of the {@code PhoneCapabilities} of the conference call. + */ + public final int getCapabilities() { + return mCapabilities; + } + + /** + * @return The audio state of the conference, describing how its audio is currently + * being routed by the system. This is {@code null} if this Conference + * does not directly know about its audio state. + */ + public final AudioState getAudioState() { + return mAudioState; + } + + /** + * Invoked when the Conference and all it's {@link Connection}s should be disconnected. + */ + public void onDisconnect() {} + + /** + * Invoked when the specified {@link Connection} should be separated from the conference call. + * + * @param connection The connection to separate. + */ + public void onSeparate(Connection connection) {} + + /** + * Invoked when the conference should be put on hold. + */ + public void onHold() {} + + /** + * Invoked when the conference should be moved from hold to active. + */ + public void onUnhold() {} + + /** + * Invoked when the child calls should be merged. Only invoked if the conference contains the + * capability {@link PhoneCapabilities#MERGE_CONFERENCE}. + */ + public void onMerge() {} + + /** + * Invoked when the child calls should be swapped. Only invoked if the conference contains the + * capability {@link PhoneCapabilities#SWAP_CONFERENCE}. + */ + public void onSwap() {} + + /** + * Notifies this conference of a request to play a DTMF tone. + * + * @param c A DTMF character. + */ + public void onPlayDtmfTone(char c) {} + + /** + * Notifies this conference of a request to stop any currently playing DTMF tones. + */ + public void onStopDtmfTone() {} + + /** + * Notifies this conference that the {@link #getAudioState()} property has a new value. + * + * @param state The new call audio state. + */ + public void onAudioStateChanged(AudioState state) {} + + /** + * Sets state to be on hold. + */ + public final void setOnHold() { + setState(Connection.STATE_HOLDING); + } + + /** + * Sets state to be active. + */ + public final void setActive() { + setState(Connection.STATE_ACTIVE); + } + + /** + * Sets state to disconnected. + * + * @param disconnectCause The reason for the disconnection, as described by + * {@link android.telecom.DisconnectCause}. + */ + public final void setDisconnected(DisconnectCause disconnectCause) { + mDisconnectCause = disconnectCause;; + setState(Connection.STATE_DISCONNECTED); + for (Listener l : mListeners) { + l.onDisconnected(this, mDisconnectCause); + } + } + + /** + * Sets the capabilities of a conference. See {@link PhoneCapabilities} for valid values. + * + * @param capabilities A bitmask of the {@code PhoneCapabilities} of the conference call. + */ + public final void setCapabilities(int capabilities) { + if (capabilities != mCapabilities) { + mCapabilities = capabilities; + + for (Listener l : mListeners) { + l.onCapabilitiesChanged(this, mCapabilities); + } + } + } + + /** + * Adds the specified connection as a child of this conference. + * + * @param connection The connection to add. + * @return True if the connection was successfully added. + */ + public final boolean addConnection(Connection connection) { + if (connection != null && !mChildConnections.contains(connection)) { + if (connection.setConference(this)) { + mChildConnections.add(connection); + for (Listener l : mListeners) { + l.onConnectionAdded(this, connection); + } + return true; + } + } + return false; + } + + /** + * Removes the specified connection as a child of this conference. + * + * @param connection The connection to remove. + */ + public final void removeConnection(Connection connection) { + Log.d(this, "removing %s from %s", connection, mChildConnections); + if (connection != null && mChildConnections.remove(connection)) { + connection.resetConference(); + for (Listener l : mListeners) { + l.onConnectionRemoved(this, connection); + } + } + } + + /** + * Tears down the conference object and any of its current connections. + */ + public final void destroy() { + Log.d(this, "destroying conference : %s", this); + // Tear down the children. + for (Connection connection : mChildConnections) { + Log.d(this, "removing connection %s", connection); + removeConnection(connection); + } + + // If not yet disconnected, set the conference call as disconnected first. + if (mState != Connection.STATE_DISCONNECTED) { + Log.d(this, "setting to disconnected"); + setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); + } + + // ...and notify. + for (Listener l : mListeners) { + l.onDestroyed(this); + } + } + + /** + * Add a listener to be notified of a state change. + * + * @param listener The new listener. + * @return This conference. + * @hide + */ + public final Conference addListener(Listener listener) { + mListeners.add(listener); + return this; + } + + /** + * Removes the specified listener. + * + * @param listener The listener to remove. + * @return This conference. + * @hide + */ + public final Conference removeListener(Listener listener) { + mListeners.remove(listener); + return this; + } + + /** + * Inform this Conference that the state of its audio output has been changed externally. + * + * @param state The new audio state. + * @hide + */ + final void setAudioState(AudioState state) { + Log.d(this, "setAudioState %s", state); + mAudioState = state; + onAudioStateChanged(state); + } + + private void setState(int newState) { + if (newState != Connection.STATE_ACTIVE && + newState != Connection.STATE_HOLDING && + newState != Connection.STATE_DISCONNECTED) { + Log.w(this, "Unsupported state transition for Conference call.", + Connection.stateToString(newState)); + return; + } + + if (mState != newState) { + int oldState = mState; + mState = newState; + for (Listener l : mListeners) { + l.onStateChanged(this, oldState, newState); + } + } + } +} diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java new file mode 100644 index 0000000..7979e44 --- /dev/null +++ b/telecomm/java/android/telecom/Connection.java @@ -0,0 +1,1116 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import com.android.internal.telecom.IVideoCallback; +import com.android.internal.telecom.IVideoProvider; + +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.view.Surface; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Represents a connection to a remote endpoint that carries voice traffic. + * <p> + * Implementations create a custom subclass of {@code Connection} and return it to the framework + * as the return value of + * {@link ConnectionService#onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)} + * or + * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * Implementations are then responsible for updating the state of the {@code Connection}, and + * must call {@link #destroy()} to signal to the framework that the {@code Connection} is no + * longer used and associated resources may be recovered. + */ +public abstract class Connection { + + public static final int STATE_INITIALIZING = 0; + + public static final int STATE_NEW = 1; + + public static final int STATE_RINGING = 2; + + public static final int STATE_DIALING = 3; + + public static final int STATE_ACTIVE = 4; + + public static final int STATE_HOLDING = 5; + + public static final int STATE_DISCONNECTED = 6; + + // Flag controlling whether PII is emitted into the logs + private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); + + /** @hide */ + public abstract static class Listener { + public void onStateChanged(Connection c, int state) {} + public void onAddressChanged(Connection c, Uri newAddress, int presentation) {} + public void onCallerDisplayNameChanged( + Connection c, String callerDisplayName, int presentation) {} + public void onVideoStateChanged(Connection c, int videoState) {} + public void onDisconnected(Connection c, DisconnectCause disconnectCause) {} + public void onPostDialWait(Connection c, String remaining) {} + public void onRingbackRequested(Connection c, boolean ringback) {} + public void onDestroyed(Connection c) {} + public void onCallCapabilitiesChanged(Connection c, int callCapabilities) {} + public void onVideoProviderChanged( + Connection c, VideoProvider videoProvider) {} + public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {} + public void onStatusHintsChanged(Connection c, StatusHints statusHints) {} + public void onConferenceableConnectionsChanged( + Connection c, List<Connection> conferenceableConnections) {} + public void onConferenceChanged(Connection c, Conference conference) {} + } + + /** @hide */ + public static abstract class VideoProvider { + + /** + * Video is not being received (no protocol pause was issued). + */ + public static final int SESSION_EVENT_RX_PAUSE = 1; + + /** + * Video reception has resumed after a SESSION_EVENT_RX_PAUSE. + */ + public static final int SESSION_EVENT_RX_RESUME = 2; + + /** + * Video transmission has begun. This occurs after a negotiated start of video transmission + * when the underlying protocol has actually begun transmitting video to the remote party. + */ + public static final int SESSION_EVENT_TX_START = 3; + + /** + * Video transmission has stopped. This occurs after a negotiated stop of video transmission + * when the underlying protocol has actually stopped transmitting video to the remote party. + */ + public static final int SESSION_EVENT_TX_STOP = 4; + + /** + * A camera failure has occurred for the selected camera. The In-Call UI can use this as a + * cue to inform the user the camera is not available. + */ + public static final int SESSION_EVENT_CAMERA_FAILURE = 5; + + /** + * Issued after {@code SESSION_EVENT_CAMERA_FAILURE} when the camera is once again ready for + * operation. The In-Call UI can use this as a cue to inform the user that the camera has + * become available again. + */ + public static final int SESSION_EVENT_CAMERA_READY = 6; + + /** + * Session modify request was successful. + */ + public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; + + /** + * Session modify request failed. + */ + public static final int SESSION_MODIFY_REQUEST_FAIL = 2; + + /** + * Session modify request ignored due to invalid parameters. + */ + public static final int SESSION_MODIFY_REQUEST_INVALID = 3; + + private static final int MSG_SET_VIDEO_CALLBACK = 1; + private static final int MSG_SET_CAMERA = 2; + private static final int MSG_SET_PREVIEW_SURFACE = 3; + private static final int MSG_SET_DISPLAY_SURFACE = 4; + private static final int MSG_SET_DEVICE_ORIENTATION = 5; + private static final int MSG_SET_ZOOM = 6; + private static final int MSG_SEND_SESSION_MODIFY_REQUEST = 7; + private static final int MSG_SEND_SESSION_MODIFY_RESPONSE = 8; + private static final int MSG_REQUEST_CAMERA_CAPABILITIES = 9; + private static final int MSG_REQUEST_CALL_DATA_USAGE = 10; + private static final int MSG_SET_PAUSE_IMAGE = 11; + + private final VideoProvider.VideoProviderHandler + mMessageHandler = new VideoProvider.VideoProviderHandler(); + private final VideoProvider.VideoProviderBinder mBinder; + private IVideoCallback mVideoCallback; + + /** + * Default handler used to consolidate binder method calls onto a single thread. + */ + private final class VideoProviderHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_VIDEO_CALLBACK: + mVideoCallback = IVideoCallback.Stub.asInterface((IBinder) msg.obj); + break; + case MSG_SET_CAMERA: + onSetCamera((String) msg.obj); + break; + case MSG_SET_PREVIEW_SURFACE: + onSetPreviewSurface((Surface) msg.obj); + break; + case MSG_SET_DISPLAY_SURFACE: + onSetDisplaySurface((Surface) msg.obj); + break; + case MSG_SET_DEVICE_ORIENTATION: + onSetDeviceOrientation(msg.arg1); + break; + case MSG_SET_ZOOM: + onSetZoom((Float) msg.obj); + break; + case MSG_SEND_SESSION_MODIFY_REQUEST: + onSendSessionModifyRequest((VideoProfile) msg.obj); + break; + case MSG_SEND_SESSION_MODIFY_RESPONSE: + onSendSessionModifyResponse((VideoProfile) msg.obj); + break; + case MSG_REQUEST_CAMERA_CAPABILITIES: + onRequestCameraCapabilities(); + break; + case MSG_REQUEST_CALL_DATA_USAGE: + onRequestCallDataUsage(); + break; + case MSG_SET_PAUSE_IMAGE: + onSetPauseImage((String) msg.obj); + break; + default: + break; + } + } + } + + /** + * IVideoProvider stub implementation. + */ + private final class VideoProviderBinder extends IVideoProvider.Stub { + public void setVideoCallback(IBinder videoCallbackBinder) { + mMessageHandler.obtainMessage( + MSG_SET_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget(); + } + + public void setCamera(String cameraId) { + mMessageHandler.obtainMessage(MSG_SET_CAMERA, cameraId).sendToTarget(); + } + + public void setPreviewSurface(Surface surface) { + mMessageHandler.obtainMessage(MSG_SET_PREVIEW_SURFACE, surface).sendToTarget(); + } + + public void setDisplaySurface(Surface surface) { + mMessageHandler.obtainMessage(MSG_SET_DISPLAY_SURFACE, surface).sendToTarget(); + } + + public void setDeviceOrientation(int rotation) { + mMessageHandler.obtainMessage(MSG_SET_DEVICE_ORIENTATION, rotation).sendToTarget(); + } + + public void setZoom(float value) { + mMessageHandler.obtainMessage(MSG_SET_ZOOM, value).sendToTarget(); + } + + public void sendSessionModifyRequest(VideoProfile requestProfile) { + mMessageHandler.obtainMessage( + MSG_SEND_SESSION_MODIFY_REQUEST, requestProfile).sendToTarget(); + } + + public void sendSessionModifyResponse(VideoProfile responseProfile) { + mMessageHandler.obtainMessage( + MSG_SEND_SESSION_MODIFY_RESPONSE, responseProfile).sendToTarget(); + } + + public void requestCameraCapabilities() { + mMessageHandler.obtainMessage(MSG_REQUEST_CAMERA_CAPABILITIES).sendToTarget(); + } + + public void requestCallDataUsage() { + mMessageHandler.obtainMessage(MSG_REQUEST_CALL_DATA_USAGE).sendToTarget(); + } + + public void setPauseImage(String uri) { + mMessageHandler.obtainMessage(MSG_SET_PAUSE_IMAGE, uri).sendToTarget(); + } + } + + public VideoProvider() { + mBinder = new VideoProvider.VideoProviderBinder(); + } + + /** + * Returns binder object which can be used across IPC methods. + * @hide + */ + public final IVideoProvider getInterface() { + return mBinder; + } + + /** + * Sets the camera to be used for video recording in a video call. + * + * @param cameraId The id of the camera. + */ + public abstract void onSetCamera(String cameraId); + + /** + * Sets the surface to be used for displaying a preview of what the user's camera is + * currently capturing. When video transmission is enabled, this is the video signal which + * is sent to the remote device. + * + * @param surface The surface. + */ + public abstract void onSetPreviewSurface(Surface surface); + + /** + * Sets the surface to be used for displaying the video received from the remote device. + * + * @param surface The surface. + */ + public abstract void onSetDisplaySurface(Surface surface); + + /** + * Sets the device orientation, in degrees. Assumes that a standard portrait orientation of + * the device is 0 degrees. + * + * @param rotation The device orientation, in degrees. + */ + public abstract void onSetDeviceOrientation(int rotation); + + /** + * Sets camera zoom ratio. + * + * @param value The camera zoom ratio. + */ + public abstract void onSetZoom(float value); + + /** + * Issues a request to modify the properties of the current session. The request is + * sent to the remote device where it it handled by the In-Call UI. + * Some examples of session modification requests: upgrade call from audio to video, + * downgrade call from video to audio, pause video. + * + * @param requestProfile The requested call video properties. + */ + public abstract void onSendSessionModifyRequest(VideoProfile requestProfile); + + /**te + * Provides a response to a request to change the current call session video + * properties. + * This is in response to a request the InCall UI has received via the InCall UI. + * + * @param responseProfile The response call video properties. + */ + public abstract void onSendSessionModifyResponse(VideoProfile responseProfile); + + /** + * Issues a request to the video provider to retrieve the camera capabilities. + * Camera capabilities are reported back to the caller via the In-Call UI. + */ + public abstract void onRequestCameraCapabilities(); + + /** + * Issues a request to the video telephony framework to retrieve the cumulative data usage + * for the current call. Data usage is reported back to the caller via the + * InCall UI. + */ + public abstract void onRequestCallDataUsage(); + + /** + * Provides the video telephony framework with the URI of an image to be displayed to remote + * devices when the video signal is paused. + * + * @param uri URI of image to display. + */ + public abstract void onSetPauseImage(String uri); + + /** + * Invokes callback method defined in In-Call UI. + * + * @param videoProfile The requested video call profile. + */ + public void receiveSessionModifyRequest(VideoProfile videoProfile) { + if (mVideoCallback != null) { + try { + mVideoCallback.receiveSessionModifyRequest(videoProfile); + } catch (RemoteException ignored) { + } + } + } + + /** + * Invokes callback method defined in In-Call UI. + * + * @param status Status of the session modify request. Valid values are + * {@link VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, + * {@link VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, + * {@link VideoProvider#SESSION_MODIFY_REQUEST_INVALID} + * @param requestedProfile The original request which was sent to the remote device. + * @param responseProfile The actual profile changes made by the remote device. + */ + public void receiveSessionModifyResponse(int status, + VideoProfile requestedProfile, VideoProfile responseProfile) { + if (mVideoCallback != null) { + try { + mVideoCallback.receiveSessionModifyResponse( + status, requestedProfile, responseProfile); + } catch (RemoteException ignored) { + } + } + } + + /** + * Invokes callback method defined in In-Call UI. + * + * Valid values are: {@link VideoProvider#SESSION_EVENT_RX_PAUSE}, + * {@link VideoProvider#SESSION_EVENT_RX_RESUME}, + * {@link VideoProvider#SESSION_EVENT_TX_START}, + * {@link VideoProvider#SESSION_EVENT_TX_STOP} + * + * @param event The event. + */ + public void handleCallSessionEvent(int event) { + if (mVideoCallback != null) { + try { + mVideoCallback.handleCallSessionEvent(event); + } catch (RemoteException ignored) { + } + } + } + + /** + * Invokes callback method defined in In-Call UI. + * + * @param width The updated peer video width. + * @param height The updated peer video height. + */ + public void changePeerDimensions(int width, int height) { + if (mVideoCallback != null) { + try { + mVideoCallback.changePeerDimensions(width, height); + } catch (RemoteException ignored) { + } + } + } + + /** + * Invokes callback method defined in In-Call UI. + * + * @param dataUsage The updated data usage. + */ + public void changeCallDataUsage(int dataUsage) { + if (mVideoCallback != null) { + try { + mVideoCallback.changeCallDataUsage(dataUsage); + } catch (RemoteException ignored) { + } + } + } + + /** + * Invokes callback method defined in In-Call UI. + * + * @param cameraCapabilities The changed camera capabilities. + */ + public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) { + if (mVideoCallback != null) { + try { + mVideoCallback.changeCameraCapabilities(cameraCapabilities); + } catch (RemoteException ignored) { + } + } + } + } + + private final Listener mConnectionDeathListener = new Listener() { + @Override + public void onDestroyed(Connection c) { + if (mConferenceableConnections.remove(c)) { + fireOnConferenceableConnectionsChanged(); + } + } + }; + + /** + * 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)); + private final List<Connection> mConferenceableConnections = new ArrayList<>(); + private final List<Connection> mUnmodifiableConferenceableConnections = + Collections.unmodifiableList(mConferenceableConnections); + + private int mState = STATE_NEW; + private AudioState mAudioState; + private Uri mAddress; + private int mAddressPresentation; + private String mCallerDisplayName; + private int mCallerDisplayNamePresentation; + private boolean mRingbackRequested = false; + private int mCallCapabilities; + private VideoProvider mVideoProvider; + private boolean mAudioModeIsVoip; + private StatusHints mStatusHints; + private int mVideoState; + private DisconnectCause mDisconnectCause; + private Conference mConference; + private ConnectionService mConnectionService; + + /** + * Create a new Connection. + */ + public Connection() {} + + /** + * @return The address (e.g., phone number) to which this Connection is currently communicating. + */ + public final Uri getAddress() { + return mAddress; + } + + /** + * @return The presentation requirements for the address. + * See {@link TelecomManager} for valid values. + */ + public final int getAddressPresentation() { + return mAddressPresentation; + } + + /** + * @return The caller display name (CNAP). + */ + public final String getCallerDisplayName() { + return mCallerDisplayName; + } + + /** + * @return The presentation requirements for the handle. + * See {@link TelecomManager} for valid values. + */ + public final int getCallerDisplayNamePresentation() { + return mCallerDisplayNamePresentation; + } + + /** + * @return The state of this Connection. + */ + public final int getState() { + return mState; + } + + /** + * Returns the video state of the call. + * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY}, + * {@link VideoProfile.VideoState#BIDIRECTIONAL}, + * {@link VideoProfile.VideoState#TX_ENABLED}, + * {@link VideoProfile.VideoState#RX_ENABLED}. + * + * @return The video state of the call. + * @hide + */ + public final int getVideoState() { + return mVideoState; + } + + /** + * @return The audio state of the call, describing how its audio is currently + * being routed by the system. This is {@code null} if this Connection + * does not directly know about its audio state. + */ + public final AudioState getAudioState() { + return mAudioState; + } + + /** + * @return The conference that this connection is a part of. Null if it is not part of any + * conference. + */ + public final Conference getConference() { + return mConference; + } + + /** + * Returns whether this connection is requesting that the system play a ringback tone + * on its behalf. + */ + public final boolean isRingbackRequested() { + return mRingbackRequested; + } + + /** + * @return True if the connection's audio mode is VOIP. + */ + public final boolean getAudioModeIsVoip() { + return mAudioModeIsVoip; + } + + /** + * @return The status hints for this connection. + */ + public final StatusHints getStatusHints() { + return mStatusHints; + } + + /** + * Assign a listener to be notified of state changes. + * + * @param l A listener. + * @return This Connection. + * + * @hide + */ + public final Connection addConnectionListener(Listener l) { + mListeners.add(l); + return this; + } + + /** + * Remove a previously assigned listener that was being notified of state changes. + * + * @param l A Listener. + * @return This Connection. + * + * @hide + */ + public final Connection removeConnectionListener(Listener l) { + if (l != null) { + mListeners.remove(l); + } + return this; + } + + /** + * @return The {@link DisconnectCause} for this connection. + */ + public final DisconnectCause getDisconnectCause() { + return mDisconnectCause; + } + + /** + * Inform this Connection that the state of its audio output has been changed externally. + * + * @param state The new audio state. + * @hide + */ + final void setAudioState(AudioState state) { + Log.d(this, "setAudioState %s", state); + mAudioState = state; + onAudioStateChanged(state); + } + + /** + * @param state An integer value of a {@code STATE_*} constant. + * @return A string representation of the value. + */ + public static String stateToString(int state) { + switch (state) { + case STATE_INITIALIZING: + return "STATE_INITIALIZING"; + case STATE_NEW: + return "STATE_NEW"; + case STATE_RINGING: + return "STATE_RINGING"; + case STATE_DIALING: + return "STATE_DIALING"; + case STATE_ACTIVE: + return "STATE_ACTIVE"; + case STATE_HOLDING: + return "STATE_HOLDING"; + case STATE_DISCONNECTED: + return "DISCONNECTED"; + default: + Log.wtf(Connection.class, "Unknown state %d", state); + return "UNKNOWN"; + } + } + + /** + * Returns the connection's {@link PhoneCapabilities} + */ + public final int getCallCapabilities() { + return mCallCapabilities; + } + + /** + * Sets the value of the {@link #getAddress()} property. + * + * @param address The new address. + * @param presentation The presentation requirements for the address. + * See {@link TelecomManager} for valid values. + */ + public final void setAddress(Uri address, int presentation) { + Log.d(this, "setAddress %s", address); + mAddress = address; + mAddressPresentation = presentation; + for (Listener l : mListeners) { + l.onAddressChanged(this, address, presentation); + } + } + + /** + * Sets the caller display name (CNAP). + * + * @param callerDisplayName The new display name. + * @param presentation The presentation requirements for the handle. + * See {@link TelecomManager} for valid values. + */ + public final void setCallerDisplayName(String callerDisplayName, int presentation) { + Log.d(this, "setCallerDisplayName %s", callerDisplayName); + mCallerDisplayName = callerDisplayName; + mCallerDisplayNamePresentation = presentation; + for (Listener l : mListeners) { + l.onCallerDisplayNameChanged(this, callerDisplayName, presentation); + } + } + + /** + * Set the video state for the connection. + * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY}, + * {@link VideoProfile.VideoState#BIDIRECTIONAL}, + * {@link VideoProfile.VideoState#TX_ENABLED}, + * {@link VideoProfile.VideoState#RX_ENABLED}. + * + * @param videoState The new video state. + * @hide + */ + public final void setVideoState(int videoState) { + Log.d(this, "setVideoState %d", videoState); + mVideoState = videoState; + for (Listener l : mListeners) { + l.onVideoStateChanged(this, mVideoState); + } + } + + /** + * Sets state to active (e.g., an ongoing call where two or more parties can actively + * communicate). + */ + public final void setActive() { + setRingbackRequested(false); + setState(STATE_ACTIVE); + } + + /** + * Sets state to ringing (e.g., an inbound ringing call). + */ + public final void setRinging() { + setState(STATE_RINGING); + } + + /** + * Sets state to initializing (this Connection is not yet ready to be used). + */ + public final void setInitializing() { + setState(STATE_INITIALIZING); + } + + /** + * Sets state to initialized (the Connection has been set up and is now ready to be used). + */ + public final void setInitialized() { + setState(STATE_NEW); + } + + /** + * Sets state to dialing (e.g., dialing an outbound call). + */ + public final void setDialing() { + setState(STATE_DIALING); + } + + /** + * Sets state to be on hold. + */ + public final void setOnHold() { + setState(STATE_HOLDING); + } + + /** + * Sets the video call provider. + * @param videoProvider The video provider. + * @hide + */ + public final void setVideoProvider(VideoProvider videoProvider) { + mVideoProvider = videoProvider; + for (Listener l : mListeners) { + l.onVideoProviderChanged(this, videoProvider); + } + } + + /** @hide */ + public final VideoProvider getVideoProvider() { + return mVideoProvider; + } + + /** + * Sets state to disconnected. + * + * @param disconnectCause The reason for the disconnection, as specified by + * {@link DisconnectCause}. + */ + public final void setDisconnected(DisconnectCause disconnectCause) { + mDisconnectCause = disconnectCause; + setState(STATE_DISCONNECTED); + Log.d(this, "Disconnected with cause %s", disconnectCause); + for (Listener l : mListeners) { + l.onDisconnected(this, disconnectCause); + } + } + + /** + * TODO: Needs documentation. + */ + public final void setPostDialWait(String remaining) { + for (Listener l : mListeners) { + l.onPostDialWait(this, remaining); + } + } + + /** + * Requests that the framework play a ringback tone. This is to be invoked by implementations + * that do not play a ringback tone themselves in the call's audio stream. + * + * @param ringback Whether the ringback tone is to be played. + */ + public final void setRingbackRequested(boolean ringback) { + if (mRingbackRequested != ringback) { + mRingbackRequested = ringback; + for (Listener l : mListeners) { + l.onRingbackRequested(this, ringback); + } + } + } + + /** + * Sets the connection's {@link PhoneCapabilities}. + * + * @param callCapabilities The new call capabilities. + */ + public final void setCallCapabilities(int callCapabilities) { + if (mCallCapabilities != callCapabilities) { + mCallCapabilities = callCapabilities; + for (Listener l : mListeners) { + l.onCallCapabilitiesChanged(this, mCallCapabilities); + } + } + } + + /** + * Tears down the Connection object. + */ + public final void destroy() { + for (Listener l : mListeners) { + l.onDestroyed(this); + } + } + + /** + * Requests that the framework use VOIP audio mode for this connection. + * + * @param isVoip True if the audio mode is VOIP. + */ + public final void setAudioModeIsVoip(boolean isVoip) { + mAudioModeIsVoip = isVoip; + for (Listener l : mListeners) { + l.onAudioModeIsVoipChanged(this, isVoip); + } + } + + /** + * Sets the label and icon status to display in the in-call UI. + * + * @param statusHints The status label and icon to set. + */ + public final void setStatusHints(StatusHints statusHints) { + mStatusHints = statusHints; + for (Listener l : mListeners) { + l.onStatusHintsChanged(this, statusHints); + } + } + + /** + * Sets the connections with which this connection can be conferenced. + * + * @param conferenceableConnections The set of connections this connection can conference with. + */ + public final void setConferenceableConnections(List<Connection> conferenceableConnections) { + clearConferenceableList(); + for (Connection c : conferenceableConnections) { + // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a + // small amount of items here. + if (!mConferenceableConnections.contains(c)) { + c.addConnectionListener(mConnectionDeathListener); + mConferenceableConnections.add(c); + } + } + fireOnConferenceableConnectionsChanged(); + } + + /** + * Obtains the connections with which this connection can be conferenced. + */ + public final List<Connection> getConferenceableConnections() { + return mUnmodifiableConferenceableConnections; + } + + /* + * @hide + */ + public final void setConnectionService(ConnectionService connectionService) { + if (mConnectionService != null) { + Log.e(this, new Exception(), "Trying to set ConnectionService on a connection " + + "which is already associated with another ConnectionService."); + } else { + mConnectionService = connectionService; + } + } + + /** + * @hide + */ + public final void unsetConnectionService(ConnectionService connectionService) { + if (mConnectionService != connectionService) { + Log.e(this, new Exception(), "Trying to remove ConnectionService from a Connection " + + "that does not belong to the ConnectionService."); + } else { + mConnectionService = null; + } + } + + /** + * Sets the conference that this connection is a part of. This will fail if the connection is + * already part of a conference call. {@link #resetConference} to un-set the conference first. + * + * @param conference The conference. + * @return {@code true} if the conference was successfully set. + * @hide + */ + public final boolean setConference(Conference conference) { + // We check to see if it is already part of another conference. + if (mConference == null) { + mConference = conference; + if (mConnectionService != null && mConnectionService.containsConference(conference)) { + fireConferenceChanged(); + } + return true; + } + return false; + } + + /** + * Resets the conference that this connection is a part of. + * @hide + */ + public final void resetConference() { + if (mConference != null) { + Log.d(this, "Conference reset"); + mConference = null; + fireConferenceChanged(); + } + } + + /** + * Notifies this Connection that the {@link #getAudioState()} property has a new value. + * + * @param state The new call audio state. + */ + public void onAudioStateChanged(AudioState state) {} + + /** + * Notifies this Connection of an internal state change. This method is called after the + * state is changed. + * + * @param state The new state, one of the {@code STATE_*} constants. + */ + public void onStateChanged(int state) {} + + /** + * Notifies this Connection of a request to play a DTMF tone. + * + * @param c A DTMF character. + */ + public void onPlayDtmfTone(char c) {} + + /** + * Notifies this Connection of a request to stop any currently playing DTMF tones. + */ + public void onStopDtmfTone() {} + + /** + * Notifies this Connection of a request to disconnect. + */ + public void onDisconnect() {} + + /** + * Notifies this Connection of a request to separate from its parent conference. + */ + public void onSeparate() {} + + /** + * Notifies this Connection of a request to abort. + */ + public void onAbort() {} + + /** + * Notifies this Connection of a request to hold. + */ + public void onHold() {} + + /** + * Notifies this Connection of a request to exit a hold state. + */ + public void onUnhold() {} + + /** + * Notifies this Connection, which is in {@link #STATE_RINGING}, of + * a request to accept. + * + * @param videoState The video state in which to answer the call. + * @hide + */ + public void onAnswer(int videoState) {} + + /** + * Notifies this Connection, which is in {@link #STATE_RINGING}, of + * a request to accept. + */ + public void onAnswer() { + onAnswer(VideoProfile.VideoState.AUDIO_ONLY); + } + + /** + * Notifies this Connection, which is in {@link #STATE_RINGING}, of + * a request to reject. + */ + public void onReject() {} + + /** + * Notifies this Connection whether the user wishes to proceed with the post-dial DTMF codes. + */ + public void onPostDialContinue(boolean proceed) {} + + /** + * Merge this connection and the specified connection into a conference call. Once the + * connections are merged, the calls should be added to the an existing or new + * {@code Conference} instance. For new {@code Conference} instances, use + * {@code ConnectionService#addConference}. + * + * @param otherConnection The connection with which this connection should be conferenced. + */ + public void onConferenceWith(Connection otherConnection) {} + + static String toLogSafePhoneNumber(String number) { + // For unknown number, log empty string. + if (number == null) { + return ""; + } + + if (PII_DEBUG) { + // When PII_DEBUG is true we emit PII. + return number; + } + + // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare + // sanitized phone numbers. + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < number.length(); i++) { + char c = number.charAt(i); + if (c == '-' || c == '@' || c == '.') { + builder.append(c); + } else { + builder.append('x'); + } + } + return builder.toString(); + } + + private void setState(int state) { + if (mState == STATE_DISCONNECTED && mState != state) { + Log.d(this, "Connection already DISCONNECTED; cannot transition out of this state."); + return; + } + if (mState != state) { + Log.d(this, "setState: %s", stateToString(state)); + mState = state; + onStateChanged(state); + for (Listener l : mListeners) { + l.onStateChanged(this, state); + } + } + } + + private static class FailureSignalingConnection extends Connection { + public FailureSignalingConnection(DisconnectCause disconnectCause) { + setDisconnected(disconnectCause); + } + } + + /** + * Return a {@code Connection} which represents a failed connection attempt. The returned + * {@code Connection} will have a {@link android.telecom.DisconnectCause} and as specified, + * and a {@link #getState()} of {@link #STATE_DISCONNECTED}. + * <p> + * The returned {@code Connection} can be assumed to {@link #destroy()} itself when appropriate, + * so users of this method need not maintain a reference to its return value to destroy it. + * + * @param disconnectCause The disconnect cause, ({@see android.telecomm.DisconnectCause}). + * @return A {@code Connection} which indicates failure. + */ + public static Connection createFailedConnection(DisconnectCause disconnectCause) { + return new FailureSignalingConnection(disconnectCause); + } + + /** + * Return a {@code Connection} which represents a canceled connection attempt. The returned + * {@code Connection} will have state {@link #STATE_DISCONNECTED}, and cannot be moved out of + * that state. This connection should not be used for anything, and no other + * {@code Connection}s should be attempted. + * <p> + * The returned {@code Connection} can be assumed to {@link #destroy()} itself when appropriate, + * so users of this method need not maintain a reference to its return value to destroy it. + * + * @return A {@code Connection} which indicates that the underlying call should be canceled. + */ + public static Connection createCanceledConnection() { + return new FailureSignalingConnection(new DisconnectCause(DisconnectCause.CANCELED)); + } + + private final void fireOnConferenceableConnectionsChanged() { + for (Listener l : mListeners) { + l.onConferenceableConnectionsChanged(this, mConferenceableConnections); + } + } + + private final void fireConferenceChanged() { + for (Listener l : mListeners) { + l.onConferenceChanged(this, mConference); + } + } + + private final void clearConferenceableList() { + for (Connection c : mConferenceableConnections) { + c.removeConnectionListener(mConnectionDeathListener); + } + mConferenceableConnections.clear(); + } +} diff --git a/telecomm/java/android/telecom/ConnectionRequest.aidl b/telecomm/java/android/telecom/ConnectionRequest.aidl new file mode 100644 index 0000000..de39c67 --- /dev/null +++ b/telecomm/java/android/telecom/ConnectionRequest.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 2014, 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 android.telecom; + +/** + * {@hide} + */ +parcelable ConnectionRequest; diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java new file mode 100644 index 0000000..71b481b --- /dev/null +++ b/telecomm/java/android/telecom/ConnectionRequest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Simple data container encapsulating a request to some entity to + * create a new {@link Connection}. + */ +public final class ConnectionRequest implements Parcelable { + + // TODO: Token to limit recursive invocations + private final PhoneAccountHandle mAccountHandle; + private final Uri mAddress; + private final Bundle mExtras; + private final int mVideoState; + + /** + * @param accountHandle The accountHandle which should be used to place the call. + * @param handle The handle (e.g., phone number) to which the {@link Connection} is to connect. + * @param extras Application-specific extra data. + */ + public ConnectionRequest( + PhoneAccountHandle accountHandle, + Uri handle, + Bundle extras) { + this(accountHandle, handle, extras, VideoProfile.VideoState.AUDIO_ONLY); + } + + /** + * @param accountHandle The accountHandle which should be used to place the call. + * @param handle The handle (e.g., phone number) to which the {@link Connection} is to connect. + * @param extras Application-specific extra data. + * @param videoState Determines the video state for the connection. + * @hide + */ + public ConnectionRequest( + PhoneAccountHandle accountHandle, + Uri handle, + Bundle extras, + int videoState) { + mAccountHandle = accountHandle; + mAddress = handle; + mExtras = extras; + mVideoState = videoState; + } + + private ConnectionRequest(Parcel in) { + mAccountHandle = in.readParcelable(getClass().getClassLoader()); + mAddress = in.readParcelable(getClass().getClassLoader()); + mExtras = in.readParcelable(getClass().getClassLoader()); + mVideoState = in.readInt(); + } + + /** + * The account which should be used to place the call. + */ + public PhoneAccountHandle getAccountHandle() { return mAccountHandle; } + + /** + * The handle (e.g., phone number) to which the {@link Connection} is to connect. + */ + public Uri getAddress() { return mAddress; } + + /** + * Application-specific extra data. Used for passing back information from an incoming + * call {@code Intent}, and for any proprietary extensions arranged between a client + * and servant {@code ConnectionService} which agree on a vocabulary for such data. + */ + public Bundle getExtras() { return mExtras; } + + /** + * Describes the video states supported by the client requesting the connection. + * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY}, + * {@link VideoProfile.VideoState#BIDIRECTIONAL}, + * {@link VideoProfile.VideoState#TX_ENABLED}, + * {@link VideoProfile.VideoState#RX_ENABLED}. + * + * @return The video state for the connection. + * @hide + */ + public int getVideoState() { + return mVideoState; + } + + @Override + public String toString() { + return String.format("ConnectionRequest %s %s", + mAddress == null + ? Uri.EMPTY + : Connection.toLogSafePhoneNumber(mAddress.toString()), + mExtras == null ? "" : mExtras); + } + + public static final Creator<ConnectionRequest> CREATOR = new Creator<ConnectionRequest> () { + @Override + public ConnectionRequest createFromParcel(Parcel source) { + return new ConnectionRequest(source); + } + + @Override + public ConnectionRequest[] newArray(int size) { + return new ConnectionRequest[size]; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeParcelable(mAccountHandle, 0); + destination.writeParcelable(mAddress, 0); + destination.writeParcelable(mExtras, 0); + destination.writeInt(mVideoState); + } +} diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java new file mode 100644 index 0000000..3e18bac --- /dev/null +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -0,0 +1,984 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.annotation.SdkConstant; +import android.app.Service; +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; + +import com.android.internal.os.SomeArgs; +import com.android.internal.telecom.IConnectionService; +import com.android.internal.telecom.IConnectionServiceAdapter; +import com.android.internal.telecom.RemoteServiceCallback; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * A {@link android.app.Service} that provides telephone connections to processes running on an + * Android device. + */ +public abstract class ConnectionService extends Service { + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService"; + + // Flag controlling whether PII is emitted into the logs + private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); + + private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1; + private static final int MSG_CREATE_CONNECTION = 2; + private static final int MSG_ABORT = 3; + private static final int MSG_ANSWER = 4; + private static final int MSG_REJECT = 5; + private static final int MSG_DISCONNECT = 6; + private static final int MSG_HOLD = 7; + private static final int MSG_UNHOLD = 8; + private static final int MSG_ON_AUDIO_STATE_CHANGED = 9; + private static final int MSG_PLAY_DTMF_TONE = 10; + private static final int MSG_STOP_DTMF_TONE = 11; + private static final int MSG_CONFERENCE = 12; + private static final int MSG_SPLIT_FROM_CONFERENCE = 13; + private static final int MSG_ON_POST_DIAL_CONTINUE = 14; + private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16; + private static final int MSG_ANSWER_VIDEO = 17; + private static final int MSG_MERGE_CONFERENCE = 18; + private static final int MSG_SWAP_CONFERENCE = 19; + + private static Connection sNullConnection; + + private final Map<String, Connection> mConnectionById = new HashMap<>(); + private final Map<Connection, String> mIdByConnection = new HashMap<>(); + private final Map<String, Conference> mConferenceById = new HashMap<>(); + private final Map<Conference, String> mIdByConference = new HashMap<>(); + private final RemoteConnectionManager mRemoteConnectionManager = + new RemoteConnectionManager(this); + private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>(); + private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter(); + + private boolean mAreAccountsInitialized = false; + private Conference sNullConference; + + private final IBinder mBinder = new IConnectionService.Stub() { + @Override + public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) { + mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget(); + } + + public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) { + mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget(); + } + + @Override + public void createConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + String id, + ConnectionRequest request, + boolean isIncoming) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionManagerPhoneAccount; + args.arg2 = id; + args.arg3 = request; + args.argi1 = isIncoming ? 1 : 0; + mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget(); + } + + @Override + public void abort(String callId) { + mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget(); + } + + @Override + /** @hide */ + public void answerVideo(String callId, int videoState) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.argi1 = videoState; + mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget(); + } + + @Override + public void answer(String callId) { + mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget(); + } + + @Override + public void reject(String callId) { + mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget(); + } + + @Override + public void disconnect(String callId) { + mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget(); + } + + @Override + public void hold(String callId) { + mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget(); + } + + @Override + public void unhold(String callId) { + mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget(); + } + + @Override + public void onAudioStateChanged(String callId, AudioState audioState) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = audioState; + mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget(); + } + + @Override + public void playDtmfTone(String callId, char digit) { + mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget(); + } + + @Override + public void stopDtmfTone(String callId) { + mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget(); + } + + @Override + public void conference(String callId1, String callId2) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId1; + args.arg2 = callId2; + mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget(); + } + + @Override + public void splitFromConference(String callId) { + mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget(); + } + + @Override + public void mergeConference(String callId) { + mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget(); + } + + @Override + public void swapConference(String callId) { + mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget(); + } + + @Override + public void onPostDialContinue(String callId, boolean proceed) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.argi1 = proceed ? 1 : 0; + mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget(); + } + }; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ADD_CONNECTION_SERVICE_ADAPTER: + mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj); + onAdapterAttached(); + break; + case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER: + mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj); + break; + case MSG_CREATE_CONNECTION: { + SomeArgs args = (SomeArgs) msg.obj; + try { + final PhoneAccountHandle connectionManagerPhoneAccount = + (PhoneAccountHandle) args.arg1; + final String id = (String) args.arg2; + final ConnectionRequest request = (ConnectionRequest) args.arg3; + final boolean isIncoming = args.argi1 == 1; + if (!mAreAccountsInitialized) { + Log.d(this, "Enqueueing pre-init request %s", id); + mPreInitializationConnectionRequests.add(new Runnable() { + @Override + public void run() { + createConnection( + connectionManagerPhoneAccount, + id, + request, + isIncoming); + } + }); + } else { + createConnection( + connectionManagerPhoneAccount, + id, + request, + isIncoming); + } + } finally { + args.recycle(); + } + break; + } + case MSG_ABORT: + abort((String) msg.obj); + break; + case MSG_ANSWER: + answer((String) msg.obj); + break; + case MSG_ANSWER_VIDEO: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + int videoState = args.argi1; + answerVideo(callId, videoState); + } finally { + args.recycle(); + } + break; + } + case MSG_REJECT: + reject((String) msg.obj); + break; + case MSG_DISCONNECT: + disconnect((String) msg.obj); + break; + case MSG_HOLD: + hold((String) msg.obj); + break; + case MSG_UNHOLD: + unhold((String) msg.obj); + break; + case MSG_ON_AUDIO_STATE_CHANGED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + AudioState audioState = (AudioState) args.arg2; + onAudioStateChanged(callId, audioState); + } finally { + args.recycle(); + } + break; + } + case MSG_PLAY_DTMF_TONE: + playDtmfTone((String) msg.obj, (char) msg.arg1); + break; + case MSG_STOP_DTMF_TONE: + stopDtmfTone((String) msg.obj); + break; + case MSG_CONFERENCE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId1 = (String) args.arg1; + String callId2 = (String) args.arg2; + conference(callId1, callId2); + } finally { + args.recycle(); + } + break; + } + case MSG_SPLIT_FROM_CONFERENCE: + splitFromConference((String) msg.obj); + break; + case MSG_MERGE_CONFERENCE: + mergeConference((String) msg.obj); + break; + case MSG_SWAP_CONFERENCE: + swapConference((String) msg.obj); + break; + case MSG_ON_POST_DIAL_CONTINUE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + boolean proceed = (args.argi1 == 1); + onPostDialContinue(callId, proceed); + } finally { + args.recycle(); + } + break; + } + default: + break; + } + } + }; + + private final Conference.Listener mConferenceListener = new Conference.Listener() { + @Override + public void onStateChanged(Conference conference, int oldState, int newState) { + String id = mIdByConference.get(conference); + switch (newState) { + case Connection.STATE_ACTIVE: + mAdapter.setActive(id); + break; + case Connection.STATE_HOLDING: + mAdapter.setOnHold(id); + break; + case Connection.STATE_DISCONNECTED: + // handled by onDisconnected + break; + } + } + + @Override + public void onDisconnected(Conference conference, DisconnectCause disconnectCause) { + String id = mIdByConference.get(conference); + mAdapter.setDisconnected(id, disconnectCause); + } + + @Override + public void onConnectionAdded(Conference conference, Connection connection) { + } + + @Override + public void onConnectionRemoved(Conference conference, Connection connection) { + } + + @Override + public void onDestroyed(Conference conference) { + removeConference(conference); + } + + @Override + public void onCapabilitiesChanged(Conference conference, int capabilities) { + String id = mIdByConference.get(conference); + Log.d(this, "call capabilities: conference: %s", + PhoneCapabilities.toString(capabilities)); + mAdapter.setCallCapabilities(id, capabilities); + } + }; + + private final Connection.Listener mConnectionListener = new Connection.Listener() { + @Override + public void onStateChanged(Connection c, int state) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state)); + switch (state) { + case Connection.STATE_ACTIVE: + mAdapter.setActive(id); + break; + case Connection.STATE_DIALING: + mAdapter.setDialing(id); + break; + case Connection.STATE_DISCONNECTED: + // Handled in onDisconnected() + break; + case Connection.STATE_HOLDING: + mAdapter.setOnHold(id); + break; + case Connection.STATE_NEW: + // Nothing to tell Telecom + break; + case Connection.STATE_RINGING: + mAdapter.setRinging(id); + break; + } + } + + @Override + public void onDisconnected(Connection c, DisconnectCause disconnectCause) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter set disconnected %s", disconnectCause); + mAdapter.setDisconnected(id, disconnectCause); + } + + @Override + public void onVideoStateChanged(Connection c, int videoState) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter set video state %d", videoState); + mAdapter.setVideoState(id, videoState); + } + + @Override + public void onAddressChanged(Connection c, Uri address, int presentation) { + String id = mIdByConnection.get(c); + mAdapter.setAddress(id, address, presentation); + } + + @Override + public void onCallerDisplayNameChanged( + Connection c, String callerDisplayName, int presentation) { + String id = mIdByConnection.get(c); + mAdapter.setCallerDisplayName(id, callerDisplayName, presentation); + } + + @Override + public void onDestroyed(Connection c) { + removeConnection(c); + } + + @Override + public void onPostDialWait(Connection c, String remaining) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining); + mAdapter.onPostDialWait(id, remaining); + } + + @Override + public void onRingbackRequested(Connection c, boolean ringback) { + String id = mIdByConnection.get(c); + Log.d(this, "Adapter onRingback %b", ringback); + mAdapter.setRingbackRequested(id, ringback); + } + + @Override + public void onCallCapabilitiesChanged(Connection c, int capabilities) { + String id = mIdByConnection.get(c); + Log.d(this, "capabilities: parcelableconnection: %s", + PhoneCapabilities.toString(capabilities)); + mAdapter.setCallCapabilities(id, capabilities); + } + + @Override + public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) { + String id = mIdByConnection.get(c); + mAdapter.setVideoProvider(id, videoProvider); + } + + @Override + public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) { + String id = mIdByConnection.get(c); + mAdapter.setIsVoipAudioMode(id, isVoip); + } + + @Override + public void onStatusHintsChanged(Connection c, StatusHints statusHints) { + String id = mIdByConnection.get(c); + mAdapter.setStatusHints(id, statusHints); + } + + @Override + public void onConferenceableConnectionsChanged( + Connection connection, List<Connection> conferenceableConnections) { + mAdapter.setConferenceableConnections( + mIdByConnection.get(connection), + createConnectionIdList(conferenceableConnections)); + } + + @Override + public void onConferenceChanged(Connection connection, Conference conference) { + String id = mIdByConnection.get(connection); + if (id != null) { + String conferenceId = null; + if (conference != null) { + conferenceId = mIdByConference.get(conference); + } + mAdapter.setIsConferenced(id, conferenceId); + } + } + }; + + /** {@inheritDoc} */ + @Override + public final IBinder onBind(Intent intent) { + return mBinder; + } + + /** {@inheritDoc} */ + @Override + public boolean onUnbind(Intent intent) { + endAllConnections(); + return super.onUnbind(intent); + } + + /** + * This can be used by telecom to either create a new outgoing call or attach to an existing + * incoming call. In either case, telecom will cycle through a set of services and call + * createConnection util a connection service cancels the process or completes it successfully. + */ + private void createConnection( + final PhoneAccountHandle callManagerAccount, + final String callId, + final ConnectionRequest request, + boolean isIncoming) { + Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + + "isIncoming: %b", callManagerAccount, callId, request, isIncoming); + + Connection connection = isIncoming + ? onCreateIncomingConnection(callManagerAccount, request) + : onCreateOutgoingConnection(callManagerAccount, request); + Log.d(this, "createConnection, connection: %s", connection); + if (connection == null) { + connection = Connection.createFailedConnection( + new DisconnectCause(DisconnectCause.ERROR)); + } + + if (connection.getState() != Connection.STATE_DISCONNECTED) { + addConnection(callId, connection); + } + + Uri address = connection.getAddress(); + String number = address == null ? "null" : address.getSchemeSpecificPart(); + Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s", + Connection.toLogSafePhoneNumber(number), + Connection.stateToString(connection.getState()), + PhoneCapabilities.toString(connection.getCallCapabilities())); + + Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId); + mAdapter.handleCreateConnectionComplete( + callId, + request, + new ParcelableConnection( + request.getAccountHandle(), + connection.getState(), + connection.getCallCapabilities(), + connection.getAddress(), + connection.getAddressPresentation(), + connection.getCallerDisplayName(), + connection.getCallerDisplayNamePresentation(), + connection.getVideoProvider() == null ? + null : connection.getVideoProvider().getInterface(), + connection.getVideoState(), + connection.isRingbackRequested(), + connection.getAudioModeIsVoip(), + connection.getStatusHints(), + connection.getDisconnectCause(), + createConnectionIdList(connection.getConferenceableConnections()))); + } + + private void abort(String callId) { + Log.d(this, "abort %s", callId); + findConnectionForAction(callId, "abort").onAbort(); + } + + private void answerVideo(String callId, int videoState) { + Log.d(this, "answerVideo %s", callId); + findConnectionForAction(callId, "answer").onAnswer(videoState); + } + + private void answer(String callId) { + Log.d(this, "answer %s", callId); + findConnectionForAction(callId, "answer").onAnswer(); + } + + private void reject(String callId) { + Log.d(this, "reject %s", callId); + findConnectionForAction(callId, "reject").onReject(); + } + + private void disconnect(String callId) { + Log.d(this, "disconnect %s", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "disconnect").onDisconnect(); + } else { + findConferenceForAction(callId, "disconnect").onDisconnect(); + } + } + + private void hold(String callId) { + Log.d(this, "hold %s", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "hold").onHold(); + } else { + findConferenceForAction(callId, "hold").onHold(); + } + } + + private void unhold(String callId) { + Log.d(this, "unhold %s", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "unhold").onUnhold(); + } else { + findConferenceForAction(callId, "unhold").onUnhold(); + } + } + + private void onAudioStateChanged(String callId, AudioState audioState) { + Log.d(this, "onAudioStateChanged %s %s", callId, audioState); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState); + } else { + findConferenceForAction(callId, "onAudioStateChanged").setAudioState(audioState); + } + } + + private void playDtmfTone(String callId, char digit) { + Log.d(this, "playDtmfTone %s %c", callId, digit); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); + } else { + findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); + } + } + + private void stopDtmfTone(String callId) { + Log.d(this, "stopDtmfTone %s", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); + } else { + findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone(); + } + } + + private void conference(String callId1, String callId2) { + Log.d(this, "conference %s, %s", callId1, callId2); + + Connection connection1 = findConnectionForAction(callId1, "conference"); + if (connection1 == getNullConnection()) { + Log.w(this, "Connection1 missing in conference request %s.", callId1); + return; + } + + Connection connection2 = findConnectionForAction(callId2, "conference"); + if (connection2 == getNullConnection()) { + Log.w(this, "Connection2 missing in conference request %s.", callId2); + return; + } + + onConference(connection1, connection2); + } + + private void splitFromConference(String callId) { + Log.d(this, "splitFromConference(%s)", callId); + + Connection connection = findConnectionForAction(callId, "splitFromConference"); + if (connection == getNullConnection()) { + Log.w(this, "Connection missing in conference request %s.", callId); + return; + } + + Conference conference = connection.getConference(); + if (conference != null) { + conference.onSeparate(connection); + } + } + + private void mergeConference(String callId) { + Log.d(this, "mergeConference(%s)", callId); + Conference conference = findConferenceForAction(callId, "mergeConference"); + if (conference != null) { + conference.onMerge(); + } + } + + private void swapConference(String callId) { + Log.d(this, "swapConference(%s)", callId); + Conference conference = findConferenceForAction(callId, "swapConference"); + if (conference != null) { + conference.onSwap(); + } + } + + private void onPostDialContinue(String callId, boolean proceed) { + Log.d(this, "onPostDialContinue(%s)", callId); + findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed); + } + + private void onAdapterAttached() { + if (mAreAccountsInitialized) { + // No need to query again if we already did it. + return; + } + + mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() { + @Override + public void onResult( + final List<ComponentName> componentNames, + final List<IBinder> services) { + mHandler.post(new Runnable() { + @Override + public void run() { + for (int i = 0; i < componentNames.size() && i < services.size(); i++) { + mRemoteConnectionManager.addConnectionService( + componentNames.get(i), + IConnectionService.Stub.asInterface(services.get(i))); + } + onAccountsInitialized(); + Log.d(this, "remote connection services found: " + services); + } + }); + } + + @Override + public void onError() { + mHandler.post(new Runnable() { + @Override + public void run() { + mAreAccountsInitialized = true; + } + }); + } + }); + } + + /** + * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an + * incoming request. This is used to attach to existing incoming calls. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request Details about the incoming call. + * @return The {@code Connection} object to satisfy this call, or {@code null} to + * not handle the call. + */ + public final RemoteConnection createRemoteIncomingConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { + return mRemoteConnectionManager.createRemoteConnection( + connectionManagerPhoneAccount, request, true); + } + + /** + * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an + * outgoing request. This is used to initiate new outgoing calls. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request Details about the incoming call. + * @return The {@code Connection} object to satisfy this call, or {@code null} to + * not handle the call. + */ + public final RemoteConnection createRemoteOutgoingConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { + return mRemoteConnectionManager.createRemoteConnection( + connectionManagerPhoneAccount, request, false); + } + + /** + * Adds two {@code RemoteConnection}s to some {@code RemoteConference}. + */ + public final void conferenceRemoteConnections( + RemoteConnection a, + RemoteConnection b) { + mRemoteConnectionManager.conferenceRemoteConnections(a, b); + } + + /** + * Adds a new conference call. When a conference call is created either as a result of an + * explicit request via {@link #onConference} or otherwise, the connection service should supply + * an instance of {@link Conference} by invoking this method. A conference call provided by this + * method will persist until {@link Conference#destroy} is invoked on the conference instance. + * + * @param conference The new conference object. + */ + public final void addConference(Conference conference) { + String id = addConferenceInternal(conference); + if (id != null) { + List<String> connectionIds = new ArrayList<>(2); + for (Connection connection : conference.getConnections()) { + if (mIdByConnection.containsKey(connection)) { + connectionIds.add(mIdByConnection.get(connection)); + } + } + ParcelableConference parcelableConference = new ParcelableConference( + conference.getPhoneAccountHandle(), + conference.getState(), + conference.getCapabilities(), + connectionIds); + mAdapter.addConferenceCall(id, parcelableConference); + + // Go through any child calls and set the parent. + for (Connection connection : conference.getConnections()) { + String connectionId = mIdByConnection.get(connection); + if (connectionId != null) { + mAdapter.setIsConferenced(connectionId, id); + } + } + } + } + + /** + * Returns all the active {@code Connection}s for which this {@code ConnectionService} + * has taken responsibility. + * + * @return A collection of {@code Connection}s created by this {@code ConnectionService}. + */ + public final Collection<Connection> getAllConnections() { + return mConnectionById.values(); + } + + /** + * Create a {@code Connection} given an incoming request. This is used to attach to existing + * incoming calls. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request Details about the incoming call. + * @return The {@code Connection} object to satisfy this call, or {@code null} to + * not handle the call. + */ + public Connection onCreateIncomingConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { + return null; + } + + /** + * Create a {@code Connection} given an outgoing request. This is used to initiate new + * outgoing calls. + * + * @param connectionManagerPhoneAccount The connection manager account to use for managing + * this call. + * <p> + * If this parameter is not {@code null}, it means that this {@code ConnectionService} + * has registered one or more {@code PhoneAccount}s having + * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain + * one of these {@code PhoneAccount}s, while the {@code request} will contain another + * (usually but not always distinct) {@code PhoneAccount} to be used for actually + * making the connection. + * <p> + * If this parameter is {@code null}, it means that this {@code ConnectionService} is + * being asked to make a direct connection. The + * {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be + * a {@code PhoneAccount} registered by this {@code ConnectionService} to use for + * making the connection. + * @param request Details about the outgoing call. + * @return The {@code Connection} object to satisfy this call, or the result of an invocation + * of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call. + */ + public Connection onCreateOutgoingConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { + return null; + } + + /** + * Conference two specified connections. Invoked when the user has made a request to merge the + * specified connections into a conference call. In response, the connection service should + * create an instance of {@link Conference} and pass it into {@link #addConference}. + * + * @param connection1 A connection to merge into a conference call. + * @param connection2 A connection to merge into a conference call. + */ + public void onConference(Connection connection1, Connection connection2) {} + + public void onRemoteConferenceAdded(RemoteConference conference) {} + + /** + * @hide + */ + public boolean containsConference(Conference conference) { + return mIdByConference.containsKey(conference); + } + + /** {@hide} */ + void addRemoteConference(RemoteConference remoteConference) { + onRemoteConferenceAdded(remoteConference); + } + + private void onAccountsInitialized() { + mAreAccountsInitialized = true; + for (Runnable r : mPreInitializationConnectionRequests) { + r.run(); + } + mPreInitializationConnectionRequests.clear(); + } + + private void addConnection(String callId, Connection connection) { + mConnectionById.put(callId, connection); + mIdByConnection.put(connection, callId); + connection.addConnectionListener(mConnectionListener); + connection.setConnectionService(this); + } + + private void removeConnection(Connection connection) { + String id = mIdByConnection.get(connection); + connection.unsetConnectionService(this); + connection.removeConnectionListener(mConnectionListener); + mConnectionById.remove(mIdByConnection.get(connection)); + mIdByConnection.remove(connection); + mAdapter.removeCall(id); + } + + private String addConferenceInternal(Conference conference) { + if (mIdByConference.containsKey(conference)) { + Log.w(this, "Re-adding an existing conference: %s.", conference); + } else if (conference != null) { + String id = UUID.randomUUID().toString(); + mConferenceById.put(id, conference); + mIdByConference.put(conference, id); + conference.addListener(mConferenceListener); + return id; + } + + return null; + } + + private void removeConference(Conference conference) { + if (mIdByConference.containsKey(conference)) { + conference.removeListener(mConferenceListener); + + String id = mIdByConference.get(conference); + mConferenceById.remove(id); + mIdByConference.remove(conference); + mAdapter.removeCall(id); + } + } + + private Connection findConnectionForAction(String callId, String action) { + if (mConnectionById.containsKey(callId)) { + return mConnectionById.get(callId); + } + Log.w(this, "%s - Cannot find Connection %s", action, callId); + return getNullConnection(); + } + + static synchronized Connection getNullConnection() { + if (sNullConnection == null) { + sNullConnection = new Connection() {}; + } + return sNullConnection; + } + + private Conference findConferenceForAction(String conferenceId, String action) { + if (mConferenceById.containsKey(conferenceId)) { + return mConferenceById.get(conferenceId); + } + Log.w(this, "%s - Cannot find conference %s", action, conferenceId); + return getNullConference(); + } + + private List<String> createConnectionIdList(List<Connection> connections) { + List<String> ids = new ArrayList<>(); + for (Connection c : connections) { + if (mIdByConnection.containsKey(c)) { + ids.add(mIdByConnection.get(c)); + } + } + Collections.sort(ids); + return ids; + } + + private Conference getNullConference() { + if (sNullConference == null) { + sNullConference = new Conference(null) {}; + } + return sNullConference; + } + + private void endAllConnections() { + // Unbound from telecomm. We should end all connections and conferences. + for (Connection connection : mIdByConnection.keySet()) { + // only operate on top-level calls. Conference calls will be removed on their own. + if (connection.getConference() == null) { + connection.onDisconnect(); + } + } + for (Conference conference : mIdByConference.keySet()) { + conference.onDisconnect(); + } + } +} diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java new file mode 100644 index 0000000..c676172 --- /dev/null +++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.net.Uri; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; + +import com.android.internal.telecom.IConnectionServiceAdapter; +import com.android.internal.telecom.RemoteServiceCallback; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Provides methods for IConnectionService implementations to interact with the system phone app. + * + * @hide + */ +final class ConnectionServiceAdapter implements DeathRecipient { + /** + * 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<IConnectionServiceAdapter> mAdapters = Collections.newSetFromMap( + new ConcurrentHashMap<IConnectionServiceAdapter, Boolean>(8, 0.9f, 1)); + + ConnectionServiceAdapter() { + } + + void addAdapter(IConnectionServiceAdapter adapter) { + if (mAdapters.add(adapter)) { + try { + adapter.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + mAdapters.remove(adapter); + } + } + } + + void removeAdapter(IConnectionServiceAdapter adapter) { + if (adapter != null && mAdapters.remove(adapter)) { + adapter.asBinder().unlinkToDeath(this, 0); + } + } + + /** ${inheritDoc} */ + @Override + public void binderDied() { + Iterator<IConnectionServiceAdapter> it = mAdapters.iterator(); + while (it.hasNext()) { + IConnectionServiceAdapter adapter = it.next(); + if (!adapter.asBinder().isBinderAlive()) { + it.remove(); + adapter.asBinder().unlinkToDeath(this, 0); + } + } + } + + void handleCreateConnectionComplete( + String id, + ConnectionRequest request, + ParcelableConnection connection) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.handleCreateConnectionComplete(id, request, connection); + } catch (RemoteException e) { + } + } + } + + /** + * Sets a call's state to active (e.g., an ongoing call where two parties can actively + * communicate). + * + * @param callId The unique ID of the call whose state is changing to active. + */ + void setActive(String callId) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setActive(callId); + } catch (RemoteException e) { + } + } + } + + /** + * Sets a call's state to ringing (e.g., an inbound ringing call). + * + * @param callId The unique ID of the call whose state is changing to ringing. + */ + void setRinging(String callId) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setRinging(callId); + } catch (RemoteException e) { + } + } + } + + /** + * Sets a call's state to dialing (e.g., dialing an outbound call). + * + * @param callId The unique ID of the call whose state is changing to dialing. + */ + void setDialing(String callId) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setDialing(callId); + } catch (RemoteException e) { + } + } + } + + /** + * Sets a call's state to disconnected. + * + * @param callId The unique ID of the call whose state is changing to disconnected. + * @param disconnectCause The reason for the disconnection, as described by + * {@link android.telecomm.DisconnectCause}. + */ + void setDisconnected(String callId, DisconnectCause disconnectCause) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setDisconnected(callId, disconnectCause); + } catch (RemoteException e) { + } + } + } + + /** + * Sets a call's state to be on hold. + * + * @param callId - The unique ID of the call whose state is changing to be on hold. + */ + void setOnHold(String callId) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setOnHold(callId); + } catch (RemoteException e) { + } + } + } + + /** + * Asks Telecom to start or stop a ringback tone for a call. + * + * @param callId The unique ID of the call whose ringback is being changed. + * @param ringback Whether Telecom should start playing a ringback tone. + */ + void setRingbackRequested(String callId, boolean ringback) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setRingbackRequested(callId, ringback); + } catch (RemoteException e) { + } + } + } + + void setCallCapabilities(String callId, int capabilities) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setCallCapabilities(callId, capabilities); + } catch (RemoteException ignored) { + } + } + } + + /** + * Indicates whether or not the specified call is currently conferenced into the specified + * conference call. + * + * @param callId The unique ID of the call being conferenced. + * @param conferenceCallId The unique ID of the conference call. Null if call is not + * conferenced. + */ + void setIsConferenced(String callId, String conferenceCallId) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + Log.d(this, "sending connection %s with conference %s", callId, conferenceCallId); + adapter.setIsConferenced(callId, conferenceCallId); + } catch (RemoteException ignored) { + } + } + } + + /** + * Indicates that the call no longer exists. Can be used with either a call or a conference + * call. + * + * @param callId The unique ID of the call. + */ + void removeCall(String callId) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.removeCall(callId); + } catch (RemoteException ignored) { + } + } + } + + void onPostDialWait(String callId, String remaining) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.onPostDialWait(callId, remaining); + } catch (RemoteException ignored) { + } + } + } + + /** + * Indicates that a new conference call has been created. + * + * @param callId The unique ID of the conference call. + */ + void addConferenceCall(String callId, ParcelableConference parcelableConference) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.addConferenceCall(callId, parcelableConference); + } catch (RemoteException ignored) { + } + } + } + + /** + * Retrieves a list of remote connection services usable to place calls. + */ + void queryRemoteConnectionServices(RemoteServiceCallback callback) { + // Only supported when there is only one adapter. + if (mAdapters.size() == 1) { + try { + mAdapters.iterator().next().queryRemoteConnectionServices(callback); + } catch (RemoteException e) { + Log.e(this, e, "Exception trying to query for remote CSs"); + } + } + } + + /** + * Sets the call video provider for a call. + * + * @param callId The unique ID of the call to set with the given call video provider. + * @param videoProvider The call video provider instance to set on the call. + */ + void setVideoProvider( + String callId, Connection.VideoProvider videoProvider) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setVideoProvider( + callId, + videoProvider == null ? null : videoProvider.getInterface()); + } catch (RemoteException e) { + } + } + } + + /** + * Requests that the framework use VOIP audio mode for this connection. + * + * @param callId The unique ID of the call to set with the given call video provider. + * @param isVoip True if the audio mode is VOIP. + */ + void setIsVoipAudioMode(String callId, boolean isVoip) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setIsVoipAudioMode(callId, isVoip); + } catch (RemoteException e) { + } + } + } + + void setStatusHints(String callId, StatusHints statusHints) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setStatusHints(callId, statusHints); + } catch (RemoteException e) { + } + } + } + + void setAddress(String callId, Uri address, int presentation) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setAddress(callId, address, presentation); + } catch (RemoteException e) { + } + } + } + + void setCallerDisplayName(String callId, String callerDisplayName, int presentation) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setCallerDisplayName(callId, callerDisplayName, presentation); + } catch (RemoteException e) { + } + } + } + + /** + * Sets the video state associated with a call. + * + * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY}, + * {@link VideoProfile.VideoState#BIDIRECTIONAL}, + * {@link VideoProfile.VideoState#TX_ENABLED}, + * {@link VideoProfile.VideoState#RX_ENABLED}. + * + * @param callId The unique ID of the call to set the video state for. + * @param videoState The video state. + */ + void setVideoState(String callId, int videoState) { + Log.v(this, "setVideoState: %d", videoState); + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setVideoState(callId, videoState); + } catch (RemoteException ignored) { + } + } + } + + void setConferenceableConnections(String callId, List<String> conferenceableCallIds) { + Log.v(this, "setConferenceableConnections: %s, %s", callId, conferenceableCallIds); + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setConferenceableConnections(callId, conferenceableCallIds); + } catch (RemoteException ignored) { + } + } + } +} diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java new file mode 100644 index 0000000..217dbc3 --- /dev/null +++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2014 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 + R* limitations under the License. + */ + +package android.telecom; + +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; + +import com.android.internal.os.SomeArgs; +import com.android.internal.telecom.IConnectionServiceAdapter; +import com.android.internal.telecom.IVideoProvider; +import com.android.internal.telecom.RemoteServiceCallback; + +import java.util.List; + +/** + * A component that provides an RPC servant implementation of {@link IConnectionServiceAdapter}, + * posting incoming messages on the main thread on a client-supplied delegate object. + * + * TODO: Generate this and similar classes using a compiler starting from AIDL interfaces. + * + * @hide + */ +final class ConnectionServiceAdapterServant { + private static final int MSG_HANDLE_CREATE_CONNECTION_COMPLETE = 1; + private static final int MSG_SET_ACTIVE = 2; + private static final int MSG_SET_RINGING = 3; + private static final int MSG_SET_DIALING = 4; + private static final int MSG_SET_DISCONNECTED = 5; + private static final int MSG_SET_ON_HOLD = 6; + private static final int MSG_SET_RINGBACK_REQUESTED = 7; + private static final int MSG_SET_CALL_CAPABILITIES = 8; + private static final int MSG_SET_IS_CONFERENCED = 9; + private static final int MSG_ADD_CONFERENCE_CALL = 10; + private static final int MSG_REMOVE_CALL = 11; + private static final int MSG_ON_POST_DIAL_WAIT = 12; + private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 13; + private static final int MSG_SET_VIDEO_STATE = 14; + private static final int MSG_SET_VIDEO_CALL_PROVIDER = 15; + private static final int MSG_SET_IS_VOIP_AUDIO_MODE = 16; + private static final int MSG_SET_STATUS_HINTS = 17; + private static final int MSG_SET_ADDRESS = 18; + private static final int MSG_SET_CALLER_DISPLAY_NAME = 19; + private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 20; + + private final IConnectionServiceAdapter mDelegate; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + try { + internalHandleMessage(msg); + } catch (RemoteException e) { + } + } + + // Internal method defined to centralize handling of RemoteException + private void internalHandleMessage(Message msg) throws RemoteException { + switch (msg.what) { + case MSG_HANDLE_CREATE_CONNECTION_COMPLETE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.handleCreateConnectionComplete( + (String) args.arg1, + (ConnectionRequest) args.arg2, + (ParcelableConnection) args.arg3); + } finally { + args.recycle(); + } + break; + } + case MSG_SET_ACTIVE: + mDelegate.setActive((String) msg.obj); + break; + case MSG_SET_RINGING: + mDelegate.setRinging((String) msg.obj); + break; + case MSG_SET_DIALING: + mDelegate.setDialing((String) msg.obj); + break; + case MSG_SET_DISCONNECTED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setDisconnected((String) args.arg1, (DisconnectCause) args.arg2); + } finally { + args.recycle(); + } + break; + } + case MSG_SET_ON_HOLD: + mDelegate.setOnHold((String) msg.obj); + break; + case MSG_SET_RINGBACK_REQUESTED: + mDelegate.setRingbackRequested((String) msg.obj, msg.arg1 == 1); + break; + case MSG_SET_CALL_CAPABILITIES: + mDelegate.setCallCapabilities((String) msg.obj, msg.arg1); + break; + case MSG_SET_IS_CONFERENCED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setIsConferenced((String) args.arg1, (String) args.arg2); + } finally { + args.recycle(); + } + break; + } + case MSG_ADD_CONFERENCE_CALL: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.addConferenceCall( + (String) args.arg1, (ParcelableConference) args.arg2); + } finally { + args.recycle(); + } + break; + } + case MSG_REMOVE_CALL: + mDelegate.removeCall((String) msg.obj); + break; + case MSG_ON_POST_DIAL_WAIT: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.onPostDialWait((String) args.arg1, (String) args.arg2); + } finally { + args.recycle(); + } + break; + } + case MSG_QUERY_REMOTE_CALL_SERVICES: + mDelegate.queryRemoteConnectionServices((RemoteServiceCallback) msg.obj); + break; + case MSG_SET_VIDEO_STATE: + mDelegate.setVideoState((String) msg.obj, msg.arg1); + break; + case MSG_SET_VIDEO_CALL_PROVIDER: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setVideoProvider((String) args.arg1, + (IVideoProvider) args.arg2); + } finally { + args.recycle(); + } + break; + } + case MSG_SET_IS_VOIP_AUDIO_MODE: + mDelegate.setIsVoipAudioMode((String) msg.obj, msg.arg1 == 1); + break; + case MSG_SET_STATUS_HINTS: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setStatusHints((String) args.arg1, (StatusHints) args.arg2); + } finally { + args.recycle(); + } + break; + } + case MSG_SET_ADDRESS: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setAddress((String) args.arg1, (Uri) args.arg2, args.argi1); + } finally { + args.recycle(); + } + break; + } + case MSG_SET_CALLER_DISPLAY_NAME: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setCallerDisplayName( + (String) args.arg1, (String) args.arg2, args.argi1); + } finally { + args.recycle(); + } + break; + } + case MSG_SET_CONFERENCEABLE_CONNECTIONS: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setConferenceableConnections( + (String) args.arg1, (List<String>) args.arg2); + } finally { + args.recycle(); + } + break; + } + } + } + }; + + private final IConnectionServiceAdapter mStub = new IConnectionServiceAdapter.Stub() { + @Override + public void handleCreateConnectionComplete( + String id, + ConnectionRequest request, + ParcelableConnection connection) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = id; + args.arg2 = request; + args.arg3 = connection; + mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_COMPLETE, args).sendToTarget(); + } + + @Override + public void setActive(String connectionId) { + mHandler.obtainMessage(MSG_SET_ACTIVE, connectionId).sendToTarget(); + } + + @Override + public void setRinging(String connectionId) { + mHandler.obtainMessage(MSG_SET_RINGING, connectionId).sendToTarget(); + } + + @Override + public void setDialing(String connectionId) { + mHandler.obtainMessage(MSG_SET_DIALING, connectionId).sendToTarget(); + } + + @Override + public void setDisconnected( + String connectionId, DisconnectCause disconnectCause) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionId; + args.arg2 = disconnectCause; + mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget(); + } + + @Override + public void setOnHold(String connectionId) { + mHandler.obtainMessage(MSG_SET_ON_HOLD, connectionId).sendToTarget(); + } + + @Override + public void setRingbackRequested(String connectionId, boolean ringback) { + mHandler.obtainMessage(MSG_SET_RINGBACK_REQUESTED, ringback ? 1 : 0, 0, connectionId) + .sendToTarget(); + } + + @Override + public void setCallCapabilities(String connectionId, int callCapabilities) { + mHandler.obtainMessage(MSG_SET_CALL_CAPABILITIES, callCapabilities, 0, connectionId) + .sendToTarget(); + } + + @Override + public void setIsConferenced(String callId, String conferenceCallId) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = conferenceCallId; + mHandler.obtainMessage(MSG_SET_IS_CONFERENCED, args).sendToTarget(); + } + + @Override + public void addConferenceCall(String callId, ParcelableConference parcelableConference) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = parcelableConference; + mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, args).sendToTarget(); + } + + @Override + public void removeCall(String connectionId) { + mHandler.obtainMessage(MSG_REMOVE_CALL, connectionId).sendToTarget(); + } + + @Override + public void onPostDialWait(String connectionId, String remainingDigits) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionId; + args.arg2 = remainingDigits; + mHandler.obtainMessage(MSG_ON_POST_DIAL_WAIT, args).sendToTarget(); + } + + @Override + public void queryRemoteConnectionServices(RemoteServiceCallback callback) { + mHandler.obtainMessage(MSG_QUERY_REMOTE_CALL_SERVICES, callback).sendToTarget(); + } + + @Override + public void setVideoState(String connectionId, int videoState) { + mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState, 0, connectionId).sendToTarget(); + } + + @Override + public void setVideoProvider(String connectionId, IVideoProvider videoProvider) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionId; + args.arg2 = videoProvider; + mHandler.obtainMessage(MSG_SET_VIDEO_CALL_PROVIDER, args).sendToTarget(); + } + + @Override + public final void setIsVoipAudioMode(String connectionId, boolean isVoip) { + mHandler.obtainMessage(MSG_SET_IS_VOIP_AUDIO_MODE, isVoip ? 1 : 0, 0, + connectionId).sendToTarget(); + } + + @Override + public final void setStatusHints(String connectionId, StatusHints statusHints) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionId; + args.arg2 = statusHints; + mHandler.obtainMessage(MSG_SET_STATUS_HINTS, args).sendToTarget(); + } + + @Override + public final void setAddress(String connectionId, Uri address, int presentation) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionId; + args.arg2 = address; + args.argi1 = presentation; + mHandler.obtainMessage(MSG_SET_ADDRESS, args).sendToTarget(); + } + + @Override + public final void setCallerDisplayName( + String connectionId, String callerDisplayName, int presentation) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionId; + args.arg2 = callerDisplayName; + args.argi1 = presentation; + mHandler.obtainMessage(MSG_SET_CALLER_DISPLAY_NAME, args).sendToTarget(); + } + + @Override + public final void setConferenceableConnections( + String connectionId, List<String> conferenceableConnectionIds) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionId; + args.arg2 = conferenceableConnectionIds; + mHandler.obtainMessage(MSG_SET_CONFERENCEABLE_CONNECTIONS, args).sendToTarget(); + } + }; + + public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) { + mDelegate = delegate; + } + + public IConnectionServiceAdapter getStub() { + return mStub; + } +} diff --git a/telecomm/java/android/telecom/DisconnectCause.aidl b/telecomm/java/android/telecom/DisconnectCause.aidl new file mode 100644 index 0000000..26b8652 --- /dev/null +++ b/telecomm/java/android/telecom/DisconnectCause.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 2014, 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 android.telecom; + +/** + * {@hide} + */ +parcelable DisconnectCause; diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java new file mode 100644 index 0000000..cae115d --- /dev/null +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.os.Parcel; +import android.os.Parcelable; +import android.media.ToneGenerator; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * Describes the cause of a disconnected call. This always includes a code describing the generic + * cause of the disconnect. Optionally, it may include a localized label and/or localized description + * to display to the user which is provided by the {@link ConnectionService}. It also may contain a + * reason for the the disconnect, which is intended for logging and not for display to the user. + */ +public final class DisconnectCause implements Parcelable { + + /** Disconnected because of an unknown or unspecified reason. */ + public static final int UNKNOWN = 0; + /** Disconnected because there was an error, such as a problem with the network. */ + public static final int ERROR = 1; + /** Disconnected because of a local user-initiated action, such as hanging up. */ + public static final int LOCAL = 2; + /** + * Disconnected because of a remote user-initiated action, such as the other party hanging up + * up. + */ + public static final int REMOTE = 3; + /** Disconnected because it has been canceled. */ + public static final int CANCELED = 4; + /** Disconnected because there was no response to an incoming call. */ + public static final int MISSED = 5; + /** Disconnected because the user rejected an incoming call. */ + public static final int REJECTED = 6; + /** Disconnected because the other party was busy. */ + public static final int BUSY = 7; + /** + * Disconnected because of a restriction on placing the call, such as dialing in airplane + * mode. + */ + public static final int RESTRICTED = 8; + /** Disconnected for reason not described by other disconnect codes. */ + public static final int OTHER = 9; + + private int mDisconnectCode; + private CharSequence mDisconnectLabel; + private CharSequence mDisconnectDescription; + private String mDisconnectReason; + private int mToneToPlay; + + /** + * Creates a new DisconnectCause. + * + * @param code The code for the disconnect cause. + */ + public DisconnectCause(int code) { + this(code, null, null, null, ToneGenerator.TONE_UNKNOWN); + } + + /** + * Creates a new DisconnectCause. + * + * @param code The code for the disconnect cause. + * @param reason The reason for the disconnect. + */ + public DisconnectCause(int code, String reason) { + this(code, null, null, reason, ToneGenerator.TONE_UNKNOWN); + } + + /** + * Creates a new DisconnectCause. + * + * @param code The code for the disconnect cause. + * @param label The localized label to show to the user to explain the disconnect. + * @param description The localized description to show to the user to explain the disconnect. + * @param reason The reason for the disconnect. + * @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}. + */ + public DisconnectCause(int code, CharSequence label, CharSequence description, String reason, + int toneToPlay) { + mDisconnectCode = code; + mDisconnectLabel = label; + mDisconnectDescription = description; + mDisconnectReason = reason; + mToneToPlay = toneToPlay; + } + + /** + * Returns the code for the reason for this disconnect. + * + * @return The disconnect code. + */ + public int getCode() { + return mDisconnectCode; + } + + /** + * Returns a short label which explains the reason for the disconnect cause and is for display + * in the user interface. The {@link ConnectionService } is responsible for providing and + * localizing this label. If there is no string provided, returns null. + * + * @return The disconnect label. + */ + public CharSequence getLabel() { + return mDisconnectLabel; + } + + /** + * Returns a description which explains the reason for the disconnect cause and is for display + * in the user interface. The {@link ConnectionService } is responsible for providing and + * localizing this message. If there is no string provided, returns null. + * + * @return The disconnect description. + */ + public CharSequence getDescription() { + return mDisconnectDescription; + } + + /** + * Returns an explanation of the reason for the disconnect. This is not intended for display to + * the user and is used mainly for logging. + * + * @return The disconnect reason. + */ + public String getReason() { + return mDisconnectReason; + } + + /** + * Returns the tone to play when disconnected. + * + * @return the tone as defined in {@link ToneGenerator} to play when disconnected. + */ + public int getTone() { + return mToneToPlay; + } + + public static final Creator<DisconnectCause> CREATOR = new Creator<DisconnectCause>() { + @Override + public DisconnectCause createFromParcel(Parcel source) { + int code = source.readInt(); + CharSequence label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + String reason = source.readString(); + int tone = source.readInt(); + return new DisconnectCause(code, label, description, reason, tone); + } + + @Override + public DisconnectCause[] newArray(int size) { + return new DisconnectCause[size]; + } + }; + + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeInt(mDisconnectCode); + TextUtils.writeToParcel(mDisconnectLabel, destination, flags); + TextUtils.writeToParcel(mDisconnectDescription, destination, flags); + destination.writeString(mDisconnectReason); + destination.writeInt(mToneToPlay); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public int hashCode() { + return Objects.hashCode(mDisconnectCode) + + Objects.hashCode(mDisconnectLabel) + + Objects.hashCode(mDisconnectDescription) + + Objects.hashCode(mDisconnectReason) + + Objects.hashCode(mToneToPlay); + } + + @Override + public boolean equals(Object o) { + if (o instanceof DisconnectCause) { + DisconnectCause d = (DisconnectCause) o; + return Objects.equals(mDisconnectCode, d.getCode()) + && Objects.equals(mDisconnectLabel, d.getLabel()) + && Objects.equals(mDisconnectDescription, d.getDescription()) + && Objects.equals(mDisconnectReason, d.getReason()) + && Objects.equals(mToneToPlay, d.getTone()); + } + return false; + } + + @Override + public String toString() { + String code = ""; + switch (getCode()) { + case ERROR: + code = "ERROR"; + break; + case LOCAL: + code = "LOCAL"; + break; + case REMOTE: + code = "REMOTE"; + break; + case MISSED: + code = "MISSED"; + break; + case REJECTED: + code = "REJECTED"; + break; + case BUSY: + code = "BUSY"; + break; + case RESTRICTED: + code = "RESTRICTED"; + break; + case OTHER: + code = "OTHER"; + break; + case UNKNOWN: + default: + code = "UNKNOWN"; + } + String label = mDisconnectLabel == null ? "" : mDisconnectLabel.toString(); + String description = mDisconnectDescription == null + ? "" : mDisconnectDescription.toString(); + String reason = mDisconnectReason == null ? "" : mDisconnectReason; + return "DisconnectCause [ Code: (" + code + ")" + + " Label: (" + label + ")" + + " Description: (" + description + ")" + + " Reason: (" + reason + ")" + + " Tone: (" + mToneToPlay + ") ]"; + } +} diff --git a/telecomm/java/android/telecom/GatewayInfo.aidl b/telecomm/java/android/telecom/GatewayInfo.aidl new file mode 100644 index 0000000..ad9858c --- /dev/null +++ b/telecomm/java/android/telecom/GatewayInfo.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 2014, 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 android.telecom; + +/** + * {@hide} + */ +parcelable GatewayInfo; diff --git a/telecomm/java/android/telecom/GatewayInfo.java b/telecomm/java/android/telecom/GatewayInfo.java new file mode 100644 index 0000000..583c3e2 --- /dev/null +++ b/telecomm/java/android/telecom/GatewayInfo.java @@ -0,0 +1,108 @@ +/* + * Copyright 2014, 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 android.telecom; + +import android.annotation.SystemApi; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * When calls are made, they may contain gateway information for services which route phone calls + * through their own service/numbers. The data consists of a number to call and the package name of + * the service. This data is used in two ways: + * <ol> + * <li> Call the appropriate routing number + * <li> Display information about how the call is being routed to the user + * </ol> + */ +public class GatewayInfo implements Parcelable { + + private final String mGatewayProviderPackageName; + private final Uri mGatewayAddress; + private final Uri mOriginalAddress; + + /** @hide */ + @SystemApi + public GatewayInfo(String packageName, Uri gatewayUri, Uri originalAddress) { + mGatewayProviderPackageName = packageName; + mGatewayAddress = gatewayUri; + mOriginalAddress = originalAddress; + } + + /** + * Package name of the gateway provider service. used to place the call with. + */ + public String getGatewayProviderPackageName() { + return mGatewayProviderPackageName; + } + + /** + * Gateway provider address to use when actually placing the call. + */ + public Uri getGatewayAddress() { + return mGatewayAddress; + } + + /** + * The actual call address that the user is trying to connect to via the gateway. + */ + public Uri getOriginalAddress() { + return mOriginalAddress; + } + + public boolean isEmpty() { + return TextUtils.isEmpty(mGatewayProviderPackageName) || mGatewayAddress == null; + } + + /** Implement the Parcelable interface */ + public static final Parcelable.Creator<GatewayInfo> CREATOR = + new Parcelable.Creator<GatewayInfo> () { + + @Override + public GatewayInfo createFromParcel(Parcel source) { + String gatewayPackageName = source.readString(); + Uri gatewayUri = Uri.CREATOR.createFromParcel(source); + Uri originalAddress = Uri.CREATOR.createFromParcel(source); + return new GatewayInfo(gatewayPackageName, gatewayUri, originalAddress); + } + + @Override + public GatewayInfo[] newArray(int size) { + return new GatewayInfo[size]; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeString(mGatewayProviderPackageName); + mGatewayAddress.writeToParcel(destination, 0); + mOriginalAddress.writeToParcel(destination, 0); + } +} diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java new file mode 100644 index 0000000..fd3cf2e --- /dev/null +++ b/telecomm/java/android/telecom/InCallAdapter.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.os.RemoteException; + +import com.android.internal.telecom.IInCallAdapter; + +/** + * Receives commands from {@link InCallService} implementations which should be executed by + * Telecom. When Telecom binds to a {@link InCallService}, an instance of this class is given to + * the in-call service through which it can manipulate live (active, dialing, ringing) calls. When + * the in-call service is notified of new calls, it can use the + * given call IDs to execute commands such as {@link #answerCall} for incoming calls or + * {@link #disconnectCall} for active calls the user would like to end. Some commands are only + * appropriate for calls in certain states; please consult each method for such limitations. + * <p> + * The adapter will stop functioning when there are no more calls. + * + * {@hide} + */ +public final class InCallAdapter { + private final IInCallAdapter mAdapter; + + /** + * {@hide} + */ + public InCallAdapter(IInCallAdapter adapter) { + mAdapter = adapter; + } + + /** + * Instructs Telecom to answer the specified call. + * + * @param callId The identifier of the call to answer. + * @param videoState The video state in which to answer the call. + */ + public void answerCall(String callId, int videoState) { + try { + mAdapter.answerCall(callId, videoState); + } catch (RemoteException e) { + } + } + + /** + * Instructs Telecom to reject the specified call. + * + * @param callId The identifier of the call to reject. + * @param rejectWithMessage Whether to reject with a text message. + * @param textMessage An optional text message with which to respond. + */ + public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) { + try { + mAdapter.rejectCall(callId, rejectWithMessage, textMessage); + } catch (RemoteException e) { + } + } + + /** + * Instructs Telecom to disconnect the specified call. + * + * @param callId The identifier of the call to disconnect. + */ + public void disconnectCall(String callId) { + try { + mAdapter.disconnectCall(callId); + } catch (RemoteException e) { + } + } + + /** + * Instructs Telecom to put the specified call on hold. + * + * @param callId The identifier of the call to put on hold. + */ + public void holdCall(String callId) { + try { + mAdapter.holdCall(callId); + } catch (RemoteException e) { + } + } + + /** + * Instructs Telecom to release the specified call from hold. + * + * @param callId The identifier of the call to release from hold. + */ + public void unholdCall(String callId) { + try { + mAdapter.unholdCall(callId); + } catch (RemoteException e) { + } + } + + /** + * Mute the microphone. + * + * @param shouldMute True if the microphone should be muted. + */ + public void mute(boolean shouldMute) { + try { + mAdapter.mute(shouldMute); + } catch (RemoteException e) { + } + } + + /** + * Sets the audio route (speaker, bluetooth, etc...). See {@link AudioState}. + * + * @param route The audio route to use. + */ + public void setAudioRoute(int route) { + try { + mAdapter.setAudioRoute(route); + } catch (RemoteException e) { + } + } + + /** + * Instructs Telecom to play a dual-tone multi-frequency signaling (DTMF) tone in a call. + * + * Any other currently playing DTMF tone in the specified call is immediately stopped. + * + * @param callId The unique ID of the call in which the tone will be played. + * @param digit A character representing the DTMF digit for which to play the tone. This + * value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}. + */ + public void playDtmfTone(String callId, char digit) { + try { + mAdapter.playDtmfTone(callId, digit); + } catch (RemoteException e) { + } + } + + /** + * Instructs Telecom to stop any dual-tone multi-frequency signaling (DTMF) tone currently + * playing. + * + * DTMF tones are played by calling {@link #playDtmfTone(String,char)}. If no DTMF tone is + * currently playing, this method will do nothing. + * + * @param callId The unique ID of the call in which any currently playing tone will be stopped. + */ + public void stopDtmfTone(String callId) { + try { + mAdapter.stopDtmfTone(callId); + } catch (RemoteException e) { + } + } + + /** + * Instructs Telecom to continue playing a post-dial DTMF string. + * + * A post-dial DTMF string is a string of digits entered after a phone number, when dialed, + * that are immediately sent as DTMF tones to the recipient as soon as the connection is made. + * While these tones are playing, Telecom will notify the {@link InCallService} that the call + * is in the post dial state. + * + * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_PAUSE} symbol, Telecom + * will temporarily pause playing the tones for a pre-defined period of time. + * + * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, Telecom + * will pause playing the tones and notify the {@link InCallService} that the call is in the + * post dial wait state. When the user decides to continue the postdial sequence, the + * {@link InCallService} should invoke the {@link #postDialContinue(String,boolean)} method. + * + * @param callId The unique ID of the call for which postdial string playing should continue. + * @param proceed Whether or not to continue with the post-dial sequence. + */ + public void postDialContinue(String callId, boolean proceed) { + try { + mAdapter.postDialContinue(callId, proceed); + } catch (RemoteException e) { + } + } + + /** + * Instructs Telecom to add a PhoneAccountHandle to the specified call + * + * @param callId The identifier of the call + * @param accountHandle The PhoneAccountHandle through which to place the call + */ + public void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle) { + try { + mAdapter.phoneAccountSelected(callId, accountHandle); + } catch (RemoteException e) { + } + } + + /** + * Instructs Telecom to conference the specified call. + * + * @param callId The unique ID of the call. + * @hide + */ + public void conference(String callId, String otherCallId) { + try { + mAdapter.conference(callId, otherCallId); + } catch (RemoteException ignored) { + } + } + + /** + * Instructs Telecom to split the specified call from any conference call with which it may be + * connected. + * + * @param callId The unique ID of the call. + * @hide + */ + public void splitFromConference(String callId) { + try { + mAdapter.splitFromConference(callId); + } catch (RemoteException ignored) { + } + } + + /** + * Instructs Telecom to merge child calls of the specified conference call. + */ + public void mergeConference(String callId) { + try { + mAdapter.mergeConference(callId); + } catch (RemoteException ignored) { + } + } + + /** + * Instructs Telecom to swap the child calls of the specified conference call. + */ + public void swapConference(String callId) { + try { + mAdapter.swapConference(callId); + } catch (RemoteException ignored) { + } + } + + /** + * Instructs Telecom to turn the proximity sensor on. + */ + public void turnProximitySensorOn() { + try { + mAdapter.turnOnProximitySensor(); + } catch (RemoteException ignored) { + } + } + + /** + * Instructs Telecom to turn the proximity sensor off. + * + * @param screenOnImmediately If true, the screen will be turned on immediately if it was + * previously off. Otherwise, the screen will only be turned on after the proximity sensor + * is no longer triggered. + */ + public void turnProximitySensorOff(boolean screenOnImmediately) { + try { + mAdapter.turnOffProximitySensor(screenOnImmediately); + } catch (RemoteException ignored) { + } + } +} diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java new file mode 100644 index 0000000..fa12756 --- /dev/null +++ b/telecomm/java/android/telecom/InCallService.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2013 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 android.telecom; + +import android.annotation.SystemApi; +import android.annotation.SdkConstant; +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.view.Surface; + +import com.android.internal.os.SomeArgs; +import com.android.internal.telecom.IInCallAdapter; +import com.android.internal.telecom.IInCallService; + +import java.lang.String; + +/** + * This service is implemented by any app that wishes to provide the user-interface for managing + * phone calls. Telecom binds to this service while there exists a live (active or incoming) call, + * and uses it to notify the in-call app of any live and and recently disconnected calls. + * + * {@hide} + */ +@SystemApi +public abstract class InCallService extends Service { + + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = "android.telecom.InCallService"; + + private static final int MSG_SET_IN_CALL_ADAPTER = 1; + private static final int MSG_ADD_CALL = 2; + private static final int MSG_UPDATE_CALL = 3; + private static final int MSG_SET_POST_DIAL_WAIT = 4; + private static final int MSG_ON_AUDIO_STATE_CHANGED = 5; + private static final int MSG_BRING_TO_FOREGROUND = 6; + + /** Default Handler used to consolidate binder method calls onto a single thread. */ + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_IN_CALL_ADAPTER: + mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj)); + onPhoneCreated(mPhone); + break; + case MSG_ADD_CALL: + mPhone.internalAddCall((ParcelableCall) msg.obj); + break; + case MSG_UPDATE_CALL: + mPhone.internalUpdateCall((ParcelableCall) msg.obj); + break; + case MSG_SET_POST_DIAL_WAIT: { + SomeArgs args = (SomeArgs) msg.obj; + try { + String callId = (String) args.arg1; + String remaining = (String) args.arg2; + mPhone.internalSetPostDialWait(callId, remaining); + } finally { + args.recycle(); + } + break; + } + case MSG_ON_AUDIO_STATE_CHANGED: + mPhone.internalAudioStateChanged((AudioState) msg.obj); + break; + case MSG_BRING_TO_FOREGROUND: + mPhone.internalBringToForeground(msg.arg1 == 1); + break; + default: + break; + } + } + }; + + /** Manages the binder calls so that the implementor does not need to deal with it. */ + private final class InCallServiceBinder extends IInCallService.Stub { + @Override + public void setInCallAdapter(IInCallAdapter inCallAdapter) { + mHandler.obtainMessage(MSG_SET_IN_CALL_ADAPTER, inCallAdapter).sendToTarget(); + } + + @Override + public void addCall(ParcelableCall call) { + mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget(); + } + + @Override + public void updateCall(ParcelableCall call) { + mHandler.obtainMessage(MSG_UPDATE_CALL, call).sendToTarget(); + } + + @Override + public void setPostDial(String callId, String remaining) { + // TODO: Unused + } + + @Override + public void setPostDialWait(String callId, String remaining) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = remaining; + mHandler.obtainMessage(MSG_SET_POST_DIAL_WAIT, args).sendToTarget(); + } + + @Override + public void onAudioStateChanged(AudioState audioState) { + mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, audioState).sendToTarget(); + } + + @Override + public void bringToForeground(boolean showDialpad) { + mHandler.obtainMessage(MSG_BRING_TO_FOREGROUND, showDialpad ? 1 : 0, 0).sendToTarget(); + } + } + + private Phone mPhone; + + public InCallService() { + } + + @Override + public IBinder onBind(Intent intent) { + return new InCallServiceBinder(); + } + + @Override + public boolean onUnbind(Intent intent) { + if (mPhone != null) { + Phone oldPhone = mPhone; + mPhone = null; + + oldPhone.destroy(); + onPhoneDestroyed(oldPhone); + } + return false; + } + + /** + * Obtain the {@code Phone} associated with this {@code InCallService}. + * + * @return The {@code Phone} object associated with this {@code InCallService}, or {@code null} + * if the {@code InCallService} is not in a state where it has an associated + * {@code Phone}. + */ + public Phone getPhone() { + return mPhone; + } + + /** + * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience + * to start displaying in-call information to the user. Each instance of {@code InCallService} + * will have only one {@code Phone}, and this method will be called exactly once in the lifetime + * of the {@code InCallService}. + * + * @param phone The {@code Phone} object associated with this {@code InCallService}. + */ + public void onPhoneCreated(Phone phone) { + } + + /** + * Invoked when a {@code Phone} has been destroyed. This is a signal to the in-call experience + * to stop displaying in-call information to the user. This method will be called exactly once + * in the lifetime of the {@code InCallService}, and it will always be called after a previous + * call to {@link #onPhoneCreated(Phone)}. + * + * @param phone The {@code Phone} object associated with this {@code InCallService}. + */ + public void onPhoneDestroyed(Phone phone) { + } + + /** + * Class to invoke functionality related to video calls. + * @hide + */ + public static abstract class VideoCall { + + /** + * Sets a listener to invoke callback methods in the InCallUI after performing video + * telephony actions. + * + * @param videoCallListener The call video client. + */ + public abstract void setVideoCallListener(VideoCall.Listener videoCallListener); + + /** + * Sets the camera to be used for video recording in a video call. + * + * @param cameraId The id of the camera. + */ + public abstract void setCamera(String cameraId); + + /** + * Sets the surface to be used for displaying a preview of what the user's camera is + * currently capturing. When video transmission is enabled, this is the video signal which + * is sent to the remote device. + * + * @param surface The surface. + */ + public abstract void setPreviewSurface(Surface surface); + + /** + * Sets the surface to be used for displaying the video received from the remote device. + * + * @param surface The surface. + */ + public abstract void setDisplaySurface(Surface surface); + + /** + * Sets the device orientation, in degrees. Assumes that a standard portrait orientation of + * the device is 0 degrees. + * + * @param rotation The device orientation, in degrees. + */ + public abstract void setDeviceOrientation(int rotation); + + /** + * Sets camera zoom ratio. + * + * @param value The camera zoom ratio. + */ + public abstract void setZoom(float value); + + /** + * Issues a request to modify the properties of the current session. The request is sent to + * the remote device where it it handled by + * {@link VideoCall.Listener#onSessionModifyRequestReceived}. + * Some examples of session modification requests: upgrade call from audio to video, + * downgrade call from video to audio, pause video. + * + * @param requestProfile The requested call video properties. + */ + public abstract void sendSessionModifyRequest(VideoProfile requestProfile); + + /** + * Provides a response to a request to change the current call session video + * properties. + * This is in response to a request the InCall UI has received via + * {@link VideoCall.Listener#onSessionModifyRequestReceived}. + * The response is handled on the remove device by + * {@link VideoCall.Listener#onSessionModifyResponseReceived}. + * + * @param responseProfile The response call video properties. + */ + public abstract void sendSessionModifyResponse(VideoProfile responseProfile); + + /** + * Issues a request to the video provider to retrieve the camera capabilities. + * Camera capabilities are reported back to the caller via + * {@link VideoCall.Listener#onCameraCapabilitiesChanged(CameraCapabilities)}. + */ + public abstract void requestCameraCapabilities(); + + /** + * Issues a request to the video telephony framework to retrieve the cumulative data usage for + * the current call. Data usage is reported back to the caller via + * {@link VideoCall.Listener#onCallDataUsageChanged}. + */ + public abstract void requestCallDataUsage(); + + /** + * Provides the video telephony framework with the URI of an image to be displayed to remote + * devices when the video signal is paused. + * + * @param uri URI of image to display. + */ + public abstract void setPauseImage(String uri); + + /** + * Listener class which invokes callbacks after video call actions occur. + * @hide + */ + public static abstract class Listener { + /** + * Called when a session modification request is received from the remote device. + * The remote request is sent via + * {@link Connection.VideoProvider#onSendSessionModifyRequest}. The InCall UI + * is responsible for potentially prompting the user whether they wish to accept the new + * call profile (e.g. prompt user if they wish to accept an upgrade from an audio to a + * video call) and should call + * {@link Connection.VideoProvider#onSendSessionModifyResponse} to indicate + * the video settings the user has agreed to. + * + * @param videoProfile The requested video call profile. + */ + public abstract void onSessionModifyRequestReceived(VideoProfile videoProfile); + + /** + * Called when a response to a session modification request is received from the remote + * device. The remote InCall UI sends the response using + * {@link Connection.VideoProvider#onSendSessionModifyResponse}. + * + * @param status Status of the session modify request. Valid values are + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID} + * @param requestedProfile The original request which was sent to the remote device. + * @param responseProfile The actual profile changes made by the remote device. + */ + public abstract void onSessionModifyResponseReceived(int status, + VideoProfile requestedProfile, VideoProfile responseProfile); + + /** + * Handles events related to the current session which the client may wish to handle. + * These are separate from requested changes to the session due to the underlying + * protocol or connection. + * + * Valid values are: + * {@link Connection.VideoProvider#SESSION_EVENT_RX_PAUSE}, + * {@link Connection.VideoProvider#SESSION_EVENT_RX_RESUME}, + * {@link Connection.VideoProvider#SESSION_EVENT_TX_START}, + * {@link Connection.VideoProvider#SESSION_EVENT_TX_STOP}, + * {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_FAILURE}, + * {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_READY} + * + * @param event The event. + */ + public abstract void onCallSessionEvent(int event); + + /** + * Handles a change to the video dimensions from the remote caller (peer). This could + * happen if, for example, the peer changes orientation of their device. + * + * @param width The updated peer video width. + * @param height The updated peer video height. + */ + public abstract void onPeerDimensionsChanged(int width, int height); + + /** + * Handles an update to the total data used for the current session. + * + * @param dataUsage The updated data usage. + */ + public abstract void onCallDataUsageChanged(int dataUsage); + + /** + * Handles a change in camera capabilities. + * + * @param cameraCapabilities The changed camera capabilities. + */ + public abstract void onCameraCapabilitiesChanged( + CameraCapabilities cameraCapabilities); + } + } +} diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java new file mode 100644 index 0000000..73cc4a5 --- /dev/null +++ b/telecomm/java/android/telecom/Log.java @@ -0,0 +1,181 @@ +/* + * Copyright 2014, 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 android.telecom; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.IllegalFormatException; +import java.util.Locale; + +/** + * Manages logging for the entire module. + * + * @hide + */ +final public class Log { + + // Generic tag for all Telecom Framework logging + private static final String TAG = "TelecomFramework"; + + public static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */ + public static final boolean DEBUG = isLoggable(android.util.Log.DEBUG); + public static final boolean INFO = isLoggable(android.util.Log.INFO); + public static final boolean VERBOSE = isLoggable(android.util.Log.VERBOSE); + public static final boolean WARN = isLoggable(android.util.Log.WARN); + public static final boolean ERROR = isLoggable(android.util.Log.ERROR); + + private Log() {} + + public static boolean isLoggable(int level) { + return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level); + } + + public static void d(String prefix, String format, Object... args) { + if (DEBUG) { + android.util.Log.d(TAG, buildMessage(prefix, format, args)); + } + } + + public static void d(Object objectPrefix, String format, Object... args) { + if (DEBUG) { + android.util.Log.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); + } + } + + public static void i(String prefix, String format, Object... args) { + if (INFO) { + android.util.Log.i(TAG, buildMessage(prefix, format, args)); + } + } + + public static void i(Object objectPrefix, String format, Object... args) { + if (INFO) { + android.util.Log.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); + } + } + + public static void v(String prefix, String format, Object... args) { + if (VERBOSE) { + android.util.Log.v(TAG, buildMessage(prefix, format, args)); + } + } + + public static void v(Object objectPrefix, String format, Object... args) { + if (VERBOSE) { + android.util.Log.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); + } + } + + public static void w(String prefix, String format, Object... args) { + if (WARN) { + android.util.Log.w(TAG, buildMessage(prefix, format, args)); + } + } + + public static void w(Object objectPrefix, String format, Object... args) { + if (WARN) { + android.util.Log.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); + } + } + + public static void e(String prefix, Throwable tr, String format, Object... args) { + if (ERROR) { + android.util.Log.e(TAG, buildMessage(prefix, format, args), tr); + } + } + + public static void e(Object objectPrefix, Throwable tr, String format, Object... args) { + if (ERROR) { + android.util.Log.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args), + tr); + } + } + + public static void wtf(String prefix, Throwable tr, String format, Object... args) { + android.util.Log.wtf(TAG, buildMessage(prefix, format, args), tr); + } + + public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) { + android.util.Log.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args), + tr); + } + + public static void wtf(String prefix, String format, Object... args) { + String msg = buildMessage(prefix, format, args); + android.util.Log.wtf(TAG, msg, new IllegalStateException(msg)); + } + + public static void wtf(Object objectPrefix, String format, Object... args) { + String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args); + android.util.Log.wtf(TAG, msg, new IllegalStateException(msg)); + } + + /** + * Redact personally identifiable information for production users. + * If we are running in verbose mode, return the original string, otherwise + * return a SHA-1 hash of the input string. + */ + public static String pii(Object pii) { + if (pii == null || VERBOSE) { + return String.valueOf(pii); + } + return "[" + secureHash(String.valueOf(pii).getBytes()) + "]"; + } + + private static String secureHash(byte[] input) { + MessageDigest messageDigest; + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return null; + } + messageDigest.update(input); + byte[] result = messageDigest.digest(); + return encodeHex(result); + } + + private static String encodeHex(byte[] bytes) { + StringBuffer hex = new StringBuffer(bytes.length * 2); + + for (int i = 0; i < bytes.length; i++) { + int byteIntValue = bytes[i] & 0xff; + if (byteIntValue < 0x10) { + hex.append("0"); + } + hex.append(Integer.toString(byteIntValue, 16)); + } + + return hex.toString(); + } + + private static String getPrefixFromObject(Object obj) { + return obj == null ? "<null>" : obj.getClass().getSimpleName(); + } + + private static String buildMessage(String prefix, String format, Object... args) { + String msg; + try { + msg = (args == null || args.length == 0) ? format + : String.format(Locale.US, format, args); + } catch (IllegalFormatException ife) { + wtf("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format, + args.length); + msg = format + " (An error occurred while formatting the message.)"; + } + return String.format(Locale.US, "%s: %s", prefix, msg); + } +} diff --git a/telecomm/java/android/telecom/ParcelableCall.aidl b/telecomm/java/android/telecom/ParcelableCall.aidl new file mode 100644 index 0000000..480e82f --- /dev/null +++ b/telecomm/java/android/telecom/ParcelableCall.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 2014, 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 android.telecom; + +/** + * {@hide} + */ +parcelable ParcelableCall; diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java new file mode 100644 index 0000000..c5c3d11 --- /dev/null +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -0,0 +1,330 @@ +/* + * Copyright 2014, 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 android.telecom; + +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.android.internal.telecom.IVideoProvider; + +/** + * Information about a call that is used between InCallService and Telecom. + * @hide + */ +public final class ParcelableCall implements Parcelable { + private final String mId; + private final int mState; + private final DisconnectCause mDisconnectCause; + private final List<String> mCannedSmsResponses; + private final int mCapabilities; + private final int mProperties; + private final long mConnectTimeMillis; + private final Uri mHandle; + private final int mHandlePresentation; + private final String mCallerDisplayName; + private final int mCallerDisplayNamePresentation; + private final GatewayInfo mGatewayInfo; + private final PhoneAccountHandle mAccountHandle; + private final IVideoProvider mVideoCallProvider; + private InCallService.VideoCall mVideoCall; + private final String mParentCallId; + private final List<String> mChildCallIds; + private final StatusHints mStatusHints; + private final int mVideoState; + private final List<String> mConferenceableCallIds; + private final Bundle mExtras; + + public ParcelableCall( + String id, + int state, + DisconnectCause disconnectCause, + List<String> cannedSmsResponses, + int capabilities, + int properties, + long connectTimeMillis, + Uri handle, + int handlePresentation, + String callerDisplayName, + int callerDisplayNamePresentation, + GatewayInfo gatewayInfo, + PhoneAccountHandle accountHandle, + IVideoProvider videoCallProvider, + String parentCallId, + List<String> childCallIds, + StatusHints statusHints, + int videoState, + List<String> conferenceableCallIds, + Bundle extras) { + mId = id; + mState = state; + mDisconnectCause = disconnectCause; + mCannedSmsResponses = cannedSmsResponses; + mCapabilities = capabilities; + mProperties = properties; + mConnectTimeMillis = connectTimeMillis; + mHandle = handle; + mHandlePresentation = handlePresentation; + mCallerDisplayName = callerDisplayName; + mCallerDisplayNamePresentation = callerDisplayNamePresentation; + mGatewayInfo = gatewayInfo; + mAccountHandle = accountHandle; + mVideoCallProvider = videoCallProvider; + mParentCallId = parentCallId; + mChildCallIds = childCallIds; + mStatusHints = statusHints; + mVideoState = videoState; + mConferenceableCallIds = Collections.unmodifiableList(conferenceableCallIds); + mExtras = extras; + } + + /** The unique ID of the call. */ + public String getId() { + return mId; + } + + /** The current state of the call. */ + public int getState() { + return mState; + } + + /** + * Reason for disconnection, as described by {@link android.telecomm.DisconnectCause}. Valid + * when call state is {@link CallState#DISCONNECTED}. + */ + public DisconnectCause getDisconnectCause() { + return mDisconnectCause; + } + + /** + * The set of possible text message responses when this call is incoming. + */ + public List<String> getCannedSmsResponses() { + return mCannedSmsResponses; + } + + // Bit mask of actions a call supports, values are defined in {@link CallCapabilities}. + public int getCapabilities() { + return mCapabilities; + } + + /** Bitmask of properties of the call. */ + public int getProperties() { return mProperties; } + + /** The time that the call switched to the active state. */ + public long getConnectTimeMillis() { + return mConnectTimeMillis; + } + + /** The endpoint to which the call is connected. */ + public Uri getHandle() { + return mHandle; + } + + /** + * The presentation requirements for the handle. See {@link TelecomManager} for valid values. + */ + public int getHandlePresentation() { + return mHandlePresentation; + } + + /** The endpoint to which the call is connected. */ + public String getCallerDisplayName() { + return mCallerDisplayName; + } + + /** + * The presentation requirements for the caller display name. + * See {@link TelecomManager} for valid values. + */ + public int getCallerDisplayNamePresentation() { + return mCallerDisplayNamePresentation; + } + + /** Gateway information for the call. */ + public GatewayInfo getGatewayInfo() { + return mGatewayInfo; + } + + /** PhoneAccountHandle information for the call. */ + public PhoneAccountHandle getAccountHandle() { + return mAccountHandle; + } + + /** + * Returns an object for remotely communicating through the video call provider's binder. + * @return The video call. + */ + public InCallService.VideoCall getVideoCall() { + if (mVideoCall == null && mVideoCallProvider != null) { + try { + mVideoCall = new VideoCallImpl(mVideoCallProvider); + } catch (RemoteException ignored) { + // Ignore RemoteException. + } + } + + return mVideoCall; + } + + /** + * The conference call to which this call is conferenced. Null if not conferenced. + */ + public String getParentCallId() { + return mParentCallId; + } + + /** + * The child call-IDs if this call is a conference call. Returns an empty list if this is not + * a conference call or if the conference call contains no children. + */ + public List<String> getChildCallIds() { + return mChildCallIds; + } + + public List<String> getConferenceableCallIds() { + return mConferenceableCallIds; + } + + /** + * The status label and icon. + * + * @return Status hints. + */ + public StatusHints getStatusHints() { + return mStatusHints; + } + + /** + * The video state. + * @return The video state of the call. + */ + public int getVideoState() { + return mVideoState; + } + + /** + * Any extras to pass with the call + * + * @return a bundle of extras + */ + public Bundle getExtras() { + return mExtras; + } + + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ + public static final Parcelable.Creator<ParcelableCall> CREATOR = + new Parcelable.Creator<ParcelableCall> () { + @Override + public ParcelableCall createFromParcel(Parcel source) { + ClassLoader classLoader = ParcelableCall.class.getClassLoader(); + String id = source.readString(); + int state = source.readInt(); + DisconnectCause disconnectCause = source.readParcelable(classLoader); + List<String> cannedSmsResponses = new ArrayList<>(); + source.readList(cannedSmsResponses, classLoader); + int capabilities = source.readInt(); + int properties = source.readInt(); + long connectTimeMillis = source.readLong(); + Uri handle = source.readParcelable(classLoader); + int handlePresentation = source.readInt(); + String callerDisplayName = source.readString(); + int callerDisplayNamePresentation = source.readInt(); + GatewayInfo gatewayInfo = source.readParcelable(classLoader); + PhoneAccountHandle accountHandle = source.readParcelable(classLoader); + IVideoProvider videoCallProvider = + IVideoProvider.Stub.asInterface(source.readStrongBinder()); + String parentCallId = source.readString(); + List<String> childCallIds = new ArrayList<>(); + source.readList(childCallIds, classLoader); + StatusHints statusHints = source.readParcelable(classLoader); + int videoState = source.readInt(); + List<String> conferenceableCallIds = new ArrayList<>(); + source.readList(conferenceableCallIds, classLoader); + Bundle extras = source.readParcelable(classLoader); + return new ParcelableCall( + id, + state, + disconnectCause, + cannedSmsResponses, + capabilities, + properties, + connectTimeMillis, + handle, + handlePresentation, + callerDisplayName, + callerDisplayNamePresentation, + gatewayInfo, + accountHandle, + videoCallProvider, + parentCallId, + childCallIds, + statusHints, + videoState, + conferenceableCallIds, + extras); + } + + @Override + public ParcelableCall[] newArray(int size) { + return new ParcelableCall[size]; + } + }; + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** Writes ParcelableCall object into a Parcel. */ + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeString(mId); + destination.writeInt(mState); + destination.writeParcelable(mDisconnectCause, 0); + destination.writeList(mCannedSmsResponses); + destination.writeInt(mCapabilities); + destination.writeInt(mProperties); + destination.writeLong(mConnectTimeMillis); + destination.writeParcelable(mHandle, 0); + destination.writeInt(mHandlePresentation); + destination.writeString(mCallerDisplayName); + destination.writeInt(mCallerDisplayNamePresentation); + destination.writeParcelable(mGatewayInfo, 0); + destination.writeParcelable(mAccountHandle, 0); + destination.writeStrongBinder( + mVideoCallProvider != null ? mVideoCallProvider.asBinder() : null); + destination.writeString(mParentCallId); + destination.writeList(mChildCallIds); + destination.writeParcelable(mStatusHints, 0); + destination.writeInt(mVideoState); + destination.writeList(mConferenceableCallIds); + destination.writeParcelable(mExtras, 0); + } + + @Override + public String toString() { + return String.format("[%s, parent:%s, children:%s]", mId, mParentCallId, mChildCallIds); + } +} diff --git a/telecomm/java/android/telecom/ParcelableConference.aidl b/telecomm/java/android/telecom/ParcelableConference.aidl new file mode 100644 index 0000000..155ba94 --- /dev/null +++ b/telecomm/java/android/telecom/ParcelableConference.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2014, 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 android.telecom; + +parcelable ParcelableConference; diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java new file mode 100644 index 0000000..97c709c --- /dev/null +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -0,0 +1,111 @@ +/* + * Copyright 2014, 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 android.telecom; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A parcelable representation of a conference connection. + * @hide + */ +public final class ParcelableConference implements Parcelable { + + private PhoneAccountHandle mPhoneAccount; + private int mState; + private int mCapabilities; + private List<String> mConnectionIds; + + public ParcelableConference( + PhoneAccountHandle phoneAccount, + int state, + int capabilities, + List<String> connectionIds) { + mPhoneAccount = phoneAccount; + mState = state; + mCapabilities = capabilities; + mConnectionIds = connectionIds; + } + + @Override + public String toString() { + return (new StringBuffer()) + .append("account: ") + .append(mPhoneAccount) + .append(", state: ") + .append(Connection.stateToString(mState)) + .append(", capabilities: ") + .append(PhoneCapabilities.toString(mCapabilities)) + .append(", children: ") + .append(mConnectionIds) + .toString(); + } + + public PhoneAccountHandle getPhoneAccount() { + return mPhoneAccount; + } + + public int getState() { + return mState; + } + + public int getCapabilities() { + return mCapabilities; + } + + public List<String> getConnectionIds() { + return mConnectionIds; + } + + public static final Parcelable.Creator<ParcelableConference> CREATOR = + new Parcelable.Creator<ParcelableConference> () { + @Override + public ParcelableConference createFromParcel(Parcel source) { + ClassLoader classLoader = ParcelableConference.class.getClassLoader(); + PhoneAccountHandle phoneAccount = source.readParcelable(classLoader); + int state = source.readInt(); + int capabilities = source.readInt(); + List<String> connectionIds = new ArrayList<>(2); + source.readList(connectionIds, classLoader); + + return new ParcelableConference(phoneAccount, state, capabilities, connectionIds); + } + + @Override + public ParcelableConference[] newArray(int size) { + return new ParcelableConference[size]; + } + }; + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** Writes ParcelableConference object into a Parcel. */ + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeParcelable(mPhoneAccount, 0); + destination.writeInt(mState); + destination.writeInt(mCapabilities); + destination.writeList(mConnectionIds); + } +} diff --git a/telecomm/java/android/telecom/ParcelableConnection.aidl b/telecomm/java/android/telecom/ParcelableConnection.aidl new file mode 100644 index 0000000..e91ebc3 --- /dev/null +++ b/telecomm/java/android/telecom/ParcelableConnection.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 2014, 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 android.telecom; + +/** + * {@hide} + */ +parcelable ParcelableConnection; diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java new file mode 100644 index 0000000..9004448 --- /dev/null +++ b/telecomm/java/android/telecom/ParcelableConnection.java @@ -0,0 +1,222 @@ +/* + * Copyright 2014, 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 android.telecom; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.telecom.IVideoProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * Information about a connection that is used between Telecom and the ConnectionService. + * This is used to send initial Connection information to Telecom when the connection is + * first created. + * @hide + */ +public final class ParcelableConnection implements Parcelable { + private final PhoneAccountHandle mPhoneAccount; + private final int mState; + private final int mCapabilities; + private final Uri mAddress; + private final int mAddressPresentation; + private final String mCallerDisplayName; + private final int mCallerDisplayNamePresentation; + private final IVideoProvider mVideoProvider; + private final int mVideoState; + private final boolean mRingbackRequested; + private final boolean mIsVoipAudioMode; + private final StatusHints mStatusHints; + private final DisconnectCause mDisconnectCause; + private final List<String> mConferenceableConnectionIds; + + /** @hide */ + public ParcelableConnection( + PhoneAccountHandle phoneAccount, + int state, + int capabilities, + Uri address, + int addressPresentation, + String callerDisplayName, + int callerDisplayNamePresentation, + IVideoProvider videoProvider, + int videoState, + boolean ringbackRequested, + boolean isVoipAudioMode, + StatusHints statusHints, + DisconnectCause disconnectCause, + List<String> conferenceableConnectionIds) { + mPhoneAccount = phoneAccount; + mState = state; + mCapabilities = capabilities; + mAddress = address; + mAddressPresentation = addressPresentation; + mCallerDisplayName = callerDisplayName; + mCallerDisplayNamePresentation = callerDisplayNamePresentation; + mVideoProvider = videoProvider; + mVideoState = videoState; + mRingbackRequested = ringbackRequested; + mIsVoipAudioMode = isVoipAudioMode; + mStatusHints = statusHints; + mDisconnectCause = disconnectCause; + this.mConferenceableConnectionIds = conferenceableConnectionIds; + } + + public PhoneAccountHandle getPhoneAccount() { + return mPhoneAccount; + } + + public int getState() { + return mState; + } + + // Bit mask of actions a call supports, values are defined in {@link CallCapabilities}. + public int getCapabilities() { + return mCapabilities; + } + + public Uri getHandle() { + return mAddress; + } + + public int getHandlePresentation() { + return mAddressPresentation; + } + + public String getCallerDisplayName() { + return mCallerDisplayName; + } + + public int getCallerDisplayNamePresentation() { + return mCallerDisplayNamePresentation; + } + + public IVideoProvider getVideoProvider() { + return mVideoProvider; + } + + public int getVideoState() { + return mVideoState; + } + + public boolean isRingbackRequested() { + return mRingbackRequested; + } + + public boolean getIsVoipAudioMode() { + return mIsVoipAudioMode; + } + + public final StatusHints getStatusHints() { + return mStatusHints; + } + + public final DisconnectCause getDisconnectCause() { + return mDisconnectCause; + } + + public final List<String> getConferenceableConnectionIds() { + return mConferenceableConnectionIds; + } + + @Override + public String toString() { + return new StringBuilder() + .append("ParcelableConnection [act:") + .append(mPhoneAccount) + .append(", state:") + .append(mState) + .append(", capabilities:") + .append(PhoneCapabilities.toString(mCapabilities)) + .toString(); + } + + public static final Parcelable.Creator<ParcelableConnection> CREATOR = + new Parcelable.Creator<ParcelableConnection> () { + @Override + public ParcelableConnection createFromParcel(Parcel source) { + ClassLoader classLoader = ParcelableConnection.class.getClassLoader(); + + PhoneAccountHandle phoneAccount = source.readParcelable(classLoader); + int state = source.readInt(); + int capabilities = source.readInt(); + Uri address = source.readParcelable(classLoader); + int addressPresentation = source.readInt(); + String callerDisplayName = source.readString(); + int callerDisplayNamePresentation = source.readInt(); + IVideoProvider videoCallProvider = + IVideoProvider.Stub.asInterface(source.readStrongBinder()); + int videoState = source.readInt(); + boolean ringbackRequested = source.readByte() == 1; + boolean audioModeIsVoip = source.readByte() == 1; + StatusHints statusHints = source.readParcelable(classLoader); + DisconnectCause disconnectCause = source.readParcelable(classLoader); + List<String> conferenceableConnectionIds = new ArrayList<>(); + source.readStringList(conferenceableConnectionIds); + + return new ParcelableConnection( + phoneAccount, + state, + capabilities, + address, + addressPresentation, + callerDisplayName, + callerDisplayNamePresentation, + videoCallProvider, + videoState, + ringbackRequested, + audioModeIsVoip, + statusHints, + disconnectCause, + conferenceableConnectionIds); + } + + @Override + public ParcelableConnection[] newArray(int size) { + return new ParcelableConnection[size]; + } + }; + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** Writes ParcelableConnection object into a Parcel. */ + @Override + public void writeToParcel(Parcel destination, int flags) { + destination.writeParcelable(mPhoneAccount, 0); + destination.writeInt(mState); + destination.writeInt(mCapabilities); + destination.writeParcelable(mAddress, 0); + destination.writeInt(mAddressPresentation); + destination.writeString(mCallerDisplayName); + destination.writeInt(mCallerDisplayNamePresentation); + destination.writeStrongBinder( + mVideoProvider != null ? mVideoProvider.asBinder() : null); + destination.writeInt(mVideoState); + destination.writeByte((byte) (mRingbackRequested ? 1 : 0)); + destination.writeByte((byte) (mIsVoipAudioMode ? 1 : 0)); + destination.writeParcelable(mStatusHints, 0); + destination.writeParcelable(mDisconnectCause, 0); + destination.writeStringList(mConferenceableConnectionIds); + } +} diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java new file mode 100644 index 0000000..5131790 --- /dev/null +++ b/telecomm/java/android/telecom/Phone.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2013 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 android.telecom; + +import android.annotation.SystemApi; +import android.util.ArrayMap; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * A unified virtual device providing a means of voice (and other) communication on a device. + * + * {@hide} + */ +@SystemApi +public final class Phone { + + public abstract static class Listener { + /** + * Called when the audio state changes. + * + * @param phone The {@code Phone} calling this method. + * @param audioState The new {@link AudioState}. + */ + public void onAudioStateChanged(Phone phone, AudioState audioState) { } + + /** + * Called to bring the in-call screen to the foreground. The in-call experience should + * respond immediately by coming to the foreground to inform the user of the state of + * ongoing {@code Call}s. + * + * @param phone The {@code Phone} calling this method. + * @param showDialpad If true, put up the dialpad when the screen is shown. + */ + public void onBringToForeground(Phone phone, boolean showDialpad) { } + + /** + * Called when a {@code Call} has been added to this in-call session. The in-call user + * experience should add necessary state listeners to the specified {@code Call} and + * immediately start to show the user information about the existence + * and nature of this {@code Call}. Subsequent invocations of {@link #getCalls()} will + * include this {@code Call}. + * + * @param phone The {@code Phone} calling this method. + * @param call A newly added {@code Call}. + */ + public void onCallAdded(Phone phone, Call call) { } + + /** + * Called when a {@code Call} has been removed from this in-call session. The in-call user + * experience should remove any state listeners from the specified {@code Call} and + * immediately stop displaying any information about this {@code Call}. + * Subsequent invocations of {@link #getCalls()} will no longer include this {@code Call}. + * + * @param phone The {@code Phone} calling this method. + * @param call A newly removed {@code Call}. + */ + public void onCallRemoved(Phone phone, Call call) { } + } + + // A Map allows us to track each Call by its Telecom-specified call ID + private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>(); + + // A List allows us to keep the Calls in a stable iteration order so that casually developed + // user interface components do not incur any spurious jank + private final List<Call> mCalls = new CopyOnWriteArrayList<>(); + + // An unmodifiable view of the above List can be safely shared with subclass implementations + private final List<Call> mUnmodifiableCalls = Collections.unmodifiableList(mCalls); + + private final InCallAdapter mInCallAdapter; + + private AudioState mAudioState; + + private final List<Listener> mListeners = new CopyOnWriteArrayList<>(); + + /** {@hide} */ + Phone(InCallAdapter adapter) { + mInCallAdapter = adapter; + } + + /** {@hide} */ + final void internalAddCall(ParcelableCall parcelableCall) { + Call call = new Call(this, parcelableCall.getId(), mInCallAdapter); + mCallByTelecomCallId.put(parcelableCall.getId(), call); + mCalls.add(call); + checkCallTree(parcelableCall); + call.internalUpdate(parcelableCall, mCallByTelecomCallId); + fireCallAdded(call); + } + + /** {@hide} */ + final void internalRemoveCall(Call call) { + mCallByTelecomCallId.remove(call.internalGetCallId()); + mCalls.remove(call); + fireCallRemoved(call); + } + + /** {@hide} */ + final void internalUpdateCall(ParcelableCall parcelableCall) { + Call call = mCallByTelecomCallId.get(parcelableCall.getId()); + if (call != null) { + checkCallTree(parcelableCall); + call.internalUpdate(parcelableCall, mCallByTelecomCallId); + } + } + + /** {@hide} */ + final void internalSetPostDialWait(String telecomId, String remaining) { + Call call = mCallByTelecomCallId.get(telecomId); + if (call != null) { + call.internalSetPostDialWait(remaining); + } + } + + /** {@hide} */ + final void internalAudioStateChanged(AudioState audioState) { + if (!Objects.equals(mAudioState, audioState)) { + mAudioState = audioState; + fireAudioStateChanged(audioState); + } + } + + /** {@hide} */ + final Call internalGetCallByTelecomId(String telecomId) { + return mCallByTelecomCallId.get(telecomId); + } + + /** {@hide} */ + final void internalBringToForeground(boolean showDialpad) { + fireBringToForeground(showDialpad); + } + + /** + * Called to destroy the phone and cleanup any lingering calls. + * @hide + */ + final void destroy() { + for (Call call : mCalls) { + if (call.getState() != Call.STATE_DISCONNECTED) { + call.internalSetDisconnected(); + } + } + } + + /** + * Adds a listener to this {@code Phone}. + * + * @param listener A {@code Listener} object. + */ + public final void addListener(Listener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener from this {@code Phone}. + * + * @param listener A {@code Listener} object. + */ + public final void removeListener(Listener listener) { + if (listener != null) { + mListeners.remove(listener); + } + } + + /** + * Obtains the current list of {@code Call}s to be displayed by this in-call experience. + * + * @return A list of the relevant {@code Call}s. + */ + public final List<Call> getCalls() { + return mUnmodifiableCalls; + } + + /** + * Sets the microphone mute state. When this request is honored, there will be change to + * the {@link #getAudioState()}. + * + * @param state {@code true} if the microphone should be muted; {@code false} otherwise. + */ + public final void setMuted(boolean state) { + mInCallAdapter.mute(state); + } + + /** + * Sets the audio route (speaker, bluetooth, etc...). When this request is honored, there will + * be change to the {@link #getAudioState()}. + * + * @param route The audio route to use. + */ + public final void setAudioRoute(int route) { + mInCallAdapter.setAudioRoute(route); + } + + /** + * Turns the proximity sensor on. When this request is made, the proximity sensor will + * become active, and the touch screen and display will be turned off when the user's face + * is detected to be in close proximity to the screen. This operation is a no-op on devices + * that do not have a proximity sensor. + */ + public final void setProximitySensorOn() { + mInCallAdapter.turnProximitySensorOn(); + } + + /** + * Turns the proximity sensor off. When this request is made, the proximity sensor will + * become inactive, and no longer affect the touch screen and display. This operation is a + * no-op on devices that do not have a proximity sensor. + * + * @param screenOnImmediately If true, the screen will be turned on immediately if it was + * previously off. Otherwise, the screen will only be turned on after the proximity sensor + * is no longer triggered. + */ + public final void setProximitySensorOff(boolean screenOnImmediately) { + mInCallAdapter.turnProximitySensorOff(screenOnImmediately); + } + + /** + * Obtains the current phone call audio state of the {@code Phone}. + * + * @return An object encapsulating the audio state. + */ + public final AudioState getAudioState() { + return mAudioState; + } + + private void fireCallAdded(Call call) { + for (Listener listener : mListeners) { + listener.onCallAdded(this, call); + } + } + + private void fireCallRemoved(Call call) { + for (Listener listener : mListeners) { + listener.onCallRemoved(this, call); + } + } + + private void fireAudioStateChanged(AudioState audioState) { + for (Listener listener : mListeners) { + listener.onAudioStateChanged(this, audioState); + } + } + + private void fireBringToForeground(boolean showDialpad) { + for (Listener listener : mListeners) { + listener.onBringToForeground(this, showDialpad); + } + } + + private void checkCallTree(ParcelableCall parcelableCall) { + if (parcelableCall.getParentCallId() != null && + !mCallByTelecomCallId.containsKey(parcelableCall.getParentCallId())) { + Log.wtf(this, "ParcelableCall %s has nonexistent parent %s", + parcelableCall.getId(), parcelableCall.getParentCallId()); + } + if (parcelableCall.getChildCallIds() != null) { + for (int i = 0; i < parcelableCall.getChildCallIds().size(); i++) { + if (!mCallByTelecomCallId.containsKey(parcelableCall.getChildCallIds().get(i))) { + Log.wtf(this, "ParcelableCall %s has nonexistent child %s", + parcelableCall.getId(), parcelableCall.getChildCallIds().get(i)); + } + } + } + } +} diff --git a/telecomm/java/android/telecom/PhoneAccount.aidl b/telecomm/java/android/telecom/PhoneAccount.aidl new file mode 100644 index 0000000..d5e6058 --- /dev/null +++ b/telecomm/java/android/telecom/PhoneAccount.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +/** + * {@hide} + */ +parcelable PhoneAccount; diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java new file mode 100644 index 0000000..4b059b24 --- /dev/null +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources.NotFoundException; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.lang.String; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.MissingResourceException; + +/** + * Describes a distinct account, line of service or call placement method that the system + * can use to place phone calls. + */ +public class PhoneAccount implements Parcelable { + + /** + * Flag indicating that this {@code PhoneAccount} can act as a connection manager for + * other connections. The {@link ConnectionService} associated with this {@code PhoneAccount} + * will be allowed to manage phone calls including using its own proprietary phone-call + * implementation (like VoIP calling) to make calls instead of the telephony stack. + * <p> + * When a user opts to place a call using the SIM-based telephony stack, the + * {@link ConnectionService} associated with this {@code PhoneAccount} will be attempted first + * if the user has explicitly selected it to be used as the default connection manager. + * <p> + * See {@link #getCapabilities} + */ + public static final int CAPABILITY_CONNECTION_MANAGER = 0x1; + + /** + * Flag indicating that this {@code PhoneAccount} can make phone calls in place of + * traditional SIM-based telephony calls. This account will be treated as a distinct method + * for placing calls alongside the traditional SIM-based telephony stack. This flag is + * distinct from {@link #CAPABILITY_CONNECTION_MANAGER} in that it is not allowed to manage + * calls from or use the built-in telephony stack to place its calls. + * <p> + * See {@link #getCapabilities} + * <p> + * {@hide} + */ + public static final int CAPABILITY_CALL_PROVIDER = 0x2; + + /** + * Flag indicating that this {@code PhoneAccount} represents a built-in PSTN SIM + * subscription. + * <p> + * Only the Android framework can register a {@code PhoneAccount} having this capability. + * <p> + * See {@link #getCapabilities} + */ + public static final int CAPABILITY_SIM_SUBSCRIPTION = 0x4; + + /** + * Flag indicating that this {@code PhoneAccount} is capable of placing video calls. + * <p> + * See {@link #getCapabilities} + * @hide + */ + public static final int CAPABILITY_VIDEO_CALLING = 0x8; + + /** + * Flag indicating that this {@code PhoneAccount} is capable of placing emergency calls. + * By default all PSTN {@code PhoneAccount}s are capable of placing emergency calls. + * <p> + * See {@link #getCapabilities} + */ + public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 0x10; + + /** + * URI scheme for telephone number URIs. + */ + public static final String SCHEME_TEL = "tel"; + + /** + * URI scheme for voicemail URIs. + */ + public static final String SCHEME_VOICEMAIL = "voicemail"; + + /** + * URI scheme for SIP URIs. + */ + public static final String SCHEME_SIP = "sip"; + + private final PhoneAccountHandle mAccountHandle; + private final Uri mAddress; + private final Uri mSubscriptionAddress; + private final int mCapabilities; + private final int mIconResId; + private final CharSequence mLabel; + private final CharSequence mShortDescription; + private final List<String> mSupportedUriSchemes; + + public static class Builder { + private PhoneAccountHandle mAccountHandle; + private Uri mAddress; + private Uri mSubscriptionAddress; + private int mCapabilities; + private int mIconResId; + private CharSequence mLabel; + private CharSequence mShortDescription; + private List<String> mSupportedUriSchemes = new ArrayList<String>(); + + public Builder(PhoneAccountHandle accountHandle, CharSequence label) { + this.mAccountHandle = accountHandle; + this.mLabel = label; + } + + /** + * Creates an instance of the {@link PhoneAccount.Builder} from an existing + * {@link PhoneAccount}. + * + * @param phoneAccount The {@link PhoneAccount} used to initialize the builder. + */ + public Builder(PhoneAccount phoneAccount) { + mAccountHandle = phoneAccount.getAccountHandle(); + mAddress = phoneAccount.getAddress(); + mSubscriptionAddress = phoneAccount.getSubscriptionAddress(); + mCapabilities = phoneAccount.getCapabilities(); + mIconResId = phoneAccount.getIconResId(); + mLabel = phoneAccount.getLabel(); + mShortDescription = phoneAccount.getShortDescription(); + mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes()); + } + + public Builder setAddress(Uri value) { + this.mAddress = value; + return this; + } + + public Builder setSubscriptionAddress(Uri value) { + this.mSubscriptionAddress = value; + return this; + } + + public Builder setCapabilities(int value) { + this.mCapabilities = value; + return this; + } + + public Builder setIconResId(int value) { + this.mIconResId = value; + return this; + } + + public Builder setShortDescription(CharSequence value) { + this.mShortDescription = value; + return this; + } + + /** + * Specifies an additional URI scheme supported by the {@link PhoneAccount}. + * + * @param uriScheme The URI scheme. + * @return The Builder. + * @hide + */ + public Builder addSupportedUriScheme(String uriScheme) { + if (!TextUtils.isEmpty(uriScheme) && !mSupportedUriSchemes.contains(uriScheme)) { + this.mSupportedUriSchemes.add(uriScheme); + } + return this; + } + + /** + * Specifies the URI schemes supported by the {@link PhoneAccount}. + * + * @param uriSchemes The URI schemes. + * @return The Builder. + */ + public Builder setSupportedUriSchemes(List<String> uriSchemes) { + mSupportedUriSchemes.clear(); + + if (uriSchemes != null && !uriSchemes.isEmpty()) { + for (String uriScheme : uriSchemes) { + addSupportedUriScheme(uriScheme); + } + } + return this; + } + + /** + * Creates an instance of a {@link PhoneAccount} based on the current builder settings. + * + * @return The {@link PhoneAccount}. + */ + public PhoneAccount build() { + // If no supported URI schemes were defined, assume "tel" is supported. + if (mSupportedUriSchemes.isEmpty()) { + addSupportedUriScheme(SCHEME_TEL); + } + + return new PhoneAccount( + mAccountHandle, + mAddress, + mSubscriptionAddress, + mCapabilities, + mIconResId, + mLabel, + mShortDescription, + mSupportedUriSchemes); + } + } + + private PhoneAccount( + PhoneAccountHandle account, + Uri address, + Uri subscriptionAddress, + int capabilities, + int iconResId, + CharSequence label, + CharSequence shortDescription, + List<String> supportedUriSchemes) { + mAccountHandle = account; + mAddress = address; + mSubscriptionAddress = subscriptionAddress; + mCapabilities = capabilities; + mIconResId = iconResId; + mLabel = label; + mShortDescription = shortDescription; + mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes); + } + + public static Builder builder( + PhoneAccountHandle accountHandle, + CharSequence label) { + return new Builder(accountHandle, label); + } + + /** + * Returns a builder initialized with the current {@link PhoneAccount} instance. + * + * @return The builder. + * @hide + */ + public Builder toBuilder() { return new Builder(this); } + + /** + * The unique identifier of this {@code PhoneAccount}. + * + * @return A {@code PhoneAccountHandle}. + */ + public PhoneAccountHandle getAccountHandle() { + return mAccountHandle; + } + + /** + * The address (e.g., a phone number) associated with this {@code PhoneAccount}. This + * represents the destination from which outgoing calls using this {@code PhoneAccount} + * will appear to come, if applicable, and the destination to which incoming calls using this + * {@code PhoneAccount} may be addressed. + * + * @return A address expressed as a {@code Uri}, for example, a phone number. + */ + public Uri getAddress() { + return mAddress; + } + + /** + * The raw callback number used for this {@code PhoneAccount}, as distinct from + * {@link #getAddress()}. For the majority of {@code PhoneAccount}s this should be registered + * as {@code null}. It is used by the system for SIM-based {@code PhoneAccount} registration + * where {@link android.telephony.TelephonyManager#setLine1NumberForDisplay(String, String)} + * has been used to alter the callback number. + * <p> + * + * @return The subscription number, suitable for display to the user. + */ + public Uri getSubscriptionAddress() { + return mSubscriptionAddress; + } + + /** + * The capabilities of this {@code PhoneAccount}. + * + * @return A bit field of flags describing this {@code PhoneAccount}'s capabilities. + */ + public int getCapabilities() { + return mCapabilities; + } + + /** + * Determines if this {@code PhoneAccount} has a capabilities specified by the passed in + * bit mask. + * + * @param capability The capabilities to check. + * @return {@code True} if the phone account has the capability. + */ + public boolean hasCapabilities(int capability) { + return (mCapabilities & capability) == capability; + } + + /** + * A short label describing a {@code PhoneAccount}. + * + * @return A label for this {@code PhoneAccount}. + */ + public CharSequence getLabel() { + return mLabel; + } + + /** + * A short paragraph describing this {@code PhoneAccount}. + * + * @return A description for this {@code PhoneAccount}. + */ + public CharSequence getShortDescription() { + return mShortDescription; + } + + /** + * The URI schemes supported by this {@code PhoneAccount}. + * + * @return The URI schemes. + */ + public List<String> getSupportedUriSchemes() { + return mSupportedUriSchemes; + } + + /** + * Determines if the {@link PhoneAccount} supports calls to/from addresses with a specified URI + * scheme. + * + * @param uriScheme The URI scheme to check. + * @return {@code True} if the {@code PhoneAccount} supports calls to/from addresses with the + * specified URI scheme. + */ + public boolean supportsUriScheme(String uriScheme) { + if (mSupportedUriSchemes == null || uriScheme == null) { + return false; + } + + for (String scheme : mSupportedUriSchemes) { + if (scheme != null && scheme.equals(uriScheme)) { + return true; + } + } + return false; + } + + /** + * The icon resource ID for the icon of this {@code PhoneAccount}. + * + * @return A resource ID. + */ + public int getIconResId() { + return mIconResId; + } + + /** + * An icon to represent this {@code PhoneAccount} in a user interface. + * + * @return An icon for this {@code PhoneAccount}. + */ + public Drawable getIcon(Context context) { + return getIcon(context, mIconResId); + } + + private Drawable getIcon(Context context, int resId) { + Context packageContext; + try { + packageContext = context.createPackageContext( + mAccountHandle.getComponentName().getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + Log.w(this, "Cannot find package %s", mAccountHandle.getComponentName().getPackageName()); + return null; + } + try { + return packageContext.getDrawable(resId); + } catch (NotFoundException|MissingResourceException e) { + Log.e(this, e, "Cannot find icon %d in package %s", + resId, mAccountHandle.getComponentName().getPackageName()); + return null; + } + } + + // + // Parcelable implementation + // + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mAccountHandle, 0); + out.writeParcelable(mAddress, 0); + out.writeParcelable(mSubscriptionAddress, 0); + out.writeInt(mCapabilities); + out.writeInt(mIconResId); + out.writeCharSequence(mLabel); + out.writeCharSequence(mShortDescription); + out.writeList(mSupportedUriSchemes); + } + + public static final Creator<PhoneAccount> CREATOR + = new Creator<PhoneAccount>() { + @Override + public PhoneAccount createFromParcel(Parcel in) { + return new PhoneAccount(in); + } + + @Override + public PhoneAccount[] newArray(int size) { + return new PhoneAccount[size]; + } + }; + + private PhoneAccount(Parcel in) { + ClassLoader classLoader = PhoneAccount.class.getClassLoader(); + + mAccountHandle = in.readParcelable(getClass().getClassLoader()); + mAddress = in.readParcelable(getClass().getClassLoader()); + mSubscriptionAddress = in.readParcelable(getClass().getClassLoader()); + mCapabilities = in.readInt(); + mIconResId = in.readInt(); + mLabel = in.readCharSequence(); + mShortDescription = in.readCharSequence(); + + List<String> supportedUriSchemes = new ArrayList<>(); + in.readList(supportedUriSchemes, classLoader); + mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes); + } +} diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.aidl b/telecomm/java/android/telecom/PhoneAccountHandle.aidl new file mode 100644 index 0000000..f8f9656 --- /dev/null +++ b/telecomm/java/android/telecom/PhoneAccountHandle.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2008 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 android.telecom; + +/** + * {@hide} + */ +parcelable PhoneAccountHandle; diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java new file mode 100644 index 0000000..e13df76 --- /dev/null +++ b/telecomm/java/android/telecom/PhoneAccountHandle.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.content.ComponentName; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * The unique identifier for a {@link PhoneAccount}. + */ +public class PhoneAccountHandle implements Parcelable { + private ComponentName mComponentName; + private String mId; + + public PhoneAccountHandle( + ComponentName componentName, + String id) { + mComponentName = componentName; + mId = id; + } + + /** + * The {@code ComponentName} of the {@link android.telecom.ConnectionService} which is + * responsible for making phone calls using this {@code PhoneAccountHandle}. + * + * @return A suitable {@code ComponentName}. + */ + public ComponentName getComponentName() { + return mComponentName; + } + + /** + * A string that uniquely distinguishes this particular {@code PhoneAccountHandle} from all the + * others supported by the {@link ConnectionService} that created it. + * <p> + * A {@code ConnectionService} must select identifiers that are stable for the lifetime of + * their users' relationship with their service, across many Android devices. For example, a + * good set of identifiers might be the email addresses with which with users registered for + * their accounts with a particular service. Depending on how a service chooses to operate, + * a bad set of identifiers might be an increasing series of integers + * ({@code 0}, {@code 1}, {@code 2}, ...) that are generated locally on each phone and could + * collide with values generated on other phones or after a data wipe of a given phone. + * + * @return A service-specific unique identifier for this {@code PhoneAccountHandle}. + */ + public String getId() { + return mId; + } + + @Override + public int hashCode() { + return Objects.hashCode(mComponentName) + Objects.hashCode(mId); + } + + @Override + public String toString() { + return new StringBuilder().append(mComponentName) + .append(", ") + .append(mId) + .toString(); + } + + @Override + public boolean equals(Object other) { + return other != null && + other instanceof PhoneAccountHandle && + Objects.equals(((PhoneAccountHandle) other).getComponentName(), + getComponentName()) && + Objects.equals(((PhoneAccountHandle) other).getId(), getId()); + } + + // + // Parcelable implementation. + // + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mComponentName, flags); + out.writeString(mId); + } + + public static final Creator<PhoneAccountHandle> CREATOR = new Creator<PhoneAccountHandle>() { + @Override + public PhoneAccountHandle createFromParcel(Parcel in) { + return new PhoneAccountHandle(in); + } + + @Override + public PhoneAccountHandle[] newArray(int size) { + return new PhoneAccountHandle[size]; + } + }; + + private PhoneAccountHandle(Parcel in) { + mComponentName = in.readParcelable(getClass().getClassLoader()); + mId = in.readString(); + } +} diff --git a/telecomm/java/android/telecom/PhoneCapabilities.java b/telecomm/java/android/telecom/PhoneCapabilities.java new file mode 100644 index 0000000..e73dfe2 --- /dev/null +++ b/telecomm/java/android/telecom/PhoneCapabilities.java @@ -0,0 +1,139 @@ +/* + * Copyright 2014, 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 android.telecom; + +/** + * Defines capabilities a phone call can support, such as conference calling and video telephony. + * Also defines properties of a phone call, such as whether it is using VoLTE technology. + */ +public final class PhoneCapabilities { + /** Call can currently be put on hold or unheld. */ + public static final int HOLD = 0x00000001; + + /** Call supports the hold feature. */ + public static final int SUPPORT_HOLD = 0x00000002; + + /** + * Calls within a conference can be merged. Some connection services create a conference call + * only after two calls have been merged. However, a conference call can also be added the + * moment there are more than one call. CDMA calls are implemented in this way because the call + * actions are more limited when more than one call exists. This flag allows merge to be exposed + * as a capability on the conference call instead of individual calls. + */ + public static final int MERGE_CONFERENCE = 0x00000004; + + /** Calls withing a conference can be swapped between foreground and background. */ + public static final int SWAP_CONFERENCE = 0x00000008; + + /** Call currently supports adding another call to this one. */ + public static final int ADD_CALL = 0x00000010; + + /** Call supports responding via text option. */ + public static final int RESPOND_VIA_TEXT = 0x00000020; + + /** Call can be muted. */ + public static final int MUTE = 0x00000040; + + /** + * Call supports conference call management. This capability only applies to conference calls + * which can have other calls as children. + */ + public static final int MANAGE_CONFERENCE = 0x00000080; + + /** + * Local device supports video telephony. + * @hide + */ + public static final int SUPPORTS_VT_LOCAL = 0x00000100; + + /** + * Remote device supports video telephony. + * @hide + */ + public static final int SUPPORTS_VT_REMOTE = 0x00000200; + + /** + * Call is using voice over LTE. + * @hide + */ + public static final int VoLTE = 0x00000400; + + /** + * Call is using voice over WIFI. + * @hide + */ + public static final int VoWIFI = 0x00000800; + + /** + * Call is able to be separated from its parent {@code Conference}, if any. + */ + public static final int SEPARATE_FROM_CONFERENCE = 0x00001000; + + /** + * Call is able to be individually disconnected when in a {@code Conference}. + */ + public static final int DISCONNECT_FROM_CONFERENCE = 0x00002000; + + public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CONFERENCE | SWAP_CONFERENCE + | ADD_CALL | RESPOND_VIA_TEXT | MUTE | MANAGE_CONFERENCE | SEPARATE_FROM_CONFERENCE + | DISCONNECT_FROM_CONFERENCE; + + public static String toString(int capabilities) { + StringBuilder builder = new StringBuilder(); + builder.append("[Capabilities:"); + if ((capabilities & HOLD) != 0) { + builder.append(" HOLD"); + } + if ((capabilities & SUPPORT_HOLD) != 0) { + builder.append(" SUPPORT_HOLD"); + } + if ((capabilities & MERGE_CONFERENCE) != 0) { + builder.append(" MERGE_CONFERENCE"); + } + if ((capabilities & SWAP_CONFERENCE) != 0) { + builder.append(" SWAP_CONFERENCE"); + } + if ((capabilities & ADD_CALL) != 0) { + builder.append(" ADD_CALL"); + } + if ((capabilities & RESPOND_VIA_TEXT) != 0) { + builder.append(" RESPOND_VIA_TEXT"); + } + if ((capabilities & MUTE) != 0) { + builder.append(" MUTE"); + } + if ((capabilities & MANAGE_CONFERENCE) != 0) { + builder.append(" MANAGE_CONFERENCE"); + } + if ((capabilities & SUPPORTS_VT_LOCAL) != 0) { + builder.append(" SUPPORTS_VT_LOCAL"); + } + if ((capabilities & SUPPORTS_VT_REMOTE) != 0) { + builder.append(" SUPPORTS_VT_REMOTE"); + } + if ((capabilities & VoLTE) != 0) { + builder.append(" VoLTE"); + } + if ((capabilities & VoWIFI) != 0) { + builder.append(" VoWIFI"); + } + builder.append("]"); + return builder.toString(); + } + + private PhoneCapabilities() {} +} diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java new file mode 100644 index 0000000..f931bc5 --- /dev/null +++ b/telecomm/java/android/telecom/RemoteConference.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import com.android.internal.telecom.IConnectionService; + +import android.os.RemoteException; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Represents a conference call which can contain any number of {@link Connection} objects. + */ +public final class RemoteConference { + + public abstract static class Callback { + public void onStateChanged(RemoteConference conference, int oldState, int newState) {} + public void onDisconnected(RemoteConference conference, DisconnectCause disconnectCause) {} + public void onConnectionAdded(RemoteConference conference, RemoteConnection connection) {} + public void onConnectionRemoved(RemoteConference conference, RemoteConnection connection) {} + public void onCapabilitiesChanged(RemoteConference conference, int capabilities) {} + public void onDestroyed(RemoteConference conference) {} + } + + private final String mId; + private final IConnectionService mConnectionService; + + private final Set<Callback> mCallbacks = new CopyOnWriteArraySet<>(); + private final List<RemoteConnection> mChildConnections = new CopyOnWriteArrayList<>(); + private final List<RemoteConnection> mUnmodifiableChildConnections = + Collections.unmodifiableList(mChildConnections); + + private int mState = Connection.STATE_NEW; + private DisconnectCause mDisconnectCause; + private int mCallCapabilities; + + /** {@hide} */ + RemoteConference(String id, IConnectionService connectionService) { + mId = id; + mConnectionService = connectionService; + } + + /** {@hide} */ + String getId() { + return mId; + } + + /** {@hide} */ + void setDestroyed() { + for (RemoteConnection connection : mChildConnections) { + connection.setConference(null); + } + for (Callback c : mCallbacks) { + c.onDestroyed(this); + } + } + + /** {@hide} */ + void setState(int newState) { + if (newState != Connection.STATE_ACTIVE && + newState != Connection.STATE_HOLDING && + newState != Connection.STATE_DISCONNECTED) { + Log.w(this, "Unsupported state transition for Conference call.", + Connection.stateToString(newState)); + return; + } + + if (mState != newState) { + int oldState = mState; + mState = newState; + for (Callback c : mCallbacks) { + c.onStateChanged(this, oldState, newState); + } + } + } + + /** {@hide} */ + void addConnection(RemoteConnection connection) { + if (!mChildConnections.contains(connection)) { + mChildConnections.add(connection); + connection.setConference(this); + for (Callback c : mCallbacks) { + c.onConnectionAdded(this, connection); + } + } + } + + /** {@hide} */ + void removeConnection(RemoteConnection connection) { + if (mChildConnections.contains(connection)) { + mChildConnections.remove(connection); + connection.setConference(null); + for (Callback c : mCallbacks) { + c.onConnectionRemoved(this, connection); + } + } + } + + /** {@hide} */ + void setCallCapabilities(int capabilities) { + if (mCallCapabilities != capabilities) { + mCallCapabilities = capabilities; + for (Callback c : mCallbacks) { + c.onCapabilitiesChanged(this, mCallCapabilities); + } + } + } + + /** {@hide} */ + void setDisconnected(DisconnectCause disconnectCause) { + if (mState != Connection.STATE_DISCONNECTED) { + mDisconnectCause = disconnectCause; + setState(Connection.STATE_DISCONNECTED); + for (Callback c : mCallbacks) { + c.onDisconnected(this, disconnectCause); + } + } + } + + public final List<RemoteConnection> getConnections() { + return mUnmodifiableChildConnections; + } + + public final int getState() { + return mState; + } + + public final int getCallCapabilities() { + return mCallCapabilities; + } + + public void disconnect() { + try { + mConnectionService.disconnect(mId); + } catch (RemoteException e) { + } + } + + public void separate(RemoteConnection connection) { + if (mChildConnections.contains(connection)) { + try { + mConnectionService.splitFromConference(connection.getId()); + } catch (RemoteException e) { + } + } + } + + public void hold() { + try { + mConnectionService.hold(mId); + } catch (RemoteException e) { + } + } + + public void unhold() { + try { + mConnectionService.unhold(mId); + } catch (RemoteException e) { + } + } + + public DisconnectCause getDisconnectCause() { + return mDisconnectCause; + } + + public void playDtmfTone(char digit) { + try { + mConnectionService.playDtmfTone(mId, digit); + } catch (RemoteException e) { + } + } + + public void stopDtmfTone() { + try { + mConnectionService.stopDtmfTone(mId); + } catch (RemoteException e) { + } + } + + public void setAudioState(AudioState state) { + try { + mConnectionService.onAudioStateChanged(mId, state); + } catch (RemoteException e) { + } + } + + public final void registerCallback(Callback callback) { + mCallbacks.add(callback); + } + + public final void unregisterCallback(Callback callback) { + mCallbacks.remove(callback); + } +} diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java new file mode 100644 index 0000000..9a094df --- /dev/null +++ b/telecomm/java/android/telecom/RemoteConnection.java @@ -0,0 +1,898 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import com.android.internal.telecom.IConnectionService; +import com.android.internal.telecom.IVideoCallback; +import com.android.internal.telecom.IVideoProvider; + +import android.net.Uri; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.Surface; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A connection provided to a {@link ConnectionService} by another {@code ConnectionService} + * running in a different process. + * + * @see ConnectionService#createRemoteOutgoingConnection(PhoneAccountHandle, ConnectionRequest) + * @see ConnectionService#createRemoteIncomingConnection(PhoneAccountHandle, ConnectionRequest) + */ +public final class RemoteConnection { + + public static abstract class Callback { + /** + * Invoked when the state of this {@code RemoteConnection} has changed. See + * {@link #getState()}. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param state The new state of the {@code RemoteConnection}. + */ + public void onStateChanged(RemoteConnection connection, int state) {} + + /** + * Invoked when this {@code RemoteConnection} is disconnected. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param disconnectCause The ({@see DisconnectCause}) associated with this failed + * connection. + */ + public void onDisconnected( + RemoteConnection connection, + DisconnectCause disconnectCause) {} + + /** + * Invoked when this {@code RemoteConnection} is requesting ringback. See + * {@link #isRingbackRequested()}. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param ringback Whether the {@code RemoteConnection} is requesting ringback. + */ + public void onRingbackRequested(RemoteConnection connection, boolean ringback) {} + + /** + * Indicates that the call capabilities of this {@code RemoteConnection} have changed. + * See {@link #getCallCapabilities()}. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param callCapabilities The new call capabilities of the {@code RemoteConnection}. + */ + public void onCallCapabilitiesChanged(RemoteConnection connection, int callCapabilities) {} + + /** + * Invoked when the post-dial sequence in the outgoing {@code Connection} has reached a + * pause character. This causes the post-dial signals to stop pending user confirmation. An + * implementation should present this choice to the user and invoke + * {@link RemoteConnection#postDialContinue(boolean)} when the user makes the choice. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param remainingPostDialSequence The post-dial characters that remain to be sent. + */ + public void onPostDialWait(RemoteConnection connection, String remainingPostDialSequence) {} + + /** + * Indicates that the VOIP audio status of this {@code RemoteConnection} has changed. + * See {@link #isVoipAudioMode()}. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param isVoip Whether the new audio state of the {@code RemoteConnection} is VOIP. + */ + public void onVoipAudioChanged(RemoteConnection connection, boolean isVoip) {} + + /** + * Indicates that the status hints of this {@code RemoteConnection} have changed. See + * {@link #getStatusHints()} ()}. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param statusHints The new status hints of the {@code RemoteConnection}. + */ + public void onStatusHintsChanged(RemoteConnection connection, StatusHints statusHints) {} + + /** + * Indicates that the address (e.g., phone number) of this {@code RemoteConnection} has + * changed. See {@link #getAddress()} and {@link #getAddressPresentation()}. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param address The new address of the {@code RemoteConnection}. + * @param presentation The presentation requirements for the address. + * See {@link TelecomManager} for valid values. + */ + public void onAddressChanged(RemoteConnection connection, Uri address, int presentation) {} + + /** + * Indicates that the caller display name of this {@code RemoteConnection} has changed. + * See {@link #getCallerDisplayName()} and {@link #getCallerDisplayNamePresentation()}. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param callerDisplayName The new caller display name of the {@code RemoteConnection}. + * @param presentation The presentation requirements for the handle. + * See {@link TelecomManager} for valid values. + */ + public void onCallerDisplayNameChanged( + RemoteConnection connection, String callerDisplayName, int presentation) {} + + /** + * Indicates that the video state of this {@code RemoteConnection} has changed. + * See {@link #getVideoState()}. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param videoState The new video state of the {@code RemoteConnection}. + * @hide + */ + public void onVideoStateChanged(RemoteConnection connection, int videoState) {} + + /** + * Indicates that this {@code RemoteConnection} has been destroyed. No further requests + * should be made to the {@code RemoteConnection}, and references to it should be cleared. + * + * @param connection The {@code RemoteConnection} invoking this method. + */ + public void onDestroyed(RemoteConnection connection) {} + + /** + * Indicates that the {@code RemoteConnection}s with which this {@code RemoteConnection} + * may be asked to create a conference has changed. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param conferenceableConnections The {@code RemoteConnection}s with which this + * {@code RemoteConnection} may be asked to create a conference. + */ + public void onConferenceableConnectionsChanged( + RemoteConnection connection, + List<RemoteConnection> conferenceableConnections) {} + + /** + * Indicates that the {@code VideoProvider} associated with this {@code RemoteConnection} + * has changed. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param videoProvider The new {@code VideoProvider} associated with this + * {@code RemoteConnection}. + * @hide + */ + public void onVideoProviderChanged( + RemoteConnection connection, VideoProvider videoProvider) {} + + /** + * Indicates that the {@code RemoteConference} that this {@code RemoteConnection} is a part + * of has changed. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param conference The {@code RemoteConference} of which this {@code RemoteConnection} is + * a part, which may be {@code null}. + */ + public void onConferenceChanged( + RemoteConnection connection, + RemoteConference conference) {} + } + + /** {@hide} */ + public static class VideoProvider { + + public abstract static class Listener { + public void onReceiveSessionModifyRequest( + VideoProvider videoProvider, + VideoProfile videoProfile) {} + + public void onReceiveSessionModifyResponse( + VideoProvider videoProvider, + int status, + VideoProfile requestedProfile, + VideoProfile responseProfile) {} + + public void onHandleCallSessionEvent(VideoProvider videoProvider, int event) {} + + public void onPeerDimensionsChanged(VideoProvider videoProvider, int width, int height) {} + + public void onCallDataUsageChanged(VideoProvider videoProvider, int dataUsage) {} + + public void onCameraCapabilitiesChanged( + VideoProvider videoProvider, + CameraCapabilities cameraCapabilities) {} + } + + private final IVideoCallback mVideoCallbackDelegate = new IVideoCallback() { + @Override + public void receiveSessionModifyRequest(VideoProfile videoProfile) { + for (Listener l : mListeners) { + l.onReceiveSessionModifyRequest(VideoProvider.this, videoProfile); + } + } + + @Override + public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile, + VideoProfile responseProfile) { + for (Listener l : mListeners) { + l.onReceiveSessionModifyResponse( + VideoProvider.this, + status, + requestedProfile, + responseProfile); + } + } + + @Override + public void handleCallSessionEvent(int event) { + for (Listener l : mListeners) { + l.onHandleCallSessionEvent(VideoProvider.this, event); + } + } + + @Override + public void changePeerDimensions(int width, int height) { + for (Listener l : mListeners) { + l.onPeerDimensionsChanged(VideoProvider.this, width, height); + } + } + + @Override + public void changeCallDataUsage(int dataUsage) { + for (Listener l : mListeners) { + l.onCallDataUsageChanged(VideoProvider.this, dataUsage); + } + } + + @Override + public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) { + for (Listener l : mListeners) { + l.onCameraCapabilitiesChanged(VideoProvider.this, cameraCapabilities); + } + } + + @Override + public IBinder asBinder() { + return null; + } + }; + + private final VideoCallbackServant mVideoCallbackServant = + new VideoCallbackServant(mVideoCallbackDelegate); + + private final IVideoProvider mVideoProviderBinder; + + /** + * 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)); + + public VideoProvider(IVideoProvider videoProviderBinder) { + mVideoProviderBinder = videoProviderBinder; + try { + mVideoProviderBinder.setVideoCallback(mVideoCallbackServant.getStub().asBinder()); + } catch (RemoteException e) { + } + } + + public void addListener(Listener l) { + mListeners.add(l); + } + + public void removeListener(Listener l) { + mListeners.remove(l); + } + + public void setCamera(String cameraId) { + try { + mVideoProviderBinder.setCamera(cameraId); + } catch (RemoteException e) { + } + } + + public void setPreviewSurface(Surface surface) { + try { + mVideoProviderBinder.setPreviewSurface(surface); + } catch (RemoteException e) { + } + } + + public void setDisplaySurface(Surface surface) { + try { + mVideoProviderBinder.setDisplaySurface(surface); + } catch (RemoteException e) { + } + } + + public void setDeviceOrientation(int rotation) { + try { + mVideoProviderBinder.setDeviceOrientation(rotation); + } catch (RemoteException e) { + } + } + + public void setZoom(float value) { + try { + mVideoProviderBinder.setZoom(value); + } catch (RemoteException e) { + } + } + + public void sendSessionModifyRequest(VideoProfile reqProfile) { + try { + mVideoProviderBinder.sendSessionModifyRequest(reqProfile); + } catch (RemoteException e) { + } + } + + public void sendSessionModifyResponse(VideoProfile responseProfile) { + try { + mVideoProviderBinder.sendSessionModifyResponse(responseProfile); + } catch (RemoteException e) { + } + } + + public void requestCameraCapabilities() { + try { + mVideoProviderBinder.requestCameraCapabilities(); + } catch (RemoteException e) { + } + } + + public void requestCallDataUsage() { + try { + mVideoProviderBinder.requestCallDataUsage(); + } catch (RemoteException e) { + } + } + + public void setPauseImage(String uri) { + try { + mVideoProviderBinder.setPauseImage(uri); + } catch (RemoteException e) { + } + } + } + + private IConnectionService mConnectionService; + private final String mConnectionId; + /** + * 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<Callback> mCallbacks = Collections.newSetFromMap( + new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1)); + private final List<RemoteConnection> mConferenceableConnections = new ArrayList<>(); + private final List<RemoteConnection> mUnmodifiableconferenceableConnections = + Collections.unmodifiableList(mConferenceableConnections); + + private int mState = Connection.STATE_NEW; + private DisconnectCause mDisconnectCause; + private boolean mRingbackRequested; + private boolean mConnected; + private int mCallCapabilities; + private int mVideoState; + private VideoProvider mVideoProvider; + private boolean mIsVoipAudioMode; + private StatusHints mStatusHints; + private Uri mAddress; + private int mAddressPresentation; + private String mCallerDisplayName; + private int mCallerDisplayNamePresentation; + private RemoteConference mConference; + + /** + * @hide + */ + RemoteConnection( + String id, + IConnectionService connectionService, + ConnectionRequest request) { + mConnectionId = id; + mConnectionService = connectionService; + mConnected = true; + mState = Connection.STATE_INITIALIZING; + } + + /** + * Create a RemoteConnection which is used for failed connections. Note that using it for any + * "real" purpose will almost certainly fail. Callers should note the failure and act + * accordingly (moving on to another RemoteConnection, for example) + * + * @param disconnectCause The reason for the failed connection. + * @hide + */ + RemoteConnection(DisconnectCause disconnectCause) { + this("NULL", null, null); + mConnected = false; + mState = Connection.STATE_DISCONNECTED; + mDisconnectCause = disconnectCause; + } + + /** + * Adds a callback to this {@code RemoteConnection}. + * + * @param callback A {@code Callback}. + */ + public void registerCallback(Callback callback) { + mCallbacks.add(callback); + } + + /** + * Removes a callback from this {@code RemoteConnection}. + * + * @param callback A {@code Callback}. + */ + public void unregisterCallback(Callback callback) { + if (callback != null) { + mCallbacks.remove(callback); + } + } + + /** + * Obtains the state of this {@code RemoteConnection}. + * + * @return A state value, chosen from the {@code STATE_*} constants. + */ + public int getState() { + return mState; + } + + /** + * @return For a {@link Connection#STATE_DISCONNECTED} {@code RemoteConnection}, the + * disconnect cause expressed as a code chosen from among those declared in + * {@link DisconnectCause}. + */ + public DisconnectCause getDisconnectCause() { + return mDisconnectCause; + } + + /** + * @return A bitmask of the capabilities of the {@code RemoteConnection}, as defined in + * {@link PhoneCapabilities}. + */ + public int getCallCapabilities() { + return mCallCapabilities; + } + + /** + * @return {@code true} if the {@code RemoteConnection}'s current audio mode is VOIP. + */ + public boolean isVoipAudioMode() { + return mIsVoipAudioMode; + } + + /** + * @return The current {@link StatusHints} of this {@code RemoteConnection}, + * or {@code null} if none have been set. + */ + public StatusHints getStatusHints() { + return mStatusHints; + } + + /** + * @return The address (e.g., phone number) to which the {@code RemoteConnection} is currently + * connected. + */ + public Uri getAddress() { + return mAddress; + } + + /** + * @return The presentation requirements for the address. See {@link TelecomManager} for valid + * values. + */ + public int getAddressPresentation() { + return mAddressPresentation; + } + + /** + * @return The display name for the caller. + */ + public CharSequence getCallerDisplayName() { + return mCallerDisplayName; + } + + /** + * @return The presentation requirements for the caller display name. See + * {@link TelecomManager} for valid values. + */ + public int getCallerDisplayNamePresentation() { + return mCallerDisplayNamePresentation; + } + + /** + * @return The video state of the {@code RemoteConnection}. See + * {@link VideoProfile.VideoState}. + * @hide + */ + public int getVideoState() { + return mVideoState; + } + + /** + * @return The video provider associated with this {@code RemoteConnection}. + * @hide + */ + public final VideoProvider getVideoProvider() { + return mVideoProvider; + } + + /** + * @return Whether the {@code RemoteConnection} is requesting that the framework play a + * ringback tone on its behalf. + */ + public boolean isRingbackRequested() { + return false; + } + + /** + * Instructs this {@code RemoteConnection} to abort. + */ + public void abort() { + try { + if (mConnected) { + mConnectionService.abort(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + /** + * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer. + */ + public void answer() { + try { + if (mConnected) { + mConnectionService.answer(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + /** + * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer. + * @param videoState The video state in which to answer the call. + * @hide + */ + public void answer(int videoState) { + try { + if (mConnected) { + mConnectionService.answerVideo(mConnectionId, videoState); + } + } catch (RemoteException ignored) { + } + } + + /** + * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to reject. + */ + public void reject() { + try { + if (mConnected) { + mConnectionService.reject(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + /** + * Instructs this {@code RemoteConnection} to go on hold. + */ + public void hold() { + try { + if (mConnected) { + mConnectionService.hold(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + /** + * Instructs this {@link Connection#STATE_HOLDING} call to release from hold. + */ + public void unhold() { + try { + if (mConnected) { + mConnectionService.unhold(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + /** + * Instructs this {@code RemoteConnection} to disconnect. + */ + public void disconnect() { + try { + if (mConnected) { + mConnectionService.disconnect(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + /** + * Instructs this {@code RemoteConnection} to play a dual-tone multi-frequency signaling + * (DTMF) tone. + * + * Any other currently playing DTMF tone in the specified call is immediately stopped. + * + * @param digit A character representing the DTMF digit for which to play the tone. This + * value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}. + */ + public void playDtmfTone(char digit) { + try { + if (mConnected) { + mConnectionService.playDtmfTone(mConnectionId, digit); + } + } catch (RemoteException ignored) { + } + } + + /** + * Instructs this {@code RemoteConnection} to stop any dual-tone multi-frequency signaling + * (DTMF) tone currently playing. + * + * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is + * currently playing, this method will do nothing. + */ + public void stopDtmfTone() { + try { + if (mConnected) { + mConnectionService.stopDtmfTone(mConnectionId); + } + } catch (RemoteException ignored) { + } + } + + /** + * Instructs this {@code RemoteConnection} to continue playing a post-dial DTMF string. + * + * A post-dial DTMF string is a string of digits following the first instance of either + * {@link TelecomManager#DTMF_CHARACTER_WAIT} or {@link TelecomManager#DTMF_CHARACTER_PAUSE}. + * These digits are immediately sent as DTMF tones to the recipient as soon as the + * connection is made. + * + * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_PAUSE} symbol, this + * {@code RemoteConnection} will temporarily pause playing the tones for a pre-defined period + * of time. + * + * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, this + * {@code RemoteConnection} will pause playing the tones and notify callbackss via + * {@link Callback#onPostDialWait(RemoteConnection, String)}. At this point, the in-call app + * should display to the user an indication of this state and an affordance to continue + * the postdial sequence. When the user decides to continue the postdial sequence, the in-call + * app should invoke the {@link #postDialContinue(boolean)} method. + * + * @param proceed Whether or not to continue with the post-dial sequence. + */ + public void postDialContinue(boolean proceed) { + try { + if (mConnected) { + mConnectionService.onPostDialContinue(mConnectionId, proceed); + } + } catch (RemoteException ignored) { + } + } + + /** + * Set the audio state of this {@code RemoteConnection}. + * + * @param state The audio state of this {@code RemoteConnection}. + */ + public void setAudioState(AudioState state) { + try { + if (mConnected) { + mConnectionService.onAudioStateChanged(mConnectionId, state); + } + } catch (RemoteException ignored) { + } + } + + /** + * Obtain the {@code RemoteConnection}s with which this {@code RemoteConnection} may be + * successfully asked to create a conference with. + * + * @return The {@code RemoteConnection}s with which this {@code RemoteConnection} may be + * merged into a {@link RemoteConference}. + */ + public List<RemoteConnection> getConferenceableConnections() { + return mUnmodifiableconferenceableConnections; + } + + /** + * Obtain the {@code RemoteConference} that this {@code RemoteConnection} may be a part + * of, or {@code null} if there is no such {@code RemoteConference}. + * + * @return A {@code RemoteConference} or {@code null}; + */ + public RemoteConference getConference() { + return mConference; + } + + /** {@hide} */ + String getId() { + return mConnectionId; + } + + /** {@hide} */ + IConnectionService getConnectionService() { + return mConnectionService; + } + + /** + * @hide + */ + void setState(int state) { + if (mState != state) { + mState = state; + for (Callback c: mCallbacks) { + c.onStateChanged(this, state); + } + } + } + + /** + * @hide + */ + void setDisconnected(DisconnectCause disconnectCause) { + if (mState != Connection.STATE_DISCONNECTED) { + mState = Connection.STATE_DISCONNECTED; + mDisconnectCause = disconnectCause; + + for (Callback c : mCallbacks) { + c.onDisconnected(this, mDisconnectCause); + } + } + } + + /** + * @hide + */ + void setRingbackRequested(boolean ringback) { + if (mRingbackRequested != ringback) { + mRingbackRequested = ringback; + for (Callback c : mCallbacks) { + c.onRingbackRequested(this, ringback); + } + } + } + + /** + * @hide + */ + void setCallCapabilities(int callCapabilities) { + mCallCapabilities = callCapabilities; + for (Callback c : mCallbacks) { + c.onCallCapabilitiesChanged(this, callCapabilities); + } + } + + /** + * @hide + */ + void setDestroyed() { + if (!mCallbacks.isEmpty()) { + // Make sure that the callbacks are notified that the call is destroyed first. + if (mState != Connection.STATE_DISCONNECTED) { + setDisconnected( + new DisconnectCause(DisconnectCause.ERROR, "Connection destroyed.")); + } + + for (Callback c : mCallbacks) { + c.onDestroyed(this); + } + mCallbacks.clear(); + + mConnected = false; + } + } + + /** + * @hide + */ + void setPostDialWait(String remainingDigits) { + for (Callback c : mCallbacks) { + c.onPostDialWait(this, remainingDigits); + } + } + + /** + * @hide + */ + void setVideoState(int videoState) { + mVideoState = videoState; + for (Callback c : mCallbacks) { + c.onVideoStateChanged(this, videoState); + } + } + + /** + * @hide + */ + void setVideoProvider(VideoProvider videoProvider) { + mVideoProvider = videoProvider; + for (Callback c : mCallbacks) { + c.onVideoProviderChanged(this, videoProvider); + } + } + + /** @hide */ + void setIsVoipAudioMode(boolean isVoip) { + mIsVoipAudioMode = isVoip; + for (Callback c : mCallbacks) { + c.onVoipAudioChanged(this, isVoip); + } + } + + /** @hide */ + void setStatusHints(StatusHints statusHints) { + mStatusHints = statusHints; + for (Callback c : mCallbacks) { + c.onStatusHintsChanged(this, statusHints); + } + } + + /** @hide */ + void setAddress(Uri address, int presentation) { + mAddress = address; + mAddressPresentation = presentation; + for (Callback c : mCallbacks) { + c.onAddressChanged(this, address, presentation); + } + } + + /** @hide */ + void setCallerDisplayName(String callerDisplayName, int presentation) { + mCallerDisplayName = callerDisplayName; + mCallerDisplayNamePresentation = presentation; + for (Callback c : mCallbacks) { + c.onCallerDisplayNameChanged(this, callerDisplayName, presentation); + } + } + + /** @hide */ + void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) { + mConferenceableConnections.clear(); + mConferenceableConnections.addAll(conferenceableConnections); + for (Callback c : mCallbacks) { + c.onConferenceableConnectionsChanged(this, mUnmodifiableconferenceableConnections); + } + } + + /** @hide */ + void setConference(RemoteConference conference) { + if (mConference != conference) { + mConference = conference; + for (Callback c : mCallbacks) { + c.onConferenceChanged(this, conference); + } + } + } + + /** + * Create a RemoteConnection represents a failure, and which will be in + * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost + * certainly result in bad things happening. Do not do this. + * + * @return a failed {@link RemoteConnection} + * + * @hide + */ + public static RemoteConnection failure(DisconnectCause disconnectCause) { + return new RemoteConnection(disconnectCause); + } +} diff --git a/telecomm/java/android/telecom/RemoteConnectionManager.java b/telecomm/java/android/telecom/RemoteConnectionManager.java new file mode 100644 index 0000000..0366509 --- /dev/null +++ b/telecomm/java/android/telecom/RemoteConnectionManager.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 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 + R* limitations under the License. + */ + +package android.telecom; + +import android.content.ComponentName; +import android.os.RemoteException; + +import com.android.internal.telecom.IConnectionService; + +import java.util.HashMap; +import java.util.Map; + +/** + * @hide + */ +public class RemoteConnectionManager { + private final Map<ComponentName, RemoteConnectionService> mRemoteConnectionServices = + new HashMap<>(); + private final ConnectionService mOurConnectionServiceImpl; + + public RemoteConnectionManager(ConnectionService ourConnectionServiceImpl) { + mOurConnectionServiceImpl = ourConnectionServiceImpl; + } + + void addConnectionService( + ComponentName componentName, + IConnectionService outgoingConnectionServiceRpc) { + if (!mRemoteConnectionServices.containsKey(componentName)) { + try { + RemoteConnectionService remoteConnectionService = new RemoteConnectionService( + outgoingConnectionServiceRpc, + mOurConnectionServiceImpl); + mRemoteConnectionServices.put(componentName, remoteConnectionService); + } catch (RemoteException ignored) { + } + } + } + + public RemoteConnection createRemoteConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request, + boolean isIncoming) { + PhoneAccountHandle accountHandle = request.getAccountHandle(); + if (accountHandle == null) { + throw new IllegalArgumentException("accountHandle must be specified."); + } + + ComponentName componentName = request.getAccountHandle().getComponentName(); + if (!mRemoteConnectionServices.containsKey(componentName)) { + throw new UnsupportedOperationException("accountHandle not supported: " + + componentName); + } + + RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName); + if (remoteService != null) { + return remoteService.createRemoteConnection( + connectionManagerPhoneAccount, request, isIncoming); + } + return null; + } + + public void conferenceRemoteConnections(RemoteConnection a, RemoteConnection b) { + if (a.getConnectionService() == b.getConnectionService()) { + try { + a.getConnectionService().conference(a.getId(), b.getId()); + } catch (RemoteException e) { + } + } else { + Log.w(this, "Request to conference incompatible remote connections (%s,%s) (%s,%s)", + a.getConnectionService(), a.getId(), + b.getConnectionService(), b.getId()); + } + } +} diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java new file mode 100644 index 0000000..03b38c2 --- /dev/null +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.net.Uri; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; + +import com.android.internal.telecom.IConnectionService; +import com.android.internal.telecom.IConnectionServiceAdapter; +import com.android.internal.telecom.IVideoProvider; +import com.android.internal.telecom.RemoteServiceCallback; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.List; +import java.util.UUID; + +/** + * Remote connection service which other connection services can use to place calls on their behalf. + * + * @hide + */ +final class RemoteConnectionService { + + private static final RemoteConnection NULL_CONNECTION = + new RemoteConnection("NULL", null, null); + + private static final RemoteConference NULL_CONFERENCE = + new RemoteConference("NULL", null); + + private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() { + @Override + public void handleCreateConnectionComplete( + String id, + ConnectionRequest request, + ParcelableConnection parcel) { + RemoteConnection connection = + findConnectionForAction(id, "handleCreateConnectionSuccessful"); + if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) { + mPendingConnections.remove(connection); + // Unconditionally initialize the connection ... + connection.setCallCapabilities(parcel.getCapabilities()); + connection.setAddress( + parcel.getHandle(), parcel.getHandlePresentation()); + connection.setCallerDisplayName( + parcel.getCallerDisplayName(), + parcel.getCallerDisplayNamePresentation()); + // Set state after handle so that the client can identify the connection. + connection.setState(parcel.getState()); + List<RemoteConnection> conferenceable = new ArrayList<>(); + for (String confId : parcel.getConferenceableConnectionIds()) { + if (mConnectionById.containsKey(confId)) { + conferenceable.add(mConnectionById.get(confId)); + } + } + connection.setConferenceableConnections(conferenceable); + connection.setVideoState(parcel.getVideoState()); + if (connection.getState() == Connection.STATE_DISCONNECTED) { + // ... then, if it was created in a disconnected state, that indicates + // failure on the providing end, so immediately mark it destroyed + connection.setDestroyed(); + } + } + } + + @Override + public void setActive(String callId) { + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "setActive") + .setState(Connection.STATE_ACTIVE); + } else { + findConferenceForAction(callId, "setActive") + .setState(Connection.STATE_ACTIVE); + } + } + + @Override + public void setRinging(String callId) { + findConnectionForAction(callId, "setRinging") + .setState(Connection.STATE_RINGING); + } + + @Override + public void setDialing(String callId) { + findConnectionForAction(callId, "setDialing") + .setState(Connection.STATE_DIALING); + } + + @Override + public void setDisconnected(String callId, DisconnectCause disconnectCause) { + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "setDisconnected") + .setDisconnected(disconnectCause); + } else { + findConferenceForAction(callId, "setDisconnected") + .setDisconnected(disconnectCause); + } + } + + @Override + public void setOnHold(String callId) { + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "setOnHold") + .setState(Connection.STATE_HOLDING); + } else { + findConferenceForAction(callId, "setOnHold") + .setState(Connection.STATE_HOLDING); + } + } + + @Override + public void setRingbackRequested(String callId, boolean ringing) { + findConnectionForAction(callId, "setRingbackRequested") + .setRingbackRequested(ringing); + } + + @Override + public void setCallCapabilities(String callId, int callCapabilities) { + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "setCallCapabilities") + .setCallCapabilities(callCapabilities); + } else { + findConferenceForAction(callId, "setCallCapabilities") + .setCallCapabilities(callCapabilities); + } + } + + @Override + public void setIsConferenced(String callId, String conferenceCallId) { + // Note: callId should not be null; conferenceCallId may be null + RemoteConnection connection = + findConnectionForAction(callId, "setIsConferenced"); + if (connection != NULL_CONNECTION) { + if (conferenceCallId == null) { + // 'connection' is being split from its conference + if (connection.getConference() != null) { + connection.getConference().removeConnection(connection); + } + } else { + RemoteConference conference = + findConferenceForAction(conferenceCallId, "setIsConferenced"); + if (conference != NULL_CONFERENCE) { + conference.addConnection(connection); + } + } + } + } + + @Override + public void addConferenceCall( + final String callId, + ParcelableConference parcel) { + RemoteConference conference = new RemoteConference(callId, + mOutgoingConnectionServiceRpc); + + for (String id : parcel.getConnectionIds()) { + RemoteConnection c = mConnectionById.get(id); + if (c != null) { + conference.addConnection(c); + } + } + + if (conference.getConnections().size() == 0) { + // A conference was created, but none of its connections are ones that have been + // created by, and therefore being tracked by, this remote connection service. It + // is of no interest to us. + return; + } + + conference.setState(parcel.getState()); + conference.setCallCapabilities(parcel.getCapabilities()); + mConferenceById.put(callId, conference); + conference.registerCallback(new RemoteConference.Callback() { + @Override + public void onDestroyed(RemoteConference c) { + mConferenceById.remove(callId); + maybeDisconnectAdapter(); + } + }); + + mOurConnectionServiceImpl.addRemoteConference(conference); + } + + @Override + public void removeCall(String callId) { + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "removeCall") + .setDestroyed(); + } else { + findConferenceForAction(callId, "removeCall") + .setDestroyed(); + } + } + + @Override + public void onPostDialWait(String callId, String remaining) { + findConnectionForAction(callId, "onPostDialWait") + .setPostDialWait(remaining); + } + + @Override + public void queryRemoteConnectionServices(RemoteServiceCallback callback) { + // Not supported from remote connection service. + } + + @Override + public void setVideoProvider(String callId, IVideoProvider videoProvider) { + findConnectionForAction(callId, "setVideoProvider") + .setVideoProvider(new RemoteConnection.VideoProvider(videoProvider)); + } + + @Override + public void setVideoState(String callId, int videoState) { + findConnectionForAction(callId, "setVideoState") + .setVideoState(videoState); + } + + @Override + public void setIsVoipAudioMode(String callId, boolean isVoip) { + findConnectionForAction(callId, "setIsVoipAudioMode") + .setIsVoipAudioMode(isVoip); + } + + @Override + public void setStatusHints(String callId, StatusHints statusHints) { + findConnectionForAction(callId, "setStatusHints") + .setStatusHints(statusHints); + } + + @Override + public void setAddress(String callId, Uri address, int presentation) { + findConnectionForAction(callId, "setAddress") + .setAddress(address, presentation); + } + + @Override + public void setCallerDisplayName(String callId, String callerDisplayName, + int presentation) { + findConnectionForAction(callId, "setCallerDisplayName") + .setCallerDisplayName(callerDisplayName, presentation); + } + + @Override + public IBinder asBinder() { + throw new UnsupportedOperationException(); + } + + @Override + public final void setConferenceableConnections( + String callId, List<String> conferenceableConnectionIds) { + List<RemoteConnection> conferenceable = new ArrayList<>(); + for (String id : conferenceableConnectionIds) { + if (mConnectionById.containsKey(id)) { + conferenceable.add(mConnectionById.get(id)); + } + } + + findConnectionForAction(callId, "setConferenceableConnections") + .setConferenceableConnections(conferenceable); + } + }; + + private final ConnectionServiceAdapterServant mServant = + new ConnectionServiceAdapterServant(mServantDelegate); + + private final DeathRecipient mDeathRecipient = new DeathRecipient() { + @Override + public void binderDied() { + for (RemoteConnection c : mConnectionById.values()) { + c.setDestroyed(); + } + for (RemoteConference c : mConferenceById.values()) { + c.setDestroyed(); + } + mConnectionById.clear(); + mConferenceById.clear(); + mPendingConnections.clear(); + mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0); + } + }; + + private final IConnectionService mOutgoingConnectionServiceRpc; + private final ConnectionService mOurConnectionServiceImpl; + private final Map<String, RemoteConnection> mConnectionById = new HashMap<>(); + private final Map<String, RemoteConference> mConferenceById = new HashMap<>(); + private final Set<RemoteConnection> mPendingConnections = new HashSet<>(); + + RemoteConnectionService( + IConnectionService outgoingConnectionServiceRpc, + ConnectionService ourConnectionServiceImpl) throws RemoteException { + mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc; + mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0); + mOurConnectionServiceImpl = ourConnectionServiceImpl; + } + + @Override + public String toString() { + return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]"; + } + + final RemoteConnection createRemoteConnection( + PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request, + boolean isIncoming) { + final String id = UUID.randomUUID().toString(); + final ConnectionRequest newRequest = new ConnectionRequest( + request.getAccountHandle(), + request.getAddress(), + request.getExtras(), + request.getVideoState()); + try { + if (mConnectionById.isEmpty()) { + mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub()); + } + RemoteConnection connection = + new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest); + mPendingConnections.add(connection); + mConnectionById.put(id, connection); + mOutgoingConnectionServiceRpc.createConnection( + connectionManagerPhoneAccount, + id, + newRequest, + isIncoming); + connection.registerCallback(new RemoteConnection.Callback() { + @Override + public void onDestroyed(RemoteConnection connection) { + mConnectionById.remove(id); + maybeDisconnectAdapter(); + } + }); + return connection; + } catch (RemoteException e) { + return RemoteConnection.failure( + new DisconnectCause(DisconnectCause.ERROR, e.toString())); + } + } + + private RemoteConnection findConnectionForAction( + String callId, String action) { + if (mConnectionById.containsKey(callId)) { + return mConnectionById.get(callId); + } + Log.w(this, "%s - Cannot find Connection %s", action, callId); + return NULL_CONNECTION; + } + + private RemoteConference findConferenceForAction( + String callId, String action) { + if (mConferenceById.containsKey(callId)) { + return mConferenceById.get(callId); + } + Log.w(this, "%s - Cannot find Conference %s", action, callId); + return NULL_CONFERENCE; + } + + private void maybeDisconnectAdapter() { + if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) { + try { + mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub()); + } catch (RemoteException e) { + } + } + } +} diff --git a/telecomm/java/android/telecom/Response.java b/telecomm/java/android/telecom/Response.java new file mode 100644 index 0000000..ce7a761 --- /dev/null +++ b/telecomm/java/android/telecom/Response.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +/** + * @hide + */ +public interface Response<IN, OUT> { + + /** + * Provide a set of results. + * + * @param request The original request. + * @param result The results. + */ + void onResult(IN request, OUT... result); + + /** + * Indicates the inability to provide results. + * + * @param request The original request. + * @param code An integer code indicating the reason for failure. + * @param msg A message explaining the reason for failure. + */ + void onError(IN request, int code, String msg); +} diff --git a/telecomm/java/android/telecom/StatusHints.aidl b/telecomm/java/android/telecom/StatusHints.aidl new file mode 100644 index 0000000..ae7df2e --- /dev/null +++ b/telecomm/java/android/telecom/StatusHints.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2008 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 android.telecom; + +/** + * {@hide} + */ +parcelable StatusHints; diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java new file mode 100644 index 0000000..a32eae7 --- /dev/null +++ b/telecomm/java/android/telecom/StatusHints.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.MissingResourceException; +import java.util.Objects; + +/** + * Contains status label and icon displayed in the in-call UI. + */ +public final class StatusHints implements Parcelable { + + private final ComponentName mPackageName; + private final CharSequence mLabel; + private final int mIconResId; + private final Bundle mExtras; + + public StatusHints(ComponentName packageName, CharSequence label, int iconResId, + Bundle extras) { + mPackageName = packageName; + mLabel = label; + mIconResId = iconResId; + mExtras = extras; + } + + /** + * @return A package used to load the icon. + */ + public ComponentName getPackageName() { + return mPackageName; + } + + /** + * @return The label displayed in the in-call UI. + */ + public CharSequence getLabel() { + return mLabel; + } + + /** + * The icon resource ID for the icon to show. + * + * @return A resource ID. + */ + public int getIconResId() { + return mIconResId; + } + + /** + * @return An icon displayed in the in-call UI. + */ + public Drawable getIcon(Context context) { + return getIcon(context, mIconResId); + } + + /** + * @return Extra data used to display status. + */ + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mPackageName, flags); + out.writeCharSequence(mLabel); + out.writeInt(mIconResId); + out.writeParcelable(mExtras, 0); + } + + public static final Creator<StatusHints> CREATOR + = new Creator<StatusHints>() { + public StatusHints createFromParcel(Parcel in) { + return new StatusHints(in); + } + + public StatusHints[] newArray(int size) { + return new StatusHints[size]; + } + }; + + private StatusHints(Parcel in) { + mPackageName = in.readParcelable(getClass().getClassLoader()); + mLabel = in.readCharSequence(); + mIconResId = in.readInt(); + mExtras = in.readParcelable(getClass().getClassLoader()); + } + + private Drawable getIcon(Context context, int resId) { + Context packageContext; + try { + packageContext = context.createPackageContext(mPackageName.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(this, e, "Cannot find package %s", mPackageName.getPackageName()); + return null; + } + try { + return packageContext.getDrawable(resId); + } catch (MissingResourceException e) { + Log.e(this, e, "Cannot find icon %d in package %s", + resId, mPackageName.getPackageName()); + return null; + } + } + + @Override + public boolean equals(Object other) { + if (other != null && other instanceof StatusHints) { + StatusHints otherHints = (StatusHints) other; + return Objects.equals(otherHints.getPackageName(), getPackageName()) && + Objects.equals(otherHints.getLabel(), getLabel()) && + otherHints.getIconResId() == getIconResId() && + Objects.equals(otherHints.getExtras(), getExtras()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(mPackageName) + Objects.hashCode(mLabel) + mIconResId + + Objects.hashCode(mExtras); + } +} diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java new file mode 100644 index 0000000..a91d92f --- /dev/null +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -0,0 +1,858 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.annotation.SystemApi; +import android.content.ComponentName; +import android.content.Context; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.telephony.TelephonyManager; +import android.util.Log; + +import com.android.internal.telecom.ITelecomService; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Provides access to Telecom-related functionality. + * TODO: Move this all into PhoneManager. + */ +public class TelecomManager { + + /** + * Activity action: Starts the UI for handing an incoming call. This intent starts the in-call + * UI by notifying the Telecom system that an incoming call exists for a specific call service + * (see {@link android.telecom.ConnectionService}). Telecom reads the Intent extras to find + * and bind to the appropriate {@link android.telecom.ConnectionService} which Telecom will + * ultimately use to control and get information about the call. + * <p> + * Input: get*Extra field {@link #EXTRA_PHONE_ACCOUNT_HANDLE} contains the component name of the + * {@link android.telecom.ConnectionService} that Telecom should bind to. Telecom will then + * ask the connection service for more information about the call prior to showing any UI. + * + * @hide + */ + public static final String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL"; + + /** + * The {@link android.content.Intent} action used to configure a + * {@link android.telecom.ConnectionService}. + */ + public static final String ACTION_CONNECTION_SERVICE_CONFIGURE = + "android.telecom.action.CONNECTION_SERVICE_CONFIGURE"; + + /** + * The {@link android.content.Intent} action used to show the call settings page. + */ + public static final String ACTION_SHOW_CALL_SETTINGS = + "android.telecom.action.SHOW_CALL_SETTINGS"; + + /** + * The {@link android.content.Intent} action used to show the settings page used to configure + * {@link PhoneAccount} preferences. + */ + public static final String ACTION_CHANGE_PHONE_ACCOUNTS = + "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; + + /** + * Optional extra for {@link android.content.Intent#ACTION_CALL} containing a boolean that + * determines whether the speakerphone should be automatically turned on for an outgoing call. + */ + public static final String EXTRA_START_CALL_WITH_SPEAKERPHONE = + "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE"; + + /** + * Optional extra for {@link android.content.Intent#ACTION_CALL} containing an integer that + * determines the desired video state for an outgoing call. + * Valid options: + * {@link VideoProfile.VideoState#AUDIO_ONLY}, + * {@link VideoProfile.VideoState#BIDIRECTIONAL}, + * {@link VideoProfile.VideoState#RX_ENABLED}, + * {@link VideoProfile.VideoState#TX_ENABLED}. + * @hide + */ + public static final String EXTRA_START_CALL_WITH_VIDEO_STATE = + "android.telecom.extra.START_CALL_WITH_VIDEO_STATE"; + + /** + * The extra used with an {@link android.content.Intent#ACTION_CALL} and + * {@link android.content.Intent#ACTION_DIAL} {@code Intent} to specify a + * {@link PhoneAccountHandle} to use when making the call. + * <p class="note"> + * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_PHONE_ACCOUNT_HANDLE = + "android.telecom.extra.PHONE_ACCOUNT_HANDLE"; + + /** + * Optional extra for {@link #ACTION_INCOMING_CALL} containing a {@link Bundle} which contains + * metadata about the call. This {@link Bundle} will be returned to the + * {@link ConnectionService}. + * + * @hide + */ + public static final String EXTRA_INCOMING_CALL_EXTRAS = + "android.telecom.extra.INCOMING_CALL_EXTRAS"; + + /** + * Optional extra for {@link android.content.Intent#ACTION_CALL} and + * {@link android.content.Intent#ACTION_DIAL} {@code Intent} containing a {@link Bundle} + * which contains metadata about the call. This {@link Bundle} will be saved into + * {@code Call.Details}. + * + * @hide + */ + public static final String EXTRA_OUTGOING_CALL_EXTRAS = + "android.telecom.extra.OUTGOING_CALL_EXTRAS"; + + /** + * Optional extra for {@link android.telephony.TelephonyManager#ACTION_PHONE_STATE_CHANGED} + * containing the disconnect code. + */ + public static final String EXTRA_CALL_DISCONNECT_CAUSE = + "android.telecom.extra.CALL_DISCONNECT_CAUSE"; + + /** + * Optional extra for {@link android.telephony.TelephonyManager#ACTION_PHONE_STATE_CHANGED} + * containing the disconnect message. + */ + public static final String EXTRA_CALL_DISCONNECT_MESSAGE = + "android.telecom.extra.CALL_DISCONNECT_MESSAGE"; + + /** + * Optional extra for {@link android.telephony.TelephonyManager#ACTION_PHONE_STATE_CHANGED} + * containing the component name of the associated connection service. + */ + public static final String EXTRA_CONNECTION_SERVICE = + "android.telecom.extra.CONNECTION_SERVICE"; + + /** + * An optional {@link android.content.Intent#ACTION_CALL} intent extra denoting the + * package name of the app specifying an alternative gateway for the call. + * The value is a string. + * + * (The following comment corresponds to the all GATEWAY_* extras) + * An app which sends the {@link android.content.Intent#ACTION_CALL} intent can specify an + * alternative address to dial which is different from the one specified and displayed to + * the user. This alternative address is referred to as the gateway address. + */ + public static final String GATEWAY_PROVIDER_PACKAGE = + "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE"; + + /** + * An optional {@link android.content.Intent#ACTION_CALL} intent extra corresponding to the + * original address to dial for the call. This is used when an alternative gateway address is + * provided to recall the original address. + * The value is a {@link android.net.Uri}. + * + * (See {@link #GATEWAY_PROVIDER_PACKAGE} for details) + */ + public static final String GATEWAY_ORIGINAL_ADDRESS = + "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS"; + + /** + * The number which the party on the other side of the line will see (and use to return the + * call). + * <p> + * {@link ConnectionService}s which interact with {@link RemoteConnection}s should only populate + * this if the {@link android.telephony.TelephonyManager#getLine1Number()} value, as that is the + * user's expected caller ID. + */ + public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER"; + + /** + * The dual tone multi-frequency signaling character sent to indicate the dialing system should + * pause for a predefined period. + */ + public static final char DTMF_CHARACTER_PAUSE = ','; + + /** + * The dual-tone multi-frequency signaling character sent to indicate the dialing system should + * wait for user confirmation before proceeding. + */ + public static final char DTMF_CHARACTER_WAIT = ';'; + + /** + * TTY (teletypewriter) mode is off. + * + * @hide + */ + public static final int TTY_MODE_OFF = 0; + + /** + * TTY (teletypewriter) mode is on. The speaker is off and the microphone is muted. The user + * will communicate with the remote party by sending and receiving text messages. + * + * @hide + */ + public static final int TTY_MODE_FULL = 1; + + /** + * TTY (teletypewriter) mode is in hearing carryover mode (HCO). The microphone is muted but the + * speaker is on. The user will communicate with the remote party by sending text messages and + * hearing an audible reply. + * + * @hide + */ + public static final int TTY_MODE_HCO = 2; + + /** + * TTY (teletypewriter) mode is in voice carryover mode (VCO). The speaker is off but the + * microphone is still on. User will communicate with the remote party by speaking and receiving + * text message replies. + * + * @hide + */ + public static final int TTY_MODE_VCO = 3; + + /** + * Broadcast intent action indicating that the current TTY mode has changed. An intent extra + * provides this state as an int. + * + * @see #EXTRA_CURRENT_TTY_MODE + * @hide + */ + public static final String ACTION_CURRENT_TTY_MODE_CHANGED = + "android.telecom.action.CURRENT_TTY_MODE_CHANGED"; + + /** + * The lookup key for an int that indicates the current TTY mode. + * Valid modes are: + * - {@link #TTY_MODE_OFF} + * - {@link #TTY_MODE_FULL} + * - {@link #TTY_MODE_HCO} + * - {@link #TTY_MODE_VCO} + * + * @hide + */ + public static final String EXTRA_CURRENT_TTY_MODE = + "android.telecom.intent.extra.CURRENT_TTY_MODE"; + + /** + * Broadcast intent action indicating that the TTY preferred operating mode has changed. An + * intent extra provides the new mode as an int. + * + * @see #EXTRA_TTY_PREFERRED_MODE + * @hide + */ + public static final String ACTION_TTY_PREFERRED_MODE_CHANGED = + "android.telecom.action.TTY_PREFERRED_MODE_CHANGED"; + + /** + * The lookup key for an int that indicates preferred TTY mode. Valid modes are: - + * {@link #TTY_MODE_OFF} - {@link #TTY_MODE_FULL} - {@link #TTY_MODE_HCO} - + * {@link #TTY_MODE_VCO} + * + * @hide + */ + public static final String EXTRA_TTY_PREFERRED_MODE = + "android.telecom.intent.extra.TTY_PREFERRED"; + + /** + * The following 4 constants define how properties such as phone numbers and names are + * displayed to the user. + */ + + /** Property is displayed normally. */ + public static final int PRESENTATION_ALLOWED = 1; + + /** Property was blocked. */ + public static final int PRESENTATION_RESTRICTED = 2; + + /** Presentation was not specified or is unknown. */ + public static final int PRESENTATION_UNKNOWN = 3; + + /** Property should be displayed as a pay phone. */ + public static final int PRESENTATION_PAYPHONE = 4; + + private static final String TAG = "TelecomManager"; + + private final Context mContext; + + /** + * @hide + */ + public static TelecomManager from(Context context) { + return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); + } + + /** + * @hide + */ + public TelecomManager(Context context) { + Context appContext = context.getApplicationContext(); + if (appContext != null) { + mContext = appContext; + } else { + mContext = context; + } + } + + /** + * Return the {@link PhoneAccount} which is the user-chosen default for making outgoing phone + * calls with a specified URI scheme. This {@code PhoneAccount} will always be a member of the + * list which is returned from calling {@link #getCallCapablePhoneAccounts()}. + * <p> + * Apps must be prepared for this method to return {@code null}, indicating that there currently + * exists no user-chosen default {@code PhoneAccount}. In this case, apps wishing to initiate a + * phone call must either create their {@link android.content.Intent#ACTION_CALL} or + * {@link android.content.Intent#ACTION_DIAL} {@code Intent} with no + * {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE}, or present the user with an affordance to + * select one of the elements of {@link #getCallCapablePhoneAccounts()}. + * <p> + * An {@link android.content.Intent#ACTION_CALL} or {@link android.content.Intent#ACTION_DIAL} + * {@code Intent} with no {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} is valid, and + * subsequent steps in the phone call flow are responsible for presenting the user with an + * affordance, if necessary, to choose a {@code PhoneAccount}. + * + * @param uriScheme The URI scheme. + */ + public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) { + try { + if (isServiceConnected()) { + return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e); + } + return null; + } + + /** + * Return the {@link PhoneAccount} which is the user-chosen default for making outgoing phone + * calls. This {@code PhoneAccount} will always be a member of the list which is returned from + * calling {@link #getCallCapablePhoneAccounts()} + * + * Apps must be prepared for this method to return {@code null}, indicating that there currently + * exists no user-chosen default {@code PhoneAccount}. + * + * @return The user outgoing phone account selected by the user. + * @hide + */ + public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() { + try { + if (isServiceConnected()) { + return getTelecomService().getUserSelectedOutgoingPhoneAccount(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getUserSelectedOutgoingPhoneAccount", e); + } + return null; + } + + /** + * Sets the default account for making outgoing phone calls. + * @hide + */ + public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { + try { + if (isServiceConnected()) { + getTelecomService().setUserSelectedOutgoingPhoneAccount(accountHandle); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#setUserSelectedOutgoingPhoneAccount"); + } + } + + /** + * Return a list of {@link PhoneAccountHandle}s which can be used to make and receive phone + * calls. + * + * @see #EXTRA_PHONE_ACCOUNT_HANDLE + * @return A list of {@code PhoneAccountHandle} objects. + */ + public List<PhoneAccountHandle> getCallCapablePhoneAccounts() { + try { + if (isServiceConnected()) { + return getTelecomService().getCallCapablePhoneAccounts(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts", e); + } + return new ArrayList<>(); + } + + /** + * Returns the current SIM call manager. Apps must be prepared for this method to return + * {@code null}, indicating that there currently exists no user-chosen default + * {@code PhoneAccount}. + * @return The phone account handle of the current sim call manager. + * @hide + */ + public PhoneAccountHandle getSimCallManager() { + try { + if (isServiceConnected()) { + return getTelecomService().getSimCallManager(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getSimCallManager"); + } + return null; + } + + /** + * Sets the SIM call manager to the specified phone account. + * @param accountHandle The phone account handle of the account to set as the sim call manager. + * @hide + */ + public void setSimCallManager(PhoneAccountHandle accountHandle) { + try { + if (isServiceConnected()) { + getTelecomService().setSimCallManager(accountHandle); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#setSimCallManager"); + } + } + + /** + * Returns the list of registered SIM call managers. + * @return List of registered SIM call managers. + * @hide + */ + public List<PhoneAccountHandle> getSimCallManagers() { + try { + if (isServiceConnected()) { + return getTelecomService().getSimCallManagers(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getSimCallManagers"); + } + return new ArrayList<>(); + } + + /** + * Returns the current connection manager. Apps must be prepared for this method to return + * {@code null}, indicating that there currently exists no user-chosen default + * {@code PhoneAccount}. + * + * @return The phone account handle of the current connection manager. + */ + public PhoneAccountHandle getConnectionManager() { + return getSimCallManager(); + } + + /** + * Returns a list of the {@link PhoneAccountHandle}s which can be used to make and receive phone + * calls which support the specified URI scheme. + * <P> + * For example, invoking with {@code "tel"} will find all {@link PhoneAccountHandle}s which + * support telephone calls (e.g. URIs such as {@code tel:555-555-1212}). Invoking with + * {@code "sip"} will find all {@link PhoneAccountHandle}s which support SIP calls (e.g. URIs + * such as {@code sip:example@sipexample.com}). + * + * @param uriScheme The URI scheme. + * @return A list of {@code PhoneAccountHandle} objects supporting the URI scheme. + */ + public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) { + try { + if (isServiceConnected()) { + return getTelecomService().getPhoneAccountsSupportingScheme(uriScheme); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getPhoneAccountsSupportingScheme", e); + } + return new ArrayList<>(); + } + + /** + * Determine whether the device has more than one account registered that can make and receive + * phone calls. + * + * @return {@code true} if the device has more than one account registered and {@code false} + * otherwise. + */ + public boolean hasMultipleCallCapableAccounts() { + return getCallCapablePhoneAccounts().size() > 1; + } + + /** + * Return the {@link PhoneAccount} for a specified {@link PhoneAccountHandle}. Object includes + * resources which can be used in a user interface. + * + * @param account The {@link PhoneAccountHandle}. + * @return The {@link PhoneAccount} object. + */ + public PhoneAccount getPhoneAccount(PhoneAccountHandle account) { + try { + if (isServiceConnected()) { + return getTelecomService().getPhoneAccount(account); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getPhoneAccount", e); + } + return null; + } + + /** + * Returns a count of all {@link PhoneAccount}s. + * + * @return The count of {@link PhoneAccount}s. + * @hide + */ + @SystemApi + public int getAllPhoneAccountsCount() { + try { + if (isServiceConnected()) { + return getTelecomService().getAllPhoneAccountsCount(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getAllPhoneAccountsCount", e); + } + return 0; + } + + /** + * Returns a list of all {@link PhoneAccount}s. + * + * @return All {@link PhoneAccount}s. + * @hide + */ + @SystemApi + public List<PhoneAccount> getAllPhoneAccounts() { + try { + if (isServiceConnected()) { + return getTelecomService().getAllPhoneAccounts(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getAllPhoneAccounts", e); + } + return Collections.EMPTY_LIST; + } + + /** + * Returns a list of all {@link PhoneAccountHandle}s. + * + * @return All {@link PhoneAccountHandle}s. + * @hide + */ + @SystemApi + public List<PhoneAccountHandle> getAllPhoneAccountHandles() { + try { + if (isServiceConnected()) { + return getTelecomService().getAllPhoneAccountHandles(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getAllPhoneAccountHandles", e); + } + return Collections.EMPTY_LIST; + } + + /** + * Register a {@link PhoneAccount} for use by the system. + * + * @param account The complete {@link PhoneAccount}. + */ + public void registerPhoneAccount(PhoneAccount account) { + try { + if (isServiceConnected()) { + getTelecomService().registerPhoneAccount(account); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#registerPhoneAccount", e); + } + } + + /** + * Remove a {@link PhoneAccount} registration from the system. + * + * @param accountHandle A {@link PhoneAccountHandle} for the {@link PhoneAccount} to unregister. + */ + public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { + try { + if (isServiceConnected()) { + getTelecomService().unregisterPhoneAccount(accountHandle); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#unregisterPhoneAccount", e); + } + } + + /** + * Remove all Accounts that belong to the calling package from the system. + */ + @SystemApi + public void clearAccounts() { + try { + if (isServiceConnected()) { + getTelecomService().clearAccounts(mContext.getPackageName()); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#clearAccounts", e); + } + } + + /** + * @hide + */ + @SystemApi + public ComponentName getDefaultPhoneApp() { + try { + if (isServiceConnected()) { + return getTelecomService().getDefaultPhoneApp(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException attempting to get the default phone app.", e); + } + return null; + } + + /** + * Returns whether there is an ongoing phone call (can be in dialing, ringing, active or holding + * states). + * <p> + * Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE} + * </p> + */ + @SystemApi + public boolean isInCall() { + try { + if (isServiceConnected()) { + return getTelecomService().isInCall(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling isInCall().", e); + } + return false; + } + + /** + * Returns one of the following constants that represents the current state of Telecom: + * + * {@link TelephonyManager#CALL_STATE_RINGING} + * {@link TelephonyManager#CALL_STATE_OFFHOOK} + * {@link TelephonyManager#CALL_STATE_IDLE} + * @hide + */ + @SystemApi + public int getCallState() { + try { + if (isServiceConnected()) { + return getTelecomService().getCallState(); + } + } catch (RemoteException e) { + Log.d(TAG, "RemoteException calling getCallState().", e); + } + return TelephonyManager.CALL_STATE_IDLE; + } + + /** + * Returns whether there currently exists is a ringing incoming-call. + * + * @hide + */ + @SystemApi + public boolean isRinging() { + try { + if (isServiceConnected()) { + return getTelecomService().isRinging(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException attempting to get ringing state of phone app.", e); + } + return false; + } + + /** + * Ends an ongoing call. + * TODO: L-release - need to convert all invocations of ITelecomService#endCall to use this + * method (clockwork & gearhead). + * @hide + */ + @SystemApi + public boolean endCall() { + try { + if (isServiceConnected()) { + return getTelecomService().endCall(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#endCall", e); + } + return false; + } + + /** + * If there is a ringing incoming call, this method accepts the call on behalf of the user. + * TODO: L-release - need to convert all invocation of ITelecmmService#answerRingingCall to use + * this method (clockwork & gearhead). + * + * @hide + */ + @SystemApi + public void acceptRingingCall() { + try { + if (isServiceConnected()) { + getTelecomService().acceptRingingCall(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#acceptRingingCall", e); + } + } + + /** + * Silences the ringer if a ringing call exists. + * + * @hide + */ + @SystemApi + public void silenceRinger() { + try { + if (isServiceConnected()) { + getTelecomService().silenceRinger(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#silenceRinger", e); + } + } + + /** + * Returns whether TTY is supported on this device. + * + * @hide + */ + @SystemApi + public boolean isTtySupported() { + try { + if (isServiceConnected()) { + return getTelecomService().isTtySupported(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException attempting to get TTY supported state.", e); + } + return false; + } + + /** + * Returns the current TTY mode of the device. For TTY to be on the user must enable it in + * settings and have a wired headset plugged in. + * Valid modes are: + * - {@link TelecomManager#TTY_MODE_OFF} + * - {@link TelecomManager#TTY_MODE_FULL} + * - {@link TelecomManager#TTY_MODE_HCO} + * - {@link TelecomManager#TTY_MODE_VCO} + * @hide + */ + public int getCurrentTtyMode() { + try { + if (isServiceConnected()) { + return getTelecomService().getCurrentTtyMode(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e); + } + return TTY_MODE_OFF; + } + + /** + * Registers a new incoming call. A {@link ConnectionService} should invoke this method when it + * has an incoming call. The specified {@link PhoneAccountHandle} must have been registered + * with {@link #registerPhoneAccount}. Once invoked, this method will cause the system to bind + * to the {@link ConnectionService} associated with the {@link PhoneAccountHandle} and request + * additional information about the call (See + * {@link ConnectionService#onCreateIncomingConnection}) before starting the incoming call UI. + * + * @param phoneAccount A {@link PhoneAccountHandle} registered with + * {@link #registerPhoneAccount}. + * @param extras A bundle that will be passed through to + * {@link ConnectionService#onCreateIncomingConnection}. + */ + public void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) { + try { + if (isServiceConnected()) { + getTelecomService().addNewIncomingCall( + phoneAccount, extras == null ? new Bundle() : extras); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException adding a new incoming call: " + phoneAccount, e); + } + } + + /** + * Processes the specified dial string as an MMI code. + * MMI codes are any sequence of characters entered into the dialpad that contain a "*" or "#". + * Some of these sequences launch special behavior through handled by Telephony. + * <p> + * Requires that the method-caller be set as the system dialer app. + * </p> + * + * @param dialString The digits to dial. + * @return True if the digits were processed as an MMI code, false otherwise. + */ + public boolean handleMmi(String dialString) { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + return service.handlePinMmi(dialString); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#handlePinMmi", e); + } + } + return false; + } + + /** + * Removes the missed-call notification if one is present. + * <p> + * Requires that the method-caller be set as the system dialer app. + * </p> + */ + public void cancelMissedCallsNotification() { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + service.cancelMissedCallsNotification(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#cancelMissedCallsNotification", e); + } + } + } + + /** + * Brings the in-call screen to the foreground if there is an ongoing call. If there is + * currently no ongoing call, then this method does nothing. + * <p> + * Requires that the method-caller be set as the system dialer app or have the + * {@link android.Manifest.permission#READ_PHONE_STATE} permission. + * </p> + * + * @param showDialpad Brings up the in-call dialpad as part of showing the in-call screen. + */ + public void showInCallScreen(boolean showDialpad) { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + service.showInCallScreen(showDialpad); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#showCallScreen", e); + } + } + } + + private ITelecomService getTelecomService() { + return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE)); + } + + private boolean isServiceConnected() { + boolean isConnected = getTelecomService() != null; + if (!isConnected) { + Log.w(TAG, "Telecom Service not found."); + } + return isConnected; + } +} diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java new file mode 100644 index 0000000..925058e --- /dev/null +++ b/telecomm/java/android/telecom/VideoCallImpl.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.telecom.InCallService.VideoCall; +import android.view.Surface; + +import com.android.internal.os.SomeArgs; +import com.android.internal.telecom.IVideoCallback; +import com.android.internal.telecom.IVideoProvider; + +/** + * Implementation of a Video Call, which allows InCallUi to communicate commands to the underlying + * {@link Connection.VideoProvider}, and direct callbacks from the + * {@link Connection.VideoProvider} to the appropriate {@link VideoCall.Listener}. + * + * {@hide} + */ +public class VideoCallImpl extends VideoCall { + private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 1; + private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 2; + private static final int MSG_HANDLE_CALL_SESSION_EVENT = 3; + private static final int MSG_CHANGE_PEER_DIMENSIONS = 4; + private static final int MSG_CHANGE_CALL_DATA_USAGE = 5; + private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 6; + + private final IVideoProvider mVideoProvider; + private final VideoCallListenerBinder mBinder; + private VideoCall.Listener mVideoCallListener; + + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + mVideoProvider.asBinder().unlinkToDeath(this, 0); + } + }; + + /** + * IVideoCallback stub implementation. + */ + private final class VideoCallListenerBinder extends IVideoCallback.Stub { + @Override + public void receiveSessionModifyRequest(VideoProfile videoProfile) { + mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_REQUEST, + videoProfile).sendToTarget(); + } + + @Override + public void receiveSessionModifyResponse(int status, VideoProfile requestProfile, + VideoProfile responseProfile) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = status; + args.arg2 = requestProfile; + args.arg3 = responseProfile; + mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args).sendToTarget(); + } + + @Override + public void handleCallSessionEvent(int event) { + mHandler.obtainMessage(MSG_HANDLE_CALL_SESSION_EVENT, event).sendToTarget(); + } + + @Override + public void changePeerDimensions(int width, int height) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = width; + args.arg2 = height; + mHandler.obtainMessage(MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget(); + } + + @Override + public void changeCallDataUsage(int dataUsage) { + mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, dataUsage).sendToTarget(); + } + + @Override + public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) { + mHandler.obtainMessage(MSG_CHANGE_CAMERA_CAPABILITIES, + cameraCapabilities).sendToTarget(); + } + } + + /** Default handler used to consolidate binder method calls onto a single thread. */ + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (mVideoCallListener == null) { + return; + } + + SomeArgs args; + switch (msg.what) { + case MSG_RECEIVE_SESSION_MODIFY_REQUEST: + mVideoCallListener.onSessionModifyRequestReceived((VideoProfile) msg.obj); + break; + case MSG_RECEIVE_SESSION_MODIFY_RESPONSE: + args = (SomeArgs) msg.obj; + try { + int status = (int) args.arg1; + VideoProfile requestProfile = (VideoProfile) args.arg2; + VideoProfile responseProfile = (VideoProfile) args.arg3; + + mVideoCallListener.onSessionModifyResponseReceived( + status, requestProfile, responseProfile); + } finally { + args.recycle(); + } + break; + case MSG_HANDLE_CALL_SESSION_EVENT: + mVideoCallListener.onCallSessionEvent((int) msg.obj); + break; + case MSG_CHANGE_PEER_DIMENSIONS: + args = (SomeArgs) msg.obj; + try { + int width = (int) args.arg1; + int height = (int) args.arg2; + mVideoCallListener.onPeerDimensionsChanged(width, height); + } finally { + args.recycle(); + } + break; + case MSG_CHANGE_CALL_DATA_USAGE: + mVideoCallListener.onCallDataUsageChanged(msg.arg1); + break; + case MSG_CHANGE_CAMERA_CAPABILITIES: + mVideoCallListener.onCameraCapabilitiesChanged( + (CameraCapabilities) msg.obj); + break; + default: + break; + } + } + }; + + /** {@hide} */ + VideoCallImpl(IVideoProvider videoProvider) throws RemoteException { + mVideoProvider = videoProvider; + mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0); + + mBinder = new VideoCallListenerBinder(); + mVideoProvider.setVideoCallback(mBinder); + } + + /** {@inheritDoc} */ + public void setVideoCallListener(VideoCall.Listener videoCallListener) { + mVideoCallListener = videoCallListener; + } + + /** {@inheritDoc} */ + public void setCamera(String cameraId) { + try { + mVideoProvider.setCamera(cameraId); + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void setPreviewSurface(Surface surface) { + try { + mVideoProvider.setPreviewSurface(surface); + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void setDisplaySurface(Surface surface) { + try { + mVideoProvider.setDisplaySurface(surface); + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void setDeviceOrientation(int rotation) { + try { + mVideoProvider.setDeviceOrientation(rotation); + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void setZoom(float value) { + try { + mVideoProvider.setZoom(value); + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void sendSessionModifyRequest(VideoProfile requestProfile) { + try { + mVideoProvider.sendSessionModifyRequest(requestProfile); + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void sendSessionModifyResponse(VideoProfile responseProfile) { + try { + mVideoProvider.sendSessionModifyResponse(responseProfile); + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void requestCameraCapabilities() { + try { + mVideoProvider.requestCameraCapabilities(); + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void requestCallDataUsage() { + try { + mVideoProvider.requestCallDataUsage(); + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void setPauseImage(String uri) { + try { + mVideoProvider.setPauseImage(uri); + } catch (RemoteException e) { + } + } +}
\ No newline at end of file diff --git a/telecomm/java/android/telecom/VideoCallbackServant.java b/telecomm/java/android/telecom/VideoCallbackServant.java new file mode 100644 index 0000000..d0e3f22 --- /dev/null +++ b/telecomm/java/android/telecom/VideoCallbackServant.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2014 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 + R* limitations under the License. + */ + +package android.telecom; + +import com.android.internal.os.SomeArgs; +import com.android.internal.telecom.IVideoCallback; + +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; + +/** + * A component that provides an RPC servant implementation of {@link IVideoCallback}, + * posting incoming messages on the main thread on a client-supplied delegate object. + * + * TODO: Generate this and similar classes using a compiler starting from AIDL interfaces. + * + * @hide + */ +final class VideoCallbackServant { + private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 0; + private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 1; + private static final int MSG_HANDLE_CALL_SESSION_EVENT = 2; + private static final int MSG_CHANGE_PEER_DIMENSIONS = 3; + private static final int MSG_CHANGE_CALL_DATA_USAGE = 4; + private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 5; + + private final IVideoCallback mDelegate; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + try { + internalHandleMessage(msg); + } catch (RemoteException e) { + } + } + + // Internal method defined to centralize handling of RemoteException + private void internalHandleMessage(Message msg) throws RemoteException { + switch (msg.what) { + case MSG_RECEIVE_SESSION_MODIFY_REQUEST: { + mDelegate.receiveSessionModifyRequest((VideoProfile) msg.obj); + break; + } + case MSG_RECEIVE_SESSION_MODIFY_RESPONSE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.receiveSessionModifyResponse( + args.argi1, + (VideoProfile) args.arg1, + (VideoProfile) args.arg2); + } finally { + args.recycle(); + } + break; + } + case MSG_HANDLE_CALL_SESSION_EVENT: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.handleCallSessionEvent(args.argi1); + } finally { + args.recycle(); + } + break; + } + case MSG_CHANGE_PEER_DIMENSIONS: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.changePeerDimensions(args.argi1, args.argi2); + } finally { + args.recycle(); + } + break; + } + case MSG_CHANGE_CALL_DATA_USAGE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.changeCallDataUsage(args.argi1); + } finally { + args.recycle(); + } + break; + } + case MSG_CHANGE_CAMERA_CAPABILITIES: { + mDelegate.changeCameraCapabilities((CameraCapabilities) msg.obj); + break; + } + } + } + }; + + private final IVideoCallback mStub = new IVideoCallback.Stub() { + @Override + public void receiveSessionModifyRequest(VideoProfile videoProfile) throws RemoteException { + mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_REQUEST, videoProfile).sendToTarget(); + } + + @Override + public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile, + VideoProfile responseProfile) throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = status; + args.arg1 = requestedProfile; + args.arg2 = responseProfile; + mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args).sendToTarget(); + } + + @Override + public void handleCallSessionEvent(int event) throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = event; + mHandler.obtainMessage(MSG_HANDLE_CALL_SESSION_EVENT, args).sendToTarget(); + } + + @Override + public void changePeerDimensions(int width, int height) throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = width; + args.argi2 = height; + mHandler.obtainMessage(MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget(); + } + + @Override + public void changeCallDataUsage(int dataUsage) throws RemoteException { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = dataUsage; + mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, args).sendToTarget(); + } + + @Override + public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) + throws RemoteException { + mHandler.obtainMessage(MSG_CHANGE_CAMERA_CAPABILITIES, cameraCapabilities) + .sendToTarget(); + } + }; + + public VideoCallbackServant(IVideoCallback delegate) { + mDelegate = delegate; + } + + public IVideoCallback getStub() { + return mStub; + } +} diff --git a/telecomm/java/android/telecom/VideoProfile.aidl b/telecomm/java/android/telecom/VideoProfile.aidl new file mode 100644 index 0000000..091b569 --- /dev/null +++ b/telecomm/java/android/telecom/VideoProfile.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +/** + * {@hide} + */ +parcelable VideoProfile; diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java new file mode 100644 index 0000000..f5cb054 --- /dev/null +++ b/telecomm/java/android/telecom/VideoProfile.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2014 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 android.telecom; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents attributes of video calls. + * + * {@hide} + */ +public class VideoProfile implements Parcelable { + /** + * "High" video quality. + */ + public static final int QUALITY_HIGH = 1; + + /** + * "Medium" video quality. + */ + public static final int QUALITY_MEDIUM = 2; + + /** + * "Low" video quality. + */ + public static final int QUALITY_LOW = 3; + + /** + * Use default video quality. + */ + public static final int QUALITY_DEFAULT = 4; + + private final int mVideoState; + + private final int mQuality; + + /** + * Creates an instance of the VideoProfile + * + * @param videoState The video state. + */ + public VideoProfile(int videoState) { + this(videoState, QUALITY_DEFAULT); + } + + /** + * Creates an instance of the VideoProfile + * + * @param videoState The video state. + * @param quality The video quality. + */ + public VideoProfile(int videoState, int quality) { + mVideoState = videoState; + mQuality = quality; + } + + /** + * The video state of the call. + * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY}, + * {@link VideoProfile.VideoState#BIDIRECTIONAL}, + * {@link VideoProfile.VideoState#TX_ENABLED}, + * {@link VideoProfile.VideoState#RX_ENABLED}, + * {@link VideoProfile.VideoState#PAUSED}. + */ + public int getVideoState() { + return mVideoState; + } + + /** + * The desired video quality for the call. + * Valid values: {@link VideoProfile#QUALITY_HIGH}, {@link VideoProfile#QUALITY_MEDIUM}, + * {@link VideoProfile#QUALITY_LOW}, {@link VideoProfile#QUALITY_DEFAULT}. + */ + public int getQuality() { + return mQuality; + } + + /** + * Responsible for creating VideoProfile objects from deserialized Parcels. + **/ + public static final Parcelable.Creator<VideoProfile> CREATOR = + new Parcelable.Creator<VideoProfile> () { + /** + * Creates a MediaProfile instances from a parcel. + * + * @param source The parcel. + * @return The MediaProfile. + */ + @Override + public VideoProfile createFromParcel(Parcel source) { + int state = source.readInt(); + int quality = source.readInt(); + + ClassLoader classLoader = VideoProfile.class.getClassLoader(); + return new VideoProfile(state, quality); + } + + @Override + public VideoProfile[] newArray(int size) { + return new VideoProfile[size]; + } + }; + + /** + * Describe the kinds of special objects contained in this Parcelable's + * marshalled representation. + * + * @return a bitmask indicating the set of special object types marshalled + * by the Parcelable. + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mVideoState); + dest.writeInt(mQuality); + } + + /** + * The video state of the call, stored as a bit-field describing whether video transmission and + * receipt it enabled, as well as whether the video is currently muted. + */ + public static class VideoState { + /** + * Call is currently in an audio-only mode with no video transmission or receipt. + */ + public static final int AUDIO_ONLY = 0x0; + + /** + * Video transmission is enabled. + */ + public static final int TX_ENABLED = 0x1; + + /** + * Video reception is enabled. + */ + public static final int RX_ENABLED = 0x2; + + /** + * Video signal is bi-directional. + */ + public static final int BIDIRECTIONAL = TX_ENABLED | RX_ENABLED; + + /** + * Video is paused. + */ + public static final int PAUSED = 0x4; + + /** + * Whether the video state is audio only. + * @param videoState The video state. + * @return Returns true if the video state is audio only. + */ + public static boolean isAudioOnly(int videoState) { + return !hasState(videoState, TX_ENABLED) && !hasState(videoState, RX_ENABLED); + } + + /** + * Whether the video transmission is enabled. + * @param videoState The video state. + * @return Returns true if the video transmission is enabled. + */ + public static boolean isTransmissionEnabled(int videoState) { + return hasState(videoState, TX_ENABLED); + } + + /** + * Whether the video reception is enabled. + * @param videoState The video state. + * @return Returns true if the video transmission is enabled. + */ + public static boolean isReceptionEnabled(int videoState) { + return hasState(videoState, RX_ENABLED); + } + + /** + * Whether the video signal is bi-directional. + * @param videoState + * @return Returns true if the video signal is bi-directional. + */ + public static boolean isBidirectional(int videoState) { + return hasState(videoState, BIDIRECTIONAL); + } + + /** + * Whether the video is paused. + * @param videoState The video state. + * @return Returns true if the video is paused. + */ + public static boolean isPaused(int videoState) { + return hasState(videoState, PAUSED); + } + + /** + * Determines if a specified state is set in a videoState bit-mask. + * + * @param videoState The video state bit-mask. + * @param state The state to check. + * @return {@code True} if the state is set. + * {@hide} + */ + private static boolean hasState(int videoState, int state) { + return (videoState & state) == state; + } + } +} diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl new file mode 100644 index 0000000..1059da3 --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 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.telecom; + +import android.os.Bundle; +import android.telecom.AudioState; +import android.telecom.ConnectionRequest; +import android.telecom.PhoneAccountHandle; + +import com.android.internal.telecom.IConnectionServiceAdapter; + +/** + * Internal remote interface for connection services. + * + * @see android.telecom.ConnectionService + * + * @hide + */ +oneway interface IConnectionService { + void addConnectionServiceAdapter(in IConnectionServiceAdapter adapter); + + void removeConnectionServiceAdapter(in IConnectionServiceAdapter adapter); + + void createConnection( + in PhoneAccountHandle connectionManagerPhoneAccount, + String callId, + in ConnectionRequest request, + boolean isIncoming); + + void abort(String callId); + + void answerVideo(String callId, int videoState); + + void answer(String callId); + + void reject(String callId); + + void disconnect(String callId); + + void hold(String callId); + + void unhold(String callId); + + void onAudioStateChanged(String activeCallId, in AudioState audioState); + + void playDtmfTone(String callId, char digit); + + void stopDtmfTone(String callId); + + void conference(String conferenceCallId, String callId); + + void splitFromConference(String callId); + + void mergeConference(String conferenceCallId); + + void swapConference(String conferenceCallId); + + void onPostDialContinue(String callId, boolean proceed); +} diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl new file mode 100644 index 0000000..5daa568 --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 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.telecom; + +import android.app.PendingIntent; +import android.net.Uri; +import android.telecom.ConnectionRequest; +import android.telecom.DisconnectCause; +import android.telecom.ParcelableConnection; +import android.telecom.ParcelableConference; +import android.telecom.StatusHints; + +import com.android.internal.telecom.IVideoProvider; +import com.android.internal.telecom.RemoteServiceCallback; + +/** + * Internal remote callback interface for connection services. + * + * @see android.telecom.ConnectionServiceAdapter + * + * {@hide} + */ +oneway interface IConnectionServiceAdapter { + void handleCreateConnectionComplete( + String callId, + in ConnectionRequest request, + in ParcelableConnection connection); + + void setActive(String callId); + + void setRinging(String callId); + + void setDialing(String callId); + + void setDisconnected(String callId, in DisconnectCause disconnectCause); + + void setOnHold(String callId); + + void setRingbackRequested(String callId, boolean ringing); + + void setCallCapabilities(String callId, int callCapabilities); + + void setIsConferenced(String callId, String conferenceCallId); + + void addConferenceCall(String callId, in ParcelableConference conference); + + void removeCall(String callId); + + void onPostDialWait(String callId, String remaining); + + void queryRemoteConnectionServices(RemoteServiceCallback callback); + + void setVideoProvider(String callId, IVideoProvider videoProvider); + + void setVideoState(String callId, int videoState); + + void setIsVoipAudioMode(String callId, boolean isVoip); + + void setStatusHints(String callId, in StatusHints statusHints); + + void setAddress(String callId, in Uri address, int presentation); + + void setCallerDisplayName(String callId, String callerDisplayName, int presentation); + + void setConferenceableConnections(String callId, in List<String> conferenceableCallIds); +} diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl new file mode 100644 index 0000000..138a877 --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 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.telecom; + +import android.telecom.PhoneAccountHandle; + +/** + * Internal remote callback interface for in-call services. + * + * @see android.telecom.InCallAdapter + * + * {@hide} + */ +oneway interface IInCallAdapter { + void answerCall(String callId, int videoState); + + void rejectCall(String callId, boolean rejectWithMessage, String textMessage); + + void disconnectCall(String callId); + + void holdCall(String callId); + + void unholdCall(String callId); + + void mute(boolean shouldMute); + + void setAudioRoute(int route); + + void playDtmfTone(String callId, char digit); + + void stopDtmfTone(String callId); + + void postDialContinue(String callId, boolean proceed); + + void phoneAccountSelected(String callId, in PhoneAccountHandle accountHandle); + + void conference(String callId, String otherCallId); + + void splitFromConference(String callId); + + void mergeConference(String callId); + + void swapConference(String callId); + + void turnOnProximitySensor(); + + void turnOffProximitySensor(boolean screenOnImmediately); +} diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl new file mode 100644 index 0000000..35f6f65 --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 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.telecom; + +import android.app.PendingIntent; +import android.telecom.AudioState; +import android.telecom.ParcelableCall; + +import com.android.internal.telecom.IInCallAdapter; + +/** + * Internal remote interface for in-call services. + * + * @see android.telecom.InCallService + * + * {@hide} + */ +oneway interface IInCallService { + void setInCallAdapter(in IInCallAdapter inCallAdapter); + + void addCall(in ParcelableCall call); + + void updateCall(in ParcelableCall call); + + void setPostDial(String callId, String remaining); + + void setPostDialWait(String callId, String remaining); + + void onAudioStateChanged(in AudioState audioState); + + void bringToForeground(boolean showDialpad); +} diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl new file mode 100644 index 0000000..77a80fe --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2014 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.telecom; + +import android.content.ComponentName; +import android.telecom.PhoneAccountHandle; +import android.os.Bundle; +import android.telecom.PhoneAccount; + +/** + * Interface used to interact with Telecom. Mostly this is used by TelephonyManager for passing + * commands that were previously handled by ITelephony. + * {@hide} + */ +interface ITelecomService { + /** + * Brings the in-call screen to the foreground if there is an active call. + * + * @param showDialpad if true, make the dialpad visible initially. + */ + void showInCallScreen(boolean showDialpad); + + /** + * @see TelecomServiceImpl#getDefaultOutgoingPhoneAccount + */ + PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme); + + /** + * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount + */ + PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(); + + /** + * @see TelecomServiceImpl#setUserSelectedOutgoingPhoneAccount + */ + void setUserSelectedOutgoingPhoneAccount(in PhoneAccountHandle account); + + /** + * @see TelecomServiceImpl#getCallCapablePhoneAccounts + */ + List<PhoneAccountHandle> getCallCapablePhoneAccounts(); + + /** + * @see TelecomManager#getPhoneAccountsSupportingScheme + */ + List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(in String uriScheme); + + /** + * @see TelecomManager#getPhoneAccount + */ + PhoneAccount getPhoneAccount(in PhoneAccountHandle account); + + /** + * @see TelecomManager#getAllPhoneAccountsCount + */ + int getAllPhoneAccountsCount(); + + /** + * @see TelecomManager#getAllPhoneAccounts + */ + List<PhoneAccount> getAllPhoneAccounts(); + + /** + * @see TelecomManager#getAllPhoneAccountHandles + */ + List<PhoneAccountHandle> getAllPhoneAccountHandles(); + + /** + * @see TelecomServiceImpl#getSimCallManager + */ + PhoneAccountHandle getSimCallManager(); + + /** + * @see TelecomServiceImpl#setSimCallManager + */ + void setSimCallManager(in PhoneAccountHandle account); + + /** + * @see TelecomServiceImpl#getSimCallManagers + */ + List<PhoneAccountHandle> getSimCallManagers(); + + /** + * @see TelecomServiceImpl#registerPhoneAccount + */ + void registerPhoneAccount(in PhoneAccount metadata); + + /** + * @see TelecomServiceImpl#unregisterPhoneAccount + */ + void unregisterPhoneAccount(in PhoneAccountHandle account); + + /** + * @see TelecomServiceImpl#clearAccounts + */ + void clearAccounts(String packageName); + + /** + * @see TelecomServiceImpl#getDefaultPhoneApp + */ + ComponentName getDefaultPhoneApp(); + + // + // Internal system apis relating to call management. + // + + /** + * @see TelecomServiceImpl#silenceRinger + */ + void silenceRinger(); + + /** + * @see TelecomServiceImpl#isInCall + */ + boolean isInCall(); + + /** + * @see TelecomServiceImpl#isRinging + */ + boolean isRinging(); + + /** + * @see TelecomServiceImpl#getCallState + */ + int getCallState(); + + /** + * @see TelecomServiceImpl#endCall + */ + boolean endCall(); + + /** + * @see TelecomServiceImpl#acceptRingingCall + */ + void acceptRingingCall(); + + /** + * @see TelecomServiceImpl#cancelMissedCallsNotification + */ + void cancelMissedCallsNotification(); + + /** + * @see TelecomServiceImpl#handleMmi + */ + boolean handlePinMmi(String dialString); + + /** + * @see TelecomServiceImpl#isTtySupported + */ + boolean isTtySupported(); + + /** + * @see TelecomServiceImpl#getCurrentTtyMode + */ + int getCurrentTtyMode(); + + /** + * @see TelecomServiceImpl#addNewIncomingCall + */ + void addNewIncomingCall(in PhoneAccountHandle phoneAccount, in Bundle extras); +} diff --git a/telecomm/java/com/android/internal/telecom/IVideoCallback.aidl b/telecomm/java/com/android/internal/telecom/IVideoCallback.aidl new file mode 100644 index 0000000..f758b60 --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/IVideoCallback.aidl @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2014 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.telecom; + +import android.telecom.CameraCapabilities; +import android.telecom.VideoProfile; + + /** + * Internal definition of a callback interface, used for an InCallUi to respond to video + * telephony changes. + * + * @see android.telecom.InCallService.VideoCall.Listener + * + * {@hide} + */ +oneway interface IVideoCallback { + void receiveSessionModifyRequest(in VideoProfile videoProfile); + + void receiveSessionModifyResponse( + int status, + in VideoProfile requestedProfile, + in VideoProfile responseProfile); + + void handleCallSessionEvent(int event); + + void changePeerDimensions(int width, int height); + + void changeCallDataUsage(int dataUsage); + + void changeCameraCapabilities(in CameraCapabilities cameraCapabilities); +} diff --git a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl new file mode 100644 index 0000000..e96d9d3 --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 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.telecom; + +import android.view.Surface; +import android.telecom.VideoProfile; + +/** + * Internal remote interface for a video call provider. + * @see android.telecom.VideoProvider + * @hide + */ +oneway interface IVideoProvider { + void setVideoCallback(IBinder videoCallbackBinder); + + void setCamera(String cameraId); + + void setPreviewSurface(in Surface surface); + + void setDisplaySurface(in Surface surface); + + void setDeviceOrientation(int rotation); + + void setZoom(float value); + + void sendSessionModifyRequest(in VideoProfile reqProfile); + + void sendSessionModifyResponse(in VideoProfile responseProfile); + + void requestCameraCapabilities(); + + void requestCallDataUsage(); + + void setPauseImage(String uri); +} diff --git a/telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl b/telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl new file mode 100644 index 0000000..441704d --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 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.telecom; + +import android.content.ComponentName; + +/** + * Simple response callback object. + * + * {@hide} + */ +oneway interface RemoteServiceCallback { + void onError(); + void onResult(in List<ComponentName> components, in List<IBinder> callServices); +} |