Introduce new In-Call Service interface

Change-Id: I2dd8494f6e397c49180b19d1347c62edcae9b4e7
(cherry picked from commit e225fecca486858e8195eaf09d172a70fe7d632b)
This commit is contained in:
Ihab Awad
2014-07-09 21:52:04 -07:00
parent 44583d351e
commit e63fadb109
8 changed files with 1147 additions and 151 deletions

View File

@@ -27850,6 +27850,62 @@ package android.system {
package android.telecomm {
public final class Call {
method public void addListener(android.telecomm.Call.Listener);
method public void answer();
method public void conference();
method public void disconnect();
method public android.telecomm.RemoteCallVideoProvider getCallVideoProvider();
method public java.util.List<java.lang.String> getCannedTextResponses();
method public java.util.List<android.telecomm.Call> getChildren();
method public android.telecomm.Call.Details getDetails();
method public android.telecomm.Call getParent();
method public java.lang.String getRemainingPostDialSequence();
method public int getState();
method public void hold();
method public void phoneAccountClicked();
method public void playDtmfTone(char);
method public void postDialContinue(boolean);
method public void reject(boolean, java.lang.String);
method public void removeListener(android.telecomm.Call.Listener);
method public void splitFromConference();
method public void stopDtmfTone();
method public void swapWithBackgroundCall();
method public void unhold();
field public static final int STATE_ACTIVE = 4; // 0x4
field public static final int STATE_DIALING = 1; // 0x1
field public static final int STATE_DISCONNECTED = 7; // 0x7
field public static final int STATE_HOLDING = 3; // 0x3
field public static final int STATE_NEW = 0; // 0x0
field public static final int STATE_RINGING = 2; // 0x2
}
public static class Call.Details {
method public android.telecomm.PhoneAccount getAccount();
method public java.lang.String getCallerDisplayName();
method public int getCallerDisplayNamePresentation();
method public int getCapabilities();
method public long getConnectTimeMillis();
method public int getDisconnectCauseCode();
method public java.lang.String getDisconnectCauseMsg();
method public android.telecomm.GatewayInfo getGatewayInfo();
method public android.net.Uri getHandle();
method public int getHandlePresentation();
}
public static abstract class Call.Listener {
ctor public Call.Listener();
method public void onCallDestroyed(android.telecomm.Call);
method public void onCallVideoProviderChanged(android.telecomm.Call, android.telecomm.RemoteCallVideoProvider);
method public void onCannedTextResponsesLoaded(android.telecomm.Call, java.util.List<java.lang.String>);
method public void onChildrenChanged(android.telecomm.Call, java.util.List<android.telecomm.Call>);
method public void onDetailsChanged(android.telecomm.Call, android.telecomm.Call.Details);
method public void onParentChanged(android.telecomm.Call, android.telecomm.Call);
method public void onPostDial(android.telecomm.Call, java.lang.String);
method public void onPostDialWait(android.telecomm.Call, java.lang.String);
method public void onStateChanged(android.telecomm.Call, int);
}
public final class CallAudioState implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
@@ -27936,8 +27992,6 @@ package android.telecomm {
enum_constant public static final android.telecomm.CallState DISCONNECTED;
enum_constant public static final android.telecomm.CallState NEW;
enum_constant public static final android.telecomm.CallState ON_HOLD;
enum_constant public static final android.telecomm.CallState POST_DIAL;
enum_constant public static final android.telecomm.CallState POST_DIAL_WAIT;
enum_constant public static final android.telecomm.CallState RINGING;
}
@@ -28112,17 +28166,29 @@ package android.telecomm {
field public static final android.os.Parcelable.Creator CREATOR;
}
public abstract class InCallService extends android.app.Service {
public abstract class InCallService {
ctor protected InCallService();
method protected abstract void addCall(android.telecomm.InCallCall);
method protected abstract void bringToForeground(boolean);
method protected final android.telecomm.InCallAdapter getAdapter();
method protected void onAdapterAttached(android.telecomm.InCallAdapter);
method protected abstract void onAudioStateChanged(android.telecomm.CallAudioState);
method public final android.os.IBinder onBind(android.content.Intent);
method protected abstract void setPostDial(java.lang.String, java.lang.String);
method protected abstract void setPostDialWait(java.lang.String, java.lang.String);
method protected abstract void updateCall(android.telecomm.InCallCall);
method public final android.os.IBinder getBinder();
method public android.telecomm.Phone getPhone();
method public void onPhoneCreated(android.telecomm.Phone);
method public void onPhoneDestroyed(android.telecomm.Phone);
}
public final class Phone {
method public final void addListener(android.telecomm.Phone.Listener);
method public final android.telecomm.CallAudioState getAudioState();
method public final java.util.List<android.telecomm.Call> getCalls();
method public final void removeListener(android.telecomm.Phone.Listener);
method public final void setAudioRoute(int);
method public final void setMuted(boolean);
}
public static abstract class Phone.Listener {
ctor public Phone.Listener();
method public void onAudioStateChanged(android.telecomm.Phone, android.telecomm.CallAudioState);
method public void onBringToForeground(android.telecomm.Phone, boolean);
method public void onCallAdded(android.telecomm.Phone, android.telecomm.Call);
method public void onCallRemoved(android.telecomm.Phone, android.telecomm.Call);
}
public final class PhoneAccount implements android.os.Parcelable {
@@ -28151,18 +28217,17 @@ package android.telecomm {
method public void updatePeerDimensions(int, int) throws android.os.RemoteException;
}
public class RemoteCallVideoProvider implements android.os.IBinder.DeathRecipient {
method public void binderDied();
method public void requestCallDataUsage() throws android.os.RemoteException;
method public void requestCameraCapabilities() throws android.os.RemoteException;
method public void sendSessionModifyRequest(android.telecomm.VideoCallProfile) throws android.os.RemoteException;
method public void sendSessionModifyResponse(android.telecomm.VideoCallProfile) throws android.os.RemoteException;
method public void setCallVideoClient(android.telecomm.CallVideoClient) throws android.os.RemoteException;
public class RemoteCallVideoProvider {
method public void requestCallDataUsage();
method public void requestCameraCapabilities();
method public void sendSessionModifyRequest(android.telecomm.VideoCallProfile);
method public void sendSessionModifyResponse(android.telecomm.VideoCallProfile);
method public void setCallVideoClient(android.telecomm.CallVideoClient);
method public void setCamera(java.lang.String) throws android.os.RemoteException;
method public void setDeviceOrientation(int) throws android.os.RemoteException;
method public void setDisplaySurface(android.view.Surface) throws android.os.RemoteException;
method public void setPauseImage(java.lang.String) throws android.os.RemoteException;
method public void setPreviewSurface(android.view.Surface) throws android.os.RemoteException;
method public void setDeviceOrientation(int);
method public void setDisplaySurface(android.view.Surface);
method public void setPauseImage(java.lang.String);
method public void setPreviewSurface(android.view.Surface);
method public void setZoom(float) throws android.os.RemoteException;
}

View File

@@ -0,0 +1,710 @@
/*
* 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.telecomm;
import android.net.Uri;
import android.os.RemoteException;
import android.telephony.DisconnectCause;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Represents an ongoing phone call that the in-call app should present to the user.
*/
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;
public static class Details {
private final Uri mHandle;
private final int mHandlePresentation;
private final String mCallerDisplayName;
private final int mCallerDisplayNamePresentation;
private final PhoneAccount mAccount;
private final int mCapabilities;
private final int mDisconnectCauseCode;
private final String mDisconnectCauseMsg;
private final long mConnectTimeMillis;
private final GatewayInfo mGatewayInfo;
/**
* @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 android.telecomm.CallPropertyPresentation} 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 android.telecomm.CallPropertyPresentation} for valid values.
*/
public int getCallerDisplayNamePresentation() {
return mCallerDisplayNamePresentation;
}
/**
* @return The {@code PhoneAccount} whereby the {@code Call} is currently being routed.
*/
public PhoneAccount getAccount() {
return mAccount;
}
/**
* @return A bitmask of the capabilities of the {@code Call}, as defined in
* {@link CallCapabilities}.
*/
public int getCapabilities() {
return mCapabilities;
}
/**
* @return For a {@link #STATE_DISCONNECTED} {@code Call}, the disconnect cause expressed
* as a code chosen from among those declared in {@link DisconnectCause}.
*/
public int getDisconnectCauseCode() {
return mDisconnectCauseCode;
}
/**
* @return For a {@link #STATE_DISCONNECTED} {@code Call}, an optional reason for
* disconnection expressed as a free text message.
*/
public String getDisconnectCauseMsg() {
return mDisconnectCauseMsg;
}
/**
* @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;
}
@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(mAccount, d.mAccount) &&
Objects.equals(mCapabilities, d.mCapabilities) &&
Objects.equals(mDisconnectCauseCode, d.mDisconnectCauseCode) &&
Objects.equals(mDisconnectCauseMsg, d.mDisconnectCauseMsg) &&
Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) &&
Objects.equals(mGatewayInfo, d.mGatewayInfo);
}
return false;
}
@Override
public int hashCode() {
return
Objects.hashCode(mHandle) +
Objects.hashCode(mHandlePresentation) +
Objects.hashCode(mCallerDisplayName) +
Objects.hashCode(mCallerDisplayNamePresentation) +
Objects.hashCode(mAccount) +
Objects.hashCode(mCapabilities) +
Objects.hashCode(mDisconnectCauseCode) +
Objects.hashCode(mDisconnectCauseMsg) +
Objects.hashCode(mConnectTimeMillis) +
Objects.hashCode(mGatewayInfo);
}
/** {@hide} */
public Details(
Uri handle,
int handlePresentation,
String callerDisplayName,
int callerDisplayNamePresentation,
PhoneAccount account,
int capabilities,
int disconnectCauseCode,
String disconnectCauseMsg,
long connectTimeMillis,
GatewayInfo gatewayInfo) {
mHandle = handle;
mHandlePresentation = handlePresentation;
mCallerDisplayName = callerDisplayName;
mCallerDisplayNamePresentation = callerDisplayNamePresentation;
mAccount = account;
mCapabilities = capabilities;
mDisconnectCauseCode = disconnectCauseCode;
mDisconnectCauseMsg = disconnectCauseMsg;
mConnectTimeMillis = connectTimeMillis;
mGatewayInfo = gatewayInfo;
}
}
public static abstract class Listener {
/**
* Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
*
* TODO(ihab): Provide previous state also?
*
* @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 outgoing {@code Call} has finished dialing but is sending DTMF signals
* that were embedded into the outgoing number.
*
* @param call The {@code Call} invoking this method.
* @param remainingPostDialSequence The post-dial characters that remain to be sent.
*/
public void onPostDial(Call call, String remainingPostDialSequence) {}
/**
* 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 RemoteCallVideoProvider} of the {@code Call} has changed.
*
* @param call The {@code Call} invoking this method.
* @param callVideoProvider The {@code RemoteCallVideoProvider} associated with the
* {@code Call}.
*/
public void onCallVideoProviderChanged(Call call,
RemoteCallVideoProvider callVideoProvider) {}
/**
* 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) {}
}
private final Phone mPhone;
private final String mTelecommCallId;
private final InCallAdapter mInCallAdapter;
private Call mParent = null;
private int mState;
private final List<Call> mChildren = new ArrayList<>();
private final List<Call> mUnmodifiableChildren = Collections.unmodifiableList(mChildren);
private List<String> mCannedTextResponses = null;
private String mRemainingPostDialSequence;
private RemoteCallVideoProvider mCallVideoProvider;
private Details mDetails;
private final List<Listener> mListeners = new ArrayList<>();
/**
* 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.
*/
public void answer() {
mInCallAdapter.answerCall(mTelecommCallId);
}
/**
* 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(mTelecommCallId, rejectWithMessage, textMessage);
}
/**
* Instructs this {@code Call} to disconnect.
*/
public void disconnect() {
mInCallAdapter.disconnectCall(mTelecommCallId);
}
/**
* Instructs this {@code Call} to go on hold.
*/
public void hold() {
mInCallAdapter.holdCall(mTelecommCallId);
}
/**
* Instructs this {@link #STATE_HOLDING} call to release from hold.
*/
public void unhold() {
mInCallAdapter.unholdCall(mTelecommCallId);
}
/**
* 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(mTelecommCallId, 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(mTelecommCallId);
}
/**
* 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.
* While these tones are playing, this {@code Call} will notify listeners via
* {@link Listener#onPostDial(Call, String)}.
*
* If the DTMF string contains a {@link TelecommConstants#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 TelecommConstants#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(mTelecommCallId, proceed);
}
/**
* Notifies this {@code Call} that the phone account user interface element was touched.
*
* TODO(ihab): Figure out if and how we can generalize this
*/
public void phoneAccountClicked() {
mInCallAdapter.phoneAccountClicked(mTelecommCallId);
}
/**
* Instructs this {@code Call} to enter a conference.
*/
public void conference() {
mInCallAdapter.conference(mTelecommCallId);
}
/**
* Instructs this {@code Call} to split from any conference call with which it may be
* connected.
*/
public void splitFromConference() {
mInCallAdapter.splitFromConference(mTelecommCallId);
}
/**
* Instructs this {@code Call} to swap itself with an existing background call, if one
* such call exists.
*/
public void swapWithBackgroundCall() {
mInCallAdapter.swapWithBackgroundCall(mTelecommCallId);
}
/**
* 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() {
return mParent;
}
/**
* 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() {
return mUnmodifiableChildren;
}
/**
* 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 ICallVideoProvider}.
*/
public RemoteCallVideoProvider getCallVideoProvider() {
return mCallVideoProvider;
}
/**
* 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) {
mListeners.remove(listener);
}
/** {@hide} */
Call(Phone phone, String telecommCallId, InCallAdapter inCallAdapter) {
mPhone = phone;
mTelecommCallId = telecommCallId;
mInCallAdapter = inCallAdapter;
mState = STATE_NEW;
}
/** {@hide} */
final String internalGetCallId() {
return mTelecommCallId;
}
/** {@hide} */
final void internalUpdate(InCallCall inCallCall) {
// First, we update the internal state as far as possible before firing any updates.
Details details = new Details(
inCallCall.getHandle(),
inCallCall.getHandlePresentation(),
inCallCall.getCallerDisplayName(),
inCallCall.getCallerDisplayNamePresentation(),
inCallCall.getAccount(),
inCallCall.getCapabilities(),
inCallCall.getDisconnectCauseCode(),
inCallCall.getDisconnectCauseMsg(),
inCallCall.getConnectTimeMillis(),
inCallCall.getGatewayInfo());
boolean detailsChanged = !Objects.equals(mDetails, details);
if (detailsChanged) {
mDetails = details;
}
boolean cannedTextResponsesChanged = false;
if (mCannedTextResponses == null && inCallCall.getCannedSmsResponses() != null
&& !inCallCall.getCannedSmsResponses().isEmpty()) {
mCannedTextResponses = Collections.unmodifiableList(inCallCall.getCannedSmsResponses());
}
boolean callVideoProviderChanged = false;
try {
callVideoProviderChanged =
!Objects.equals(mCallVideoProvider, inCallCall.getCallVideoProvider());
if (callVideoProviderChanged) {
mCallVideoProvider = inCallCall.getCallVideoProvider();
}
} catch (RemoteException e) {
}
int state = stateFromInCallCallState(inCallCall.getState());
boolean stateChanged = mState != state;
if (stateChanged) {
mState = state;
}
if (inCallCall.getParentCallId() != null) {
mParent = mPhone.internalGetCallByTelecommId(inCallCall.getParentCallId());
}
mChildren.clear();
if (inCallCall.getChildCallIds() != null) {
for (int i = 0; i < inCallCall.getChildCallIds().size(); i++) {
mChildren.add(mPhone.internalGetCallByTelecommId(
inCallCall.getChildCallIds().get(i)));
}
}
// 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 (callVideoProviderChanged) {
fireCallVideoProviderChanged(mCallVideoProvider);
}
// 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 internalSetPostDial(String remaining) {
mRemainingPostDialSequence = remaining;
firePostDial(mRemainingPostDialSequence);
}
/** {@hide} */
final void internalSetPostDialWait(String remaining) {
mRemainingPostDialSequence = remaining;
firePostDialWait(mRemainingPostDialSequence);
}
private void fireStateChanged(int newState) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onStateChanged(this, newState);
}
}
private void fireParentChanged(Call newParent) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onParentChanged(this, newParent);
}
}
private void fireChildrenChanged(List<Call> children) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onChildrenChanged(this, children);
}
}
private void fireDetailsChanged(Details details) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onDetailsChanged(this, details);
}
}
private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onCannedTextResponsesLoaded(this, cannedTextResponses);
}
}
private void fireCallVideoProviderChanged(RemoteCallVideoProvider callVideoProvider) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onCallVideoProviderChanged(this, callVideoProvider);
}
}
private void firePostDial(String remainingPostDialSequence) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onPostDial(this, remainingPostDialSequence);
}
}
private void firePostDialWait(String remainingPostDialSequence) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onPostDialWait(this, remainingPostDialSequence);
}
}
private void fireCallDestroyed() {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onCallDestroyed(this);
}
}
private int stateFromInCallCallState(CallState inCallCallState) {
switch (inCallCallState) {
case NEW:
return STATE_NEW;
case DIALING:
return STATE_DIALING;
case RINGING:
return STATE_RINGING;
case ACTIVE:
return STATE_ACTIVE;
case ON_HOLD:
return STATE_HOLDING;
case DISCONNECTED:
return STATE_DISCONNECTED;
case ABORTED:
return STATE_DISCONNECTED;
default:
Log.wtf(this, "Unrecognized CallState %s", inCallCallState);
return STATE_NEW;
}
}
}

View File

@@ -47,22 +47,6 @@ public enum CallState {
*/
RINGING,
/**
* Indicates that the call is active but in a "post-dial" state where Telecomm is now sending
* some dual-tone multi-frequency signaling (DTMF) tones appended to the dialed number. Normal
* transitions are to {@link #POST_DIAL_WAIT} when the post-dial string requires user
* confirmation to proceed, {@link #ACTIVE} when the post-dial tones are completed, or
* {@link #DISCONNECTED}.
*/
POST_DIAL,
/**
* Indicates that the call was in the {@link #POST_DIAL} state but is now waiting for user
* confirmation before the remaining digits can be sent. Normal transitions are to
* {@link #POST_DIAL} when the user asks Telecomm to proceed with the post-dial sequence.
*/
POST_DIAL_WAIT,
/**
* 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

View File

@@ -241,6 +241,7 @@ public abstract class CallVideoClient {
*
* @param callCameraCapabilities The changed camera capabilities.
*/
public abstract void onHandleCameraCapabilitiesChange(CallCameraCapabilities callCameraCapabilities);
public abstract void onHandleCameraCapabilitiesChange(
CallCameraCapabilities callCameraCapabilities);
}

View File

@@ -24,7 +24,7 @@ import com.android.internal.telecomm.IInCallAdapter;
* Receives commands from {@link InCallService} implementations which should be executed by
* Telecomm. When Telecomm 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 ({@link InCallService#addCall}), it can use the
* 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.
@@ -167,16 +167,15 @@ public final class InCallAdapter {
* 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, Telecomm will notify the {@link InCallService} that the call
* is in the {@link InCallService#setPostDial(String,String)} state.
* is in the post dial state.
*
* If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_PAUSE} symbol, Telecomm
* will temporarily pause playing the tones for a pre-defined period of time.
*
* If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_WAIT} symbol, Telecomm
* will pause playing the tones and notify the {@link InCallService} that the call is in the
* {@link InCallService#setPostDialWait(String,String)} state. When the user decides to continue
* the postdial sequence, the {@link InCallService} should invoke the
* {@link #postDialContinue(String,boolean)} method.
* 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.

View File

@@ -16,8 +16,6 @@
package android.telecomm;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -31,11 +29,10 @@ import com.android.internal.telecomm.IInCallService;
* This service is implemented by any app that wishes to provide the user-interface for managing
* phone calls. Telecomm 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.
* TODO(santoscordon): Needs more/better description of lifecycle once the interface is better
* defined.
*
* TODO(santoscordon): What happens if two or more apps on a given device implement this interface?
*/
public abstract class InCallService extends Service {
public abstract class 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;
@@ -50,21 +47,21 @@ public abstract class InCallService extends Service {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_IN_CALL_ADAPTER:
mAdapter = new InCallAdapter((IInCallAdapter) msg.obj);
onAdapterAttached(mAdapter);
mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj));
onPhoneCreated(mPhone);
break;
case MSG_ADD_CALL:
addCall((InCallCall) msg.obj);
mPhone.internalAddCall((InCallCall) msg.obj);
break;
case MSG_UPDATE_CALL:
updateCall((InCallCall) msg.obj);
mPhone.internalUpdateCall((InCallCall) msg.obj);
break;
case MSG_SET_POST_DIAL: {
case MSG_SET_POST_DIAL: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
String remaining = (String) args.arg2;
setPostDial(callId, remaining);
mPhone.internalSetPostDial(callId, remaining);
} finally {
args.recycle();
}
@@ -75,17 +72,17 @@ public abstract class InCallService extends Service {
try {
String callId = (String) args.arg1;
String remaining = (String) args.arg2;
setPostDialWait(callId, remaining);
mPhone.internalSetPostDialWait(callId, remaining);
} finally {
args.recycle();
}
break;
}
case MSG_ON_AUDIO_STATE_CHANGED:
onAudioStateChanged((CallAudioState) msg.obj);
mPhone.internalAudioStateChanged((CallAudioState) msg.obj);
break;
case MSG_BRING_TO_FOREGROUND:
bringToForeground(msg.arg1 == 1);
mPhone.internalBringToForeground(msg.arg1 == 1);
break;
default:
break;
@@ -142,85 +139,41 @@ public abstract class InCallService extends Service {
}
}
private final InCallServiceBinder mBinder;
private Phone mPhone;
private InCallAdapter mAdapter;
protected InCallService() {}
protected InCallService() {
mBinder = new InCallServiceBinder();
}
@Override
public final IBinder onBind(Intent intent) {
return mBinder;
public final IBinder getBinder() {
return new InCallServiceBinder();
}
/**
* @return The attached {@link InCallAdapter} if attached, or null otherwise.
* 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}.
*/
protected final InCallAdapter getAdapter() {
return mAdapter;
public Phone getPhone() {
return mPhone;
}
/**
* Lifecycle callback which is called when this {@link InCallService} has been attached
* to a {@link InCallAdapter}, indicating {@link #getAdapter()} is now safe to use.
* 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 adapter The adapter now attached to this in-call service.
* @param phone The {@code Phone} object associated with this {@code InCallService}.
*/
protected void onAdapterAttached(InCallAdapter adapter) {
}
public void onPhoneCreated(Phone phone) { }
/**
* Indicates to the in-call app that a new call has been created and an appropriate
* user-interface should be built and shown to notify the user.
* 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 call Information about the new call.
* @param phone The {@code Phone} object associated with this {@code InCallService}.
*/
protected abstract void addCall(InCallCall call);
/**
* Call when information about a call has changed.
*
* @param call Information about the new call.
*/
protected abstract void updateCall(InCallCall call);
/**
* Indicates to the in-call app that the specified call is active but in a "post-dial" state
* where Telecomm is now sending some dual-tone multi-frequency signaling (DTMF) tones appended
* to the dialed number. Normal transitions are to {@link #setPostDialWait(String,String)} when
* the post-dial string requires user confirmation to proceed, and {@link CallState#ACTIVE} when
* the post-dial tones are completed.
*
* @param callId The identifier of the call changing state.
* @param remaining The remaining postdial string to be dialed.
*/
protected abstract void setPostDial(String callId, String remaining);
/**
* Indicates to the in-call app that the specified call was in the
* {@link #setPostDial(String,String)} state but is now waiting for user confirmation before the
* remaining digits can be sent. Normal transitions are to {@link #setPostDial(String,String)}
* when the user asks Telecomm to proceed with the post-dial sequence and the in-call app
* informs Telecomm of this by invoking {@link InCallAdapter#postDialContinue(String,boolean)}.
*
* @param callId The identifier of the call changing state.
* @param remaining The remaining postdial string to be dialed.
*/
protected abstract void setPostDialWait(String callId, String remaining);
/**
* Called when the audio state changes.
*
* @param audioState The new {@link CallAudioState}.
*/
protected abstract void onAudioStateChanged(CallAudioState audioState);
/**
* Brings the in-call screen to the foreground.
*
* @param showDialpad If true, put up the dialpad when the screen is shown.
*/
protected abstract void bringToForeground(boolean showDialpad);
public void onPhoneDestroyed(Phone phone) { }
}

View File

@@ -0,0 +1,255 @@
/*
* 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.telecomm;
import android.util.ArrayMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* A unified virtual device providing a means of voice (and other) communication on a device.
*/
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 CallAudioState}.
*/
public void onAudioStateChanged(Phone phone, CallAudioState 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 Telecomm-specified call ID
private final Map<String, Call> mCallByTelecommCallId = 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 ArrayList<>();
// 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 CallAudioState mAudioState;
private final List<Listener> mListeners = new ArrayList<>();
/** {@hide} */
Phone(InCallAdapter adapter) {
mInCallAdapter = adapter;
}
/** {@hide} */
final void internalAddCall(InCallCall inCallCall) {
Call call = new Call(this, inCallCall.getId(), mInCallAdapter);
mCallByTelecommCallId.put(inCallCall.getId(), call);
mCalls.add(call);
checkCallTree(inCallCall);
call.internalUpdate(inCallCall);
fireCallAdded(call);
}
/** {@hide} */
final void internalRemoveCall(Call call) {
mCallByTelecommCallId.remove(call.internalGetCallId());
mCalls.remove(call);
fireCallRemoved(call);
}
/** {@hide} */
final void internalUpdateCall(InCallCall inCallCall) {
Call call = mCallByTelecommCallId.get(inCallCall.getId());
if (call != null) {
checkCallTree(inCallCall);
call.internalUpdate(inCallCall);
}
}
/** {@hide} */
final void internalSetPostDial(String callId, String remaining) {
Call call = mCallByTelecommCallId.get(callId);
if (call != null) {
call.internalSetPostDial(remaining);
}
}
/** {@hide} */
final void internalSetPostDialWait(String callId, String remaining) {
Call call = mCallByTelecommCallId.get(callId);
if (call != null) {
call.internalSetPostDialWait(remaining);
}
}
/** {@hide} */
final void internalAudioStateChanged(CallAudioState callAudioState) {
if (!Objects.equals(mAudioState, callAudioState)) {
mAudioState = callAudioState;
fireAudioStateChanged(callAudioState);
}
}
/** {@hide} */
final Call internalGetCallByTelecommId(String telecommId) {
return mCallByTelecommCallId.get(telecommId);
}
/** {@hide} */
final void internalBringToForeground(boolean showDialpad) {
fireBringToForeground(showDialpad);
}
/**
* 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) {
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);
}
/**
* Obtains the current phone call audio state of the {@code Phone}.
*
* @return An object encapsulating the audio state.
*/
public final CallAudioState getAudioState() {
return mAudioState;
}
private void fireCallAdded(Call call) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onCallAdded(this, call);
}
}
private void fireCallRemoved(Call call) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onCallRemoved(this, call);
}
}
private void fireAudioStateChanged(CallAudioState audioState) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onAudioStateChanged(this, audioState);
}
}
private void fireBringToForeground(boolean showDialpad) {
Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
listeners[i].onBringToForeground(this, showDialpad);
}
}
private void checkCallTree(InCallCall inCallCall) {
if (inCallCall.getParentCallId() != null &&
!mCallByTelecommCallId.containsKey(inCallCall.getParentCallId())) {
Log.wtf(this, "InCallCall %s has nonexistent parent %s",
inCallCall.getId(), inCallCall.getParentCallId());
}
if (inCallCall.getChildCallIds() != null) {
for (int i = 0; i < inCallCall.getChildCallIds().size(); i++) {
if (!mCallByTelecommCallId.containsKey(inCallCall.getChildCallIds().get(i))) {
Log.wtf(this, "InCallCall %s has nonexistent child %s",
inCallCall.getId(), inCallCall.getChildCallIds().get(i));
}
}
}
}
}

View File

@@ -20,63 +20,92 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.view.Surface;
import com.android.internal.telecomm.ICallVideoClient;
import com.android.internal.telecomm.ICallVideoProvider;
public class RemoteCallVideoProvider implements IBinder.DeathRecipient {
public class RemoteCallVideoProvider {
private final ICallVideoProvider mCallVideoProvider;
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
mCallVideoProvider.asBinder().unlinkToDeath(this, 0);
}
};
/** {@hide} */
RemoteCallVideoProvider(ICallVideoProvider callVideoProvider) throws RemoteException {
mCallVideoProvider = callVideoProvider;
mCallVideoProvider.asBinder().linkToDeath(this, 0);
mCallVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
}
@Override
public void binderDied() {
mCallVideoProvider.asBinder().unlinkToDeath(this, 0);
}
public void setCallVideoClient(CallVideoClient callVideoClient) throws RemoteException {
mCallVideoProvider.setCallVideoClient(callVideoClient.getBinder());
public void setCallVideoClient(CallVideoClient callVideoClient) {
try {
mCallVideoProvider.setCallVideoClient(callVideoClient.getBinder());
} catch (RemoteException e) {
}
}
public void setCamera(String cameraId) throws RemoteException {
mCallVideoProvider.setCamera(cameraId);
}
public void setPreviewSurface(Surface surface) throws RemoteException {
mCallVideoProvider.setPreviewSurface(surface);
public void setPreviewSurface(Surface surface) {
try {
mCallVideoProvider.setPreviewSurface(surface);
} catch (RemoteException e) {
}
}
public void setDisplaySurface(Surface surface) throws RemoteException {
mCallVideoProvider.setDisplaySurface(surface);
public void setDisplaySurface(Surface surface) {
try {
mCallVideoProvider.setDisplaySurface(surface);
} catch (RemoteException e) {
}
}
public void setDeviceOrientation(int rotation) throws RemoteException {
mCallVideoProvider.setDeviceOrientation(rotation);
public void setDeviceOrientation(int rotation) {
try {
mCallVideoProvider.setDeviceOrientation(rotation);
} catch (RemoteException e) {
}
}
public void setZoom(float value) throws RemoteException {
mCallVideoProvider.setZoom(value);
}
public void sendSessionModifyRequest(VideoCallProfile requestProfile) throws RemoteException {
mCallVideoProvider.sendSessionModifyRequest(requestProfile);
public void sendSessionModifyRequest(VideoCallProfile requestProfile) {
try {
mCallVideoProvider.sendSessionModifyRequest(requestProfile);
} catch (RemoteException e) {
}
}
public void sendSessionModifyResponse(VideoCallProfile responseProfile) throws RemoteException {
mCallVideoProvider.sendSessionModifyResponse(responseProfile);
public void sendSessionModifyResponse(VideoCallProfile responseProfile) {
try {
mCallVideoProvider.sendSessionModifyResponse(responseProfile);
} catch (RemoteException e) {
}
}
public void requestCameraCapabilities() throws RemoteException {
mCallVideoProvider.requestCameraCapabilities();
public void requestCameraCapabilities() {
try {
mCallVideoProvider.requestCameraCapabilities();
} catch (RemoteException e) {
}
}
public void requestCallDataUsage() throws RemoteException {
mCallVideoProvider.requestCallDataUsage();
public void requestCallDataUsage() {
try {
mCallVideoProvider.requestCallDataUsage();
} catch (RemoteException e) {
}
}
public void setPauseImage(String uri) throws RemoteException {
mCallVideoProvider.setPauseImage(uri);
public void setPauseImage(String uri) {
try {
mCallVideoProvider.setPauseImage(uri);
} catch (RemoteException e) {
}
}
}