diff options
Diffstat (limited to 'telecomm/java/android')
37 files changed, 4318 insertions, 1448 deletions
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java index 9c03319..33013ac 100644 --- a/telecomm/java/android/telecom/AudioState.java +++ b/telecomm/java/android/telecom/AudioState.java @@ -25,10 +25,12 @@ import java.util.Locale; /** * Encapsulates the telecom audio state, including the current audio routing, supported audio * routing and mute. + * @deprecated - use {@link CallAudioState} instead. * @hide */ +@Deprecated @SystemApi -public final class AudioState implements Parcelable { +public class AudioState implements Parcelable { /** Direct the audio stream through the device's earpiece. */ public static final int ROUTE_EARPIECE = 0x00000001; @@ -47,21 +49,13 @@ public final class AudioState implements Parcelable { */ 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 | + /** Bit mask of all possible audio routes. */ + private static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET | ROUTE_SPEAKER; - /** Note: Deprecated, please do not use if possible. */ - @SystemApi public final boolean isMuted; - - /** Note: Deprecated, please do not use if possible. */ - @SystemApi public final int route; - - /** Note: Deprecated, please do not use if possible. */ - @SystemApi public final int supportedRouteMask; + private final boolean isMuted; + private final int route; + private final int supportedRouteMask; public AudioState(boolean muted, int route, int supportedRouteMask) { this.isMuted = muted; @@ -75,6 +69,12 @@ public final class AudioState implements Parcelable { supportedRouteMask = state.getSupportedRouteMask(); } + public AudioState(CallAudioState state) { + isMuted = state.isMuted(); + route = state.getRoute(); + supportedRouteMask = state.getSupportedRouteMask(); + } + @Override public boolean equals(Object obj) { if (obj == null) { @@ -97,7 +97,6 @@ public final class AudioState implements Parcelable { audioRouteToString(supportedRouteMask)); } - /** @hide */ public static String audioRouteToString(int route) { if (route == 0 || (route & ~ROUTE_ALL) != 0x0) { return "UNKNOWN"; diff --git a/telecomm/java/android/telecom/AuthenticatorService.java b/telecomm/java/android/telecom/AuthenticatorService.java new file mode 100644 index 0000000..1e43c71 --- /dev/null +++ b/telecomm/java/android/telecom/AuthenticatorService.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015 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.accounts.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.NetworkErrorException; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +/** + * A generic stub account authenticator service often used for sync adapters that do not directly + * involve accounts. + * + * @hide + */ +public class AuthenticatorService extends Service { + private static Authenticator mAuthenticator; + + @Override + public void onCreate() { + mAuthenticator = new Authenticator(this); + } + + @Override + public IBinder onBind(Intent intent) { + return mAuthenticator.getIBinder(); + } + + /** + * Stub account authenticator. All methods either return null or throw an exception. + */ + public class Authenticator extends AbstractAccountAuthenticator { + public Authenticator(Context context) { + super(context); + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse, + String s) { + throw new UnsupportedOperationException(); + } + + @Override + public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, + String s, String s2, String[] strings, Bundle bundle) + throws NetworkErrorException { + return null; + } + + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, + Account account, Bundle bundle) + throws NetworkErrorException { + return null; + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse, + Account account, String s, Bundle bundle) + throws NetworkErrorException { + throw new UnsupportedOperationException(); + } + + @Override + public String getAuthTokenLabel(String s) { + throw new UnsupportedOperationException(); + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, + Account account, String s, Bundle bundle) + throws NetworkErrorException { + throw new UnsupportedOperationException(); + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse, + Account account, String[] strings) + throws NetworkErrorException { + throw new UnsupportedOperationException(); + } + } +} diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index d8a14ef..4569549 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -19,10 +19,12 @@ package android.telecom; import android.annotation.SystemApi; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import java.lang.String; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -30,10 +32,7 @@ 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. @@ -69,9 +68,18 @@ public final class Call { public static final int STATE_DISCONNECTED = 7; /** - * The state of an outgoing {@code Call}, but waiting for user input before proceeding. + * The state of an outgoing {@code Call} when waiting on user to select a + * {@link PhoneAccount} through which to place the call. + */ + public static final int STATE_SELECT_PHONE_ACCOUNT = 8; + + /** + * @hide + * @deprecated use STATE_SELECT_PHONE_ACCOUNT. */ - public static final int STATE_PRE_DIAL_WAIT = 8; + @Deprecated + @SystemApi + public static final int STATE_PRE_DIAL_WAIT = STATE_SELECT_PHONE_ACCOUNT; /** * The initial state of an outgoing {@code Call}. @@ -91,8 +99,6 @@ public final class Call { * The key to retrieve the optional {@code PhoneAccount}s Telecom can bundle with its Call * extras. Used to pass the phone accounts to display on the front end to the user in order to * select phone accounts to (for example) place a call. - * - * @hide */ public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts"; @@ -126,7 +132,7 @@ public final class Call { /** * @hide */ - public static final int CAPABILITY_UNUSED = 0x00000010; + public static final int CAPABILITY_UNUSED_1 = 0x00000010; /** Call supports responding via text option. */ public static final int CAPABILITY_RESPOND_VIA_TEXT = 0x00000020; @@ -141,28 +147,36 @@ public final class Call { public static final int CAPABILITY_MANAGE_CONFERENCE = 0x00000080; /** - * Local device supports video telephony. - * @hide + * Local device supports receiving video. */ - public static final int CAPABILITY_SUPPORTS_VT_LOCAL = 0x00000100; + public static final int CAPABILITY_SUPPORTS_VT_LOCAL_RX = 0x00000100; /** - * Remote device supports video telephony. - * @hide + * Local device supports transmitting video. */ - public static final int CAPABILITY_SUPPORTS_VT_REMOTE = 0x00000200; + public static final int CAPABILITY_SUPPORTS_VT_LOCAL_TX = 0x00000200; /** - * Call is using high definition audio. - * @hide + * Local device supports bidirectional video calling. */ - public static final int CAPABILITY_HIGH_DEF_AUDIO = 0x00000400; + public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = + CAPABILITY_SUPPORTS_VT_LOCAL_RX | CAPABILITY_SUPPORTS_VT_LOCAL_TX; /** - * Call is using voice over WIFI. - * @hide + * Remote device supports receiving video. */ - public static final int CAPABILITY_VoWIFI = 0x00000800; + public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 0x00000400; + + /** + * Remote device supports transmitting video. + */ + public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 0x00000800; + + /** + * Remote device supports bidirectional video calling. + */ + public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = + CAPABILITY_SUPPORTS_VT_REMOTE_RX | CAPABILITY_SUPPORTS_VT_REMOTE_TX; /** * Call is able to be separated from its parent {@code Conference}, if any. @@ -173,20 +187,58 @@ public final class Call { * Call is able to be individually disconnected when in a {@code Conference}. */ public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 0x00002000; - + /** - * Whether the call is a generic conference, where we do not know the precise state of - * participants in the conference (eg. on CDMA). - * + * Speed up audio setup for MT call. * @hide */ - public static final int CAPABILITY_GENERIC_CONFERENCE = 0x00004000; + public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00040000; /** - * Speed up audio setup for MT call. + * Call can be upgraded to a video call. * @hide */ - public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00008000; + public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 0x00080000; + + /** + * For video calls, indicates whether the outgoing video for the call can be paused using + * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. + */ + public static final int CAPABILITY_CAN_PAUSE_VIDEO = 0x00100000; + + //****************************************************************************************** + // Next CAPABILITY value: 0x00004000 + //****************************************************************************************** + + /** + * Whether the call is currently a conference. + */ + public static final int PROPERTY_CONFERENCE = 0x00000001; + + /** + * Whether the call is a generic conference, where we do not know the precise state of + * participants in the conference (eg. on CDMA). + */ + public static final int PROPERTY_GENERIC_CONFERENCE = 0x00000002; + + /** + * Whether the call is made while the device is in emergency callback mode. + */ + public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 0x00000004; + + /** + * Connection is using WIFI. + */ + public static final int PROPERTY_WIFI = 0x00000008; + + /** + * Call is using high definition audio. + */ + public static final int PROPERTY_HIGH_DEF_AUDIO = 0x00000010; + + //****************************************************************************************** + // Next PROPERTY value: 0x00000020 + //****************************************************************************************** private final Uri mHandle; private final int mHandlePresentation; @@ -201,6 +253,7 @@ public final class Call { private final int mVideoState; private final StatusHints mStatusHints; private final Bundle mExtras; + private final Bundle mIntentExtras; /** * Whether the supplied capabilities supports the specified capability. @@ -208,7 +261,6 @@ public final class Call { * @param capabilities A bit field of capabilities. * @param capability The capability to check capabilities for. * @return Whether the specified capability is supported. - * @hide */ public static boolean can(int capabilities, int capability) { return (capabilities & capability) != 0; @@ -219,7 +271,6 @@ public final class Call { * * @param capability The capability to check capabilities for. * @return Whether the specified capability is supported. - * @hide */ public boolean can(int capability) { return can(mCallCapabilities, capability); @@ -255,23 +306,81 @@ public final class Call { if (can(capabilities, CAPABILITY_MANAGE_CONFERENCE)) { builder.append(" CAPABILITY_MANAGE_CONFERENCE"); } - if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL)) { - builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL"); + if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_RX)) { + builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_RX"); + } + if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_TX)) { + builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_TX"); } - if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE)) { - builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE"); + if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) { + builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL"); } - if (can(capabilities, CAPABILITY_HIGH_DEF_AUDIO)) { - builder.append(" CAPABILITY_HIGH_DEF_AUDIO"); + if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_RX)) { + builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_RX"); } - if (can(capabilities, CAPABILITY_VoWIFI)) { - builder.append(" CAPABILITY_VoWIFI"); + if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_TX)) { + builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_TX"); } - if (can(capabilities, CAPABILITY_GENERIC_CONFERENCE)) { - builder.append(" CAPABILITY_GENERIC_CONFERENCE"); + if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) { + builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL"); } if (can(capabilities, CAPABILITY_SPEED_UP_MT_AUDIO)) { - builder.append(" CAPABILITY_SPEED_UP_IMS_MT_AUDIO"); + builder.append(" CAPABILITY_SPEED_UP_MT_AUDIO"); + } + if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) { + builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO"); + } + if (can(capabilities, CAPABILITY_CAN_PAUSE_VIDEO)) { + builder.append(" CAPABILITY_CAN_PAUSE_VIDEO"); + } + builder.append("]"); + return builder.toString(); + } + + /** + * Whether the supplied properties includes the specified property. + * + * @param properties A bit field of properties. + * @param property The property to check properties for. + * @return Whether the specified property is supported. + */ + public static boolean hasProperty(int properties, int property) { + return (properties & property) != 0; + } + + /** + * Whether the properties of this {@code Details} includes the specified property. + * + * @param property The property to check properties for. + * @return Whether the specified property is supported. + */ + public boolean hasProperty(int property) { + return hasProperty(mCallProperties, property); + } + + /** + * Render a set of property bits ({@code PROPERTY_*}) as a human readable string. + * + * @param properties A property bit field. + * @return A human readable string representation. + */ + public static String propertiesToString(int properties) { + StringBuilder builder = new StringBuilder(); + builder.append("[Properties:"); + if (hasProperty(properties, PROPERTY_CONFERENCE)) { + builder.append(" PROPERTY_CONFERENCE"); + } + if (hasProperty(properties, PROPERTY_GENERIC_CONFERENCE)) { + builder.append(" PROPERTY_GENERIC_CONFERENCE"); + } + if (hasProperty(properties, PROPERTY_WIFI)) { + builder.append(" PROPERTY_WIFI"); + } + if (hasProperty(properties, PROPERTY_HIGH_DEF_AUDIO)) { + builder.append(" PROPERTY_HIGH_DEF_AUDIO"); + } + if (hasProperty(properties, PROPERTY_EMERGENCY_CALLBACK_MODE)) { + builder.append(" PROPERTY_EMERGENCY_CALLBACK_MODE"); } builder.append("]"); return builder.toString(); @@ -325,8 +434,8 @@ public final class Call { } /** - * @return A bitmask of the properties of the {@code Call}, as defined in - * {@link CallProperties}. + * @return A bitmask of the properties of the {@code Call}, as defined by the various + * {@code PROPERTY_*} constants in this class. */ public int getCallProperties() { return mCallProperties; @@ -345,7 +454,7 @@ public final class Call { * periodically, but user interfaces should not rely on this to display any "call time * clock". */ - public long getConnectTimeMillis() { + public final long getConnectTimeMillis() { return mConnectTimeMillis; } @@ -372,12 +481,19 @@ public final class Call { } /** - * @return A bundle extras to pass with the call + * @return The extras associated with this call. */ public Bundle getExtras() { return mExtras; } + /** + * @return The extras used with the original intent to place this call. + */ + public Bundle getIntentExtras() { + return mIntentExtras; + } + @Override public boolean equals(Object o) { if (o instanceof Details) { @@ -396,7 +512,8 @@ public final class Call { Objects.equals(mGatewayInfo, d.mGatewayInfo) && Objects.equals(mVideoState, d.mVideoState) && Objects.equals(mStatusHints, d.mStatusHints) && - Objects.equals(mExtras, d.mExtras); + Objects.equals(mExtras, d.mExtras) && + Objects.equals(mIntentExtras, d.mIntentExtras); } return false; } @@ -416,7 +533,8 @@ public final class Call { Objects.hashCode(mGatewayInfo) + Objects.hashCode(mVideoState) + Objects.hashCode(mStatusHints) + - Objects.hashCode(mExtras); + Objects.hashCode(mExtras) + + Objects.hashCode(mIntentExtras); } /** {@hide} */ @@ -433,7 +551,8 @@ public final class Call { GatewayInfo gatewayInfo, int videoState, StatusHints statusHints, - Bundle extras) { + Bundle extras, + Bundle intentExtras) { mHandle = handle; mHandlePresentation = handlePresentation; mCallerDisplayName = callerDisplayName; @@ -447,10 +566,11 @@ public final class Call { mVideoState = videoState; mStatusHints = statusHints; mExtras = extras; + mIntentExtras = intentExtras; } } - public static abstract class Listener { + public static abstract class Callback { /** * Invoked when the state of this {@code Call} has changed. See {@link #getState()}. * @@ -509,7 +629,6 @@ public final class Call { * * @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) {} @@ -535,13 +654,21 @@ public final class Call { public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {} } + /** + * @deprecated Use {@code Call.Callback} instead. + * @hide + */ + @Deprecated + @SystemApi + public static abstract class Listener extends Callback { } + 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<CallbackRecord<Callback>> mCallbackRecords = new CopyOnWriteArrayList<>(); private final List<Call> mConferenceableCalls = new ArrayList<>(); private final List<Call> mUnmodifiableConferenceableCalls = Collections.unmodifiableList(mConferenceableCalls); @@ -636,8 +763,8 @@ public final class Call { * {@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 + * {@code Call} will pause playing the tones and notify callbacks via + * {@link Callback#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. @@ -762,7 +889,6 @@ public final class Call { * 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; @@ -779,25 +905,72 @@ public final class Call { } /** + * Registers a callback to this {@code Call}. + * + * @param callback A {@code Callback}. + */ + public void registerCallback(Callback callback) { + registerCallback(callback, new Handler()); + } + + /** + * Registers a callback to this {@code Call}. + * + * @param callback A {@code Callback}. + * @param handler A handler which command and status changes will be delivered to. + */ + public void registerCallback(Callback callback, Handler handler) { + unregisterCallback(callback); + // Don't allow new callback registration if the call is already being destroyed. + if (callback != null && handler != null && mState != STATE_DISCONNECTED) { + mCallbackRecords.add(new CallbackRecord<Callback>(callback, handler)); + } + } + + /** + * Unregisters a callback from this {@code Call}. + * + * @param callback A {@code Callback}. + */ + public void unregisterCallback(Callback callback) { + // Don't allow callback deregistration if the call is already being destroyed. + if (callback != null && mState != STATE_DISCONNECTED) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + if (record.getCallback() == callback) { + mCallbackRecords.remove(record); + break; + } + } + } + } + + /** * Adds a listener to this {@code Call}. * * @param listener A {@code Listener}. + * @deprecated Use {@link #registerCallback} instead. + * @hide */ + @Deprecated + @SystemApi public void addListener(Listener listener) { - mListeners.add(listener); + registerCallback(listener); } /** * Removes a listener from this {@code Call}. * * @param listener A {@code Listener}. + * @deprecated Use {@link #unregisterCallback} instead. + * @hide */ + @Deprecated + @SystemApi public void removeListener(Listener listener) { - if (listener != null) { - mListeners.remove(listener); - } + unregisterCallback(listener); } + /** {@hide} */ Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter) { mPhone = phone; @@ -827,7 +1000,8 @@ public final class Call { parcelableCall.getGatewayInfo(), parcelableCall.getVideoState(), parcelableCall.getStatusHints(), - parcelableCall.getExtras()); + parcelableCall.getExtras(), + parcelableCall.getIntentExtras()); boolean detailsChanged = !Objects.equals(mDetails, details); if (detailsChanged) { mDetails = details; @@ -840,12 +1014,13 @@ public final class Call { Collections.unmodifiableList(parcelableCall.getCannedSmsResponses()); } - boolean videoCallChanged = !Objects.equals(mVideoCall, parcelableCall.getVideoCall()); + boolean videoCallChanged = parcelableCall.isVideoCallProviderChanged() && + !Objects.equals(mVideoCall, parcelableCall.getVideoCall(this)); if (videoCallChanged) { - mVideoCall = parcelableCall.getVideoCall(); + mVideoCall = parcelableCall.getVideoCall(this); } - int state = stateFromParcelableCallState(parcelableCall.getState()); + int state = parcelableCall.getState(); boolean stateChanged = mState != state; if (stateChanged) { mState = state; @@ -907,7 +1082,6 @@ public final class Call { // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list. if (mState == STATE_DISCONNECTED) { fireCallDestroyed(); - mPhone.internalRemoveCall(this); } } @@ -923,89 +1097,152 @@ public final class Call { 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 fireStateChanged(final int newState) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onStateChanged(call, newState); + } + }); } } - private void fireParentChanged(Call newParent) { - for (Listener listener : mListeners) { - listener.onParentChanged(this, newParent); + private void fireParentChanged(final Call newParent) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onParentChanged(call, newParent); + } + }); } } - private void fireChildrenChanged(List<Call> children) { - for (Listener listener : mListeners) { - listener.onChildrenChanged(this, children); + private void fireChildrenChanged(final List<Call> children) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onChildrenChanged(call, children); + } + }); } } - private void fireDetailsChanged(Details details) { - for (Listener listener : mListeners) { - listener.onDetailsChanged(this, details); + private void fireDetailsChanged(final Details details) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onDetailsChanged(call, details); + } + }); } } - private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) { - for (Listener listener : mListeners) { - listener.onCannedTextResponsesLoaded(this, cannedTextResponses); + private void fireCannedTextResponsesLoaded(final List<String> cannedTextResponses) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onCannedTextResponsesLoaded(call, cannedTextResponses); + } + }); } } - private void fireVideoCallChanged(InCallService.VideoCall videoCall) { - for (Listener listener : mListeners) { - listener.onVideoCallChanged(this, videoCall); + private void fireVideoCallChanged(final InCallService.VideoCall videoCall) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onVideoCallChanged(call, videoCall); + } + }); } } - private void firePostDialWait(String remainingPostDialSequence) { - for (Listener listener : mListeners) { - listener.onPostDialWait(this, remainingPostDialSequence); + private void firePostDialWait(final String remainingPostDialSequence) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onPostDialWait(call, remainingPostDialSequence); + } + }); } } private void fireCallDestroyed() { - for (Listener listener : mListeners) { - listener.onCallDestroyed(this); + /** + * To preserve the ordering of the Call's onCallDestroyed callback and Phone's + * onCallRemoved callback, we remove this call from the Phone's record + * only once all of the registered onCallDestroyed callbacks are executed. + * All the callbacks get removed from our records as a part of this operation + * since onCallDestroyed is the final callback. + */ + final Call call = this; + if (mCallbackRecords.isEmpty()) { + // No callbacks registered, remove the call from Phone's record. + mPhone.internalRemoveCall(call); + } + for (final CallbackRecord<Callback> record : mCallbackRecords) { + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + boolean isFinalRemoval = false; + RuntimeException toThrow = null; + try { + callback.onCallDestroyed(call); + } catch (RuntimeException e) { + toThrow = e; + } + synchronized(Call.this) { + mCallbackRecords.remove(record); + if (mCallbackRecords.isEmpty()) { + isFinalRemoval = true; + } + } + if (isFinalRemoval) { + mPhone.internalRemoveCall(call); + } + if (toThrow != null) { + throw toThrow; + } + } + }); } } 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; - case CallState.DISCONNECTING: - return STATE_DISCONNECTING; - default: - Log.wtf(this, "Unrecognized CallState %s", parcelableCallState); - return STATE_NEW; + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onConferenceableCallsChanged(call, mUnmodifiableConferenceableCalls); + } + }); } } } diff --git a/telecomm/java/android/telecom/CameraCapabilities.aidl b/telecomm/java/android/telecom/CallAudioState.aidl index c8e0c5e..90dbbe5 100644 --- a/telecomm/java/android/telecom/CameraCapabilities.aidl +++ b/telecomm/java/android/telecom/CallAudioState.aidl @@ -1,17 +1,17 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * 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 + * 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 + * limitations under the License. */ package android.telecom; @@ -19,4 +19,4 @@ package android.telecom; /** * {@hide} */ -parcelable CameraCapabilities; +parcelable CallAudioState; diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java new file mode 100644 index 0000000..2b16722 --- /dev/null +++ b/telecomm/java/android/telecom/CallAudioState.java @@ -0,0 +1,209 @@ +/* + * 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 the telecom audio state, including the current audio routing, supported audio + * routing and mute. + */ +public final class CallAudioState 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. */ + private static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET | + ROUTE_SPEAKER; + + private final boolean isMuted; + private final int route; + private final int supportedRouteMask; + + /** + * Constructor for a {@link CallAudioState} object. + * + * @param muted {@code true} if the call is muted, {@code false} otherwise. + * @param route The current audio route being used. + * Allowed values: + * {@link #ROUTE_EARPIECE} + * {@link #ROUTE_BLUETOOTH} + * {@link #ROUTE_WIRED_HEADSET} + * {@link #ROUTE_SPEAKER} + * @param supportedRouteMask Bit mask of all routes supported by this call. This should be a + * bitwise combination of the following values: + * {@link #ROUTE_EARPIECE} + * {@link #ROUTE_BLUETOOTH} + * {@link #ROUTE_WIRED_HEADSET} + * {@link #ROUTE_SPEAKER} + */ + public CallAudioState(boolean muted, int route, int supportedRouteMask) { + this.isMuted = muted; + this.route = route; + this.supportedRouteMask = supportedRouteMask; + } + + /** @hide */ + public CallAudioState(CallAudioState state) { + isMuted = state.isMuted(); + route = state.getRoute(); + supportedRouteMask = state.getSupportedRouteMask(); + } + + /** @hide */ + @SuppressWarnings("deprecation") + public CallAudioState(AudioState state) { + isMuted = state.isMuted(); + route = state.getRoute(); + supportedRouteMask = state.getSupportedRouteMask(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof CallAudioState)) { + return false; + } + CallAudioState state = (CallAudioState) obj; + return isMuted() == state.isMuted() && getRoute() == state.getRoute() && + getSupportedRouteMask() == state.getSupportedRouteMask(); + } + + @Override + public String toString() { + return String.format(Locale.US, + "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s]", + isMuted, + audioRouteToString(route), + audioRouteToString(supportedRouteMask)); + } + + /** + * @return {@code true} if the call is muted, {@code false} otherwise. + */ + public boolean isMuted() { + return isMuted; + } + + /** + * @return The current audio route being used. + */ + public int getRoute() { + return route; + } + + /** + * @return Bit mask of all routes supported by this call. + */ + public int getSupportedRouteMask() { + return supportedRouteMask; + } + + /** + * Converts the provided audio route into a human readable string representation. + * + * @param route to convert into a string. + * + * @return String representation of the provided audio route. + */ + 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(); + } + + /** + * Responsible for creating AudioState objects for deserialized Parcels. + */ + public static final Parcelable.Creator<CallAudioState> CREATOR = + new Parcelable.Creator<CallAudioState> () { + + @Override + public CallAudioState createFromParcel(Parcel source) { + boolean isMuted = source.readByte() == 0 ? false : true; + int route = source.readInt(); + int supportedRouteMask = source.readInt(); + return new CallAudioState(isMuted, route, supportedRouteMask); + } + + @Override + public CallAudioState[] newArray(int size) { + return new CallAudioState[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); + } + + private static void listAppend(StringBuffer buffer, String str) { + if (buffer.length() > 0) { + buffer.append(", "); + } + buffer.append(str); + } +} diff --git a/telecomm/java/android/telecom/CallProperties.java b/telecomm/java/android/telecom/CallProperties.java deleted file mode 100644 index b1b82e2..0000000 --- a/telecomm/java/android/telecom/CallProperties.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 deleted file mode 100644 index bd9223a..0000000 --- a/telecomm/java/android/telecom/CallState.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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; - - /** - * Indicates that the call is in the process of being disconnected and will transition next - * to a {@link #DISCONNECTED} state. - * <p> - * This state is not expected to be communicated from the Telephony layer, but will be reported - * to the InCall UI for calls where disconnection has been initiated by the user but the - * ConnectionService has confirmed the call as disconnected. - */ - public static final int DISCONNECTING = 9; - - 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"; - case DISCONNECTING: - return "DISCONNECTING"; - default: - return "UNKNOWN"; - } - } -} diff --git a/telecomm/java/android/telecom/CallbackRecord.java b/telecomm/java/android/telecom/CallbackRecord.java new file mode 100644 index 0000000..1a81925 --- /dev/null +++ b/telecomm/java/android/telecom/CallbackRecord.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 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; + + +/** + * This class is used to associate a generic callback of type T with a handler to which commands and + * status updates will be delivered to. + * + * @hide + */ +class CallbackRecord<T> { + private final T mCallback; + private final Handler mHandler; + + public CallbackRecord(T callback, Handler handler) { + mCallback = callback; + mHandler = handler; + } + + public T getCallback() { + return mCallback; + } + + public Handler getHandler() { + return mHandler; + } +} diff --git a/telecomm/java/android/telecom/CameraCapabilities.java b/telecomm/java/android/telecom/CameraCapabilities.java deleted file mode 100644 index f968c13..0000000 --- a/telecomm/java/android/telecom/CameraCapabilities.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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 index 33bbb29..77fdb65 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -16,27 +16,29 @@ package android.telecom; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.os.Bundle; +import android.telecom.Connection.VideoProvider; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; 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. - * @hide */ -@SystemApi -public abstract class Conference implements IConferenceable { +public abstract class Conference extends Conferenceable { /** * Used to indicate that the conference connection time is not specified. If not specified, * Telecom will set the connect time. */ - public static long CONNECT_TIME_NOT_SPECIFIED = 0; + public static final long CONNECT_TIME_NOT_SPECIFIED = 0; /** @hide */ public abstract static class Listener { @@ -49,6 +51,10 @@ public abstract class Conference implements IConferenceable { public void onDestroyed(Conference conference) {} public void onConnectionCapabilitiesChanged( Conference conference, int connectionCapabilities) {} + public void onVideoStateChanged(Conference c, int videoState) { } + public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) {} + public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {} + public void onExtrasChanged(Conference conference, Bundle extras) {} } private final Set<Listener> mListeners = new CopyOnWriteArraySet<>(); @@ -59,13 +65,15 @@ public abstract class Conference implements IConferenceable { private final List<Connection> mUnmodifiableConferenceableConnections = Collections.unmodifiableList(mConferenceableConnections); - protected PhoneAccountHandle mPhoneAccount; - private AudioState mAudioState; + private PhoneAccountHandle mPhoneAccount; + private CallAudioState mCallAudioState; private int mState = Connection.STATE_NEW; private DisconnectCause mDisconnectCause; private int mConnectionCapabilities; private String mDisconnectMessage; private long mConnectTimeMillis = CONNECT_TIME_NOT_SPECIFIED; + private StatusHints mStatusHints; + private Bundle mExtras; private final Connection.Listener mConnectionDeathListener = new Connection.Listener() { @Override @@ -112,13 +120,8 @@ public abstract class Conference implements IConferenceable { return mState; } - /** @hide */ - @Deprecated public final int getCapabilities() { - return getConnectionCapabilities(); - } - /** - * Returns the capabilities of a conference. See {@code CAPABILITY_*} constants in class + * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class * {@link Connection} for valid values. * * @return A bitmask of the capabilities of the conference call. @@ -174,9 +177,36 @@ public abstract class Conference implements IConferenceable { * @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. + * @deprecated Use {@link #getCallAudioState()} instead. + * @hide */ + @Deprecated + @SystemApi public final AudioState getAudioState() { - return mAudioState; + return new AudioState(mCallAudioState); + } + + /** + * @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 CallAudioState getCallAudioState() { + return mCallAudioState; + } + + /** + * Returns VideoProvider of the primary call. This can be null. + */ + public VideoProvider getVideoProvider() { + return null; + } + + /** + * Returns video state of the primary call. + */ + public int getVideoState() { + return VideoProfile.STATE_AUDIO_ONLY; } /** @@ -236,10 +266,21 @@ public abstract class Conference implements IConferenceable { * Notifies this conference that the {@link #getAudioState()} property has a new value. * * @param state The new call audio state. + * @deprecated Use {@link #onCallAudioStateChanged(CallAudioState)} instead. + * @hide */ + @SystemApi + @Deprecated public void onAudioStateChanged(AudioState state) {} /** + * Notifies this conference that the {@link #getCallAudioState()} property has a new value. + * + * @param state The new call audio state. + */ + public void onCallAudioStateChanged(CallAudioState state) {} + + /** * Notifies this conference that a connection has been added to it. * * @param connection The newly added connection. @@ -254,6 +295,13 @@ public abstract class Conference implements IConferenceable { } /** + * Sets state to be dialing. + */ + public final void setDialing() { + setState(Connection.STATE_DIALING); + } + + /** * Sets state to be active. */ public final void setActive() { @@ -281,11 +329,6 @@ public abstract class Conference implements IConferenceable { return mDisconnectCause; } - /** @hide */ - @Deprecated public final void setCapabilities(int connectionCapabilities) { - setConnectionCapabilities(connectionCapabilities); - } - /** * Sets the capabilities of a conference. See {@code CAPABILITY_*} constants of class * {@link Connection} for valid values. @@ -309,6 +352,7 @@ public abstract class Conference implements IConferenceable { * @return True if the connection was successfully added. */ public final boolean addConnection(Connection connection) { + Log.d(this, "Connection=%s, connection=", connection); if (connection != null && !mChildConnections.contains(connection)) { if (connection.setConference(this)) { mChildConnections.add(connection); @@ -355,6 +399,36 @@ public abstract class Conference implements IConferenceable { fireOnConferenceableConnectionsChanged(); } + /** + * Set the video state for the conference. + * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, + * {@link VideoProfile#STATE_BIDIRECTIONAL}, + * {@link VideoProfile#STATE_TX_ENABLED}, + * {@link VideoProfile#STATE_RX_ENABLED}. + * + * @param videoState The new video state. + */ + public final void setVideoState(Connection c, int videoState) { + Log.d(this, "setVideoState Conference: %s Connection: %s VideoState: %s", + this, c, videoState); + for (Listener l : mListeners) { + l.onVideoStateChanged(this, videoState); + } + } + + /** + * Sets the video connection provider. + * + * @param videoProvider The video provider. + */ + public final void setVideoProvider(Connection c, Connection.VideoProvider videoProvider) { + Log.d(this, "setVideoProvider Conference: %s Connection: %s VideoState: %s", + this, c, videoProvider); + for (Listener l : mListeners) { + l.onVideoProviderChanged(this, videoProvider); + } + } + private final void fireOnConferenceableConnectionsChanged() { for (Listener l : mListeners) { l.onConferenceableConnectionsChanged(this, getConferenceableConnections()); @@ -420,7 +494,9 @@ public abstract class Conference implements IConferenceable { * the connection from which the conference will retrieve its current state. * * @return The primary connection. + * @hide */ + @SystemApi public Connection getPrimaryConnection() { if (mUnmodifiableChildConnections == null || mUnmodifiableChildConnections.isEmpty()) { return null; @@ -429,22 +505,42 @@ public abstract class Conference implements IConferenceable { } /** - * Sets the connect time of the {@code Conference}. + * @hide + * @deprecated Use {@link #setConnectionTime}. + */ + @Deprecated + @SystemApi + public final void setConnectTimeMillis(long connectTimeMillis) { + setConnectionTime(connectTimeMillis); + } + + /** + * Sets the connection start time of the {@code Conference}. * - * @param connectTimeMillis The connection time, in milliseconds. + * @param connectionTimeMillis The connection time, in milliseconds. + */ + public final void setConnectionTime(long connectionTimeMillis) { + mConnectTimeMillis = connectionTimeMillis; + } + + /** + * @hide + * @deprecated Use {@link #getConnectionTime}. */ - public void setConnectTimeMillis(long connectTimeMillis) { - mConnectTimeMillis = connectTimeMillis; + @Deprecated + @SystemApi + public final long getConnectTimeMillis() { + return getConnectionTime(); } /** - * Retrieves the connect time of the {@code Conference}, if specified. A value of + * Retrieves the connection start time of the {@code Conference}, if specified. A value of * {@link #CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the start time * of the conference. * - * @return The time the {@code Conference} has been connected. + * @return The time at which the {@code Conference} was connected. */ - public long getConnectTimeMillis() { + public final long getConnectionTime() { return mConnectTimeMillis; } @@ -454,10 +550,11 @@ public abstract class Conference implements IConferenceable { * @param state The new audio state. * @hide */ - final void setAudioState(AudioState state) { - Log.d(this, "setAudioState %s", state); - mAudioState = state; - onAudioStateChanged(state); + final void setCallAudioState(CallAudioState state) { + Log.d(this, "setCallAudioState %s", state); + mCallAudioState = state; + onAudioStateChanged(getAudioState()); + onCallAudioStateChanged(state); } private void setState(int newState) { @@ -484,4 +581,55 @@ public abstract class Conference implements IConferenceable { } mConferenceableConnections.clear(); } + + @Override + public String toString() { + return String.format(Locale.US, + "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]", + Connection.stateToString(mState), + Call.Details.capabilitiesToString(mConnectionCapabilities), + getVideoState(), + getVideoProvider(), + super.toString()); + } + + /** + * Sets the label and icon status to display in the InCall 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); + } + } + + /** + * @return The status hints for this conference. + */ + public final StatusHints getStatusHints() { + return mStatusHints; + } + + /** + * Set some extras that can be associated with this {@code Conference}. No assumptions should + * be made as to how an In-Call UI or service will handle these extras. + * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts. + * + * @param extras The extras associated with this {@code Connection}. + */ + public final void setExtras(@Nullable Bundle extras) { + mExtras = extras; + for (Listener l : mListeners) { + l.onExtrasChanged(this, extras); + } + } + + /** + * @return The extras associated with this conference. + */ + public final Bundle getExtras() { + return mExtras; + } } diff --git a/telecomm/java/android/telecom/IConferenceable.java b/telecomm/java/android/telecom/Conferenceable.java index 095d7cb..bb6f2b8 100644 --- a/telecomm/java/android/telecom/IConferenceable.java +++ b/telecomm/java/android/telecom/Conferenceable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2015 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. @@ -16,16 +16,11 @@ package android.telecom; -import android.annotation.SystemApi; - /** * Interface used to identify entities with which another entity can participate in a conference * call with. The {@link ConnectionService} implementation will only recognize - * {@link IConferenceable}s which are {@link Connection}s or {@link Conference}s. - * - * @hide + * {@link Conferenceable}s which are {@link Connection}s or {@link Conference}s. */ -@SystemApi -public interface IConferenceable { - +public abstract class Conferenceable { + Conferenceable() {} } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 00a4136..f304d1d 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -16,13 +16,18 @@ package android.telecom; +import com.android.internal.os.SomeArgs; import com.android.internal.telecom.IVideoCallback; import com.android.internal.telecom.IVideoProvider; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.hardware.camera2.CameraManager; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.view.Surface; @@ -34,7 +39,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** - * Represents a connection to a remote endpoint that carries voice traffic. + * Represents a phone call or connection to a remote endpoint that carries voice and/or video + * traffic. * <p> * Implementations create a custom subclass of {@code Connection} and return it to the framework * as the return value of @@ -44,26 +50,55 @@ import java.util.concurrent.ConcurrentHashMap; * 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. - * @hide */ -@SystemApi -public abstract class Connection implements IConferenceable { +public abstract class Connection extends Conferenceable { + /** + * The connection is initializing. This is generally the first state for a {@code Connection} + * returned by a {@link ConnectionService}. + */ public static final int STATE_INITIALIZING = 0; + /** + * The connection is new and not connected. + */ public static final int STATE_NEW = 1; + /** + * An incoming connection is in the ringing state. During this state, the user's ringer or + * vibration feature will be activated. + */ public static final int STATE_RINGING = 2; + /** + * An outgoing connection is in the dialing state. In this state the other party has not yet + * answered the call and the user traditionally hears a ringback tone. + */ public static final int STATE_DIALING = 3; + /** + * A connection is active. Both parties are connected to the call and can actively communicate. + */ public static final int STATE_ACTIVE = 4; + /** + * A connection is on hold. + */ public static final int STATE_HOLDING = 5; + /** + * A connection has been disconnected. This is the final state once the user has been + * disconnected from a call either locally, remotely or by an error in the service. + */ public static final int STATE_DISCONNECTED = 6; - /** Connection can currently be put on hold or unheld. */ + /** + * Connection can currently be put on hold or unheld. This is distinct from + * {@link #CAPABILITY_SUPPORT_HOLD} in that although a connection may support 'hold' most times, + * it does not at the moment support the function. This can be true while the call is in the + * state {@link #STATE_DIALING}, for example. During this condition, an in-call UI may + * display a disabled 'hold' button. + */ public static final int CAPABILITY_HOLD = 0x00000001; /** Connection supports the hold feature. */ @@ -106,28 +141,36 @@ public abstract class Connection implements IConferenceable { public static final int CAPABILITY_MANAGE_CONFERENCE = 0x00000080; /** - * Local device supports video telephony. - * @hide + * Local device supports receiving video. */ - public static final int CAPABILITY_SUPPORTS_VT_LOCAL = 0x00000100; + public static final int CAPABILITY_SUPPORTS_VT_LOCAL_RX = 0x00000100; /** - * Remote device supports video telephony. - * @hide + * Local device supports transmitting video. */ - public static final int CAPABILITY_SUPPORTS_VT_REMOTE = 0x00000200; + public static final int CAPABILITY_SUPPORTS_VT_LOCAL_TX = 0x00000200; /** - * Connection is using high definition audio. - * @hide + * Local device supports bidirectional video calling. */ - public static final int CAPABILITY_HIGH_DEF_AUDIO = 0x00000400; + public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = + CAPABILITY_SUPPORTS_VT_LOCAL_RX | CAPABILITY_SUPPORTS_VT_LOCAL_TX; /** - * Connection is using voice over WIFI. - * @hide + * Remote device supports receiving video. + */ + public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 0x00000400; + + /** + * Remote device supports transmitting video. + */ + public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 0x00000800; + + /** + * Remote device supports bidirectional video calling. */ - public static final int CAPABILITY_VoWIFI = 0x00000800; + public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = + CAPABILITY_SUPPORTS_VT_REMOTE_RX | CAPABILITY_SUPPORTS_VT_REMOTE_TX; /** * Connection is able to be separated from its parent {@code Conference}, if any. @@ -148,10 +191,66 @@ public abstract class Connection implements IConferenceable { public static final int CAPABILITY_GENERIC_CONFERENCE = 0x00004000; /** + * Connection is using high definition audio. + * @hide + */ + public static final int CAPABILITY_HIGH_DEF_AUDIO = 0x00008000; + + /** + * Connection is using WIFI. + * @hide + */ + public static final int CAPABILITY_WIFI = 0x00010000; + + /** + * Indicates that the current device callback number should be shown. + * + * @hide + */ + public static final int CAPABILITY_SHOW_CALLBACK_NUMBER = 0x00020000; + + /** * Speed up audio setup for MT call. * @hide - */ - public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00008000; + */ + public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00040000; + + /** + * Call can be upgraded to a video call. + */ + public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 0x00080000; + + /** + * For video calls, indicates whether the outgoing video for the call can be paused using + * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. + */ + public static final int CAPABILITY_CAN_PAUSE_VIDEO = 0x00100000; + + /** + * For a conference, indicates the conference will not have child connections. + * <p> + * An example of a conference with child connections is a GSM conference call, where the radio + * retains connections to the individual participants of the conference. Another example is an + * IMS conference call where conference event package functionality is supported; in this case + * the conference server ensures the radio is aware of the participants in the conference, which + * are represented by child connections. + * <p> + * An example of a conference with no child connections is an IMS conference call with no + * conference event package support. Such a conference is represented by the radio as a single + * connection to the IMS conference server. + * <p> + * Indicating whether a conference has children or not is important to help user interfaces + * visually represent a conference. A conference with no children, for example, will have the + * conference connection shown in the list of calls on a Bluetooth device, where if the + * conference has children, only the children will be shown in the list of calls on a Bluetooth + * device. + * @hide + */ + public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 0x00200000; + + //********************************************************************************************** + // Next CAPABILITY value: 0x00400000 + //********************************************************************************************** // Flag controlling whether PII is emitted into the logs private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); @@ -224,23 +323,47 @@ public abstract class Connection implements IConferenceable { if (can(capabilities, CAPABILITY_MANAGE_CONFERENCE)) { builder.append(" CAPABILITY_MANAGE_CONFERENCE"); } - if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL)) { - builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL"); + if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_RX)) { + builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_RX"); + } + if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_TX)) { + builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_TX"); + } + if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) { + builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL"); + } + if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_RX)) { + builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_RX"); + } + if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_TX)) { + builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_TX"); } - if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE)) { - builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE"); + if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) { + builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL"); } if (can(capabilities, CAPABILITY_HIGH_DEF_AUDIO)) { builder.append(" CAPABILITY_HIGH_DEF_AUDIO"); } - if (can(capabilities, CAPABILITY_VoWIFI)) { - builder.append(" CAPABILITY_VoWIFI"); + if (can(capabilities, CAPABILITY_WIFI)) { + builder.append(" CAPABILITY_WIFI"); } if (can(capabilities, CAPABILITY_GENERIC_CONFERENCE)) { builder.append(" CAPABILITY_GENERIC_CONFERENCE"); } + if (can(capabilities, CAPABILITY_SHOW_CALLBACK_NUMBER)) { + builder.append(" CAPABILITY_SHOW_CALLBACK_NUMBER"); + } if (can(capabilities, CAPABILITY_SPEED_UP_MT_AUDIO)) { - builder.append(" CAPABILITY_SPEED_UP_IMS_MT_AUDIO"); + builder.append(" CAPABILITY_SPEED_UP_MT_AUDIO"); + } + if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) { + builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO"); + } + if (can(capabilities, CAPABILITY_CAN_PAUSE_VIDEO)) { + builder.append(" CAPABILITY_CAN_PAUSE_VIDEO"); + } + if (can(capabilities, CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) { + builder.append(" CAPABILITY_SINGLE_PARTY_CONFERENCE"); } builder.append("]"); return builder.toString(); @@ -264,68 +387,106 @@ public abstract class Connection implements IConferenceable { public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {} public void onStatusHintsChanged(Connection c, StatusHints statusHints) {} public void onConferenceablesChanged( - Connection c, List<IConferenceable> conferenceables) {} + Connection c, List<Conferenceable> conferenceables) {} public void onConferenceChanged(Connection c, Conference conference) {} /** @hide */ public void onConferenceParticipantsChanged(Connection c, List<ConferenceParticipant> participants) {} public void onConferenceStarted() {} + public void onConferenceMergeFailed(Connection c) {} + public void onExtrasChanged(Connection c, Bundle extras) {} } - /** @hide */ + /** + * Provides a means of controlling the video session associated with a {@link Connection}. + * <p> + * Implementations create a custom subclass of {@link VideoProvider} and the + * {@link ConnectionService} creates an instance sets it on the {@link Connection} using + * {@link Connection#setVideoProvider(VideoProvider)}. Any connection which supports video + * should set the {@link VideoProvider}. + * <p> + * The {@link VideoProvider} serves two primary purposes: it provides a means for Telecom and + * {@link InCallService} implementations to issue requests related to the video session; + * it provides a means for the {@link ConnectionService} to report events and information + * related to the video session to Telecom and the {@link InCallService} implementations. + * <p> + * {@link InCallService} implementations interact with the {@link VideoProvider} via + * {@link android.telecom.InCallService.VideoCall}. + */ public static abstract class VideoProvider { /** * Video is not being received (no protocol pause was issued). + * @see #handleCallSessionEvent(int) */ public static final int SESSION_EVENT_RX_PAUSE = 1; /** - * Video reception has resumed after a SESSION_EVENT_RX_PAUSE. + * Video reception has resumed after a {@link #SESSION_EVENT_RX_PAUSE}. + * @see #handleCallSessionEvent(int) */ 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. + * @see #handleCallSessionEvent(int) */ 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. + * @see #handleCallSessionEvent(int) */ 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. + * A camera failure has occurred for the selected camera. The {@link InCallService} can use + * this as a cue to inform the user the camera is not available. + * @see #handleCallSessionEvent(int) */ 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. + * Issued after {@link #SESSION_EVENT_CAMERA_FAILURE} when the camera is once again ready + * for operation. The {@link InCallService} can use this as a cue to inform the user that + * the camera has become available again. + * @see #handleCallSessionEvent(int) */ public static final int SESSION_EVENT_CAMERA_READY = 6; /** * Session modify request was successful. + * @see #receiveSessionModifyResponse(int, VideoProfile, VideoProfile) */ public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; /** * Session modify request failed. + * @see #receiveSessionModifyResponse(int, VideoProfile, VideoProfile) */ public static final int SESSION_MODIFY_REQUEST_FAIL = 2; /** * Session modify request ignored due to invalid parameters. + * @see #receiveSessionModifyResponse(int, VideoProfile, VideoProfile) */ public static final int SESSION_MODIFY_REQUEST_INVALID = 3; - private static final int MSG_SET_VIDEO_CALLBACK = 1; + /** + * Session modify request timed out. + * @see #receiveSessionModifyResponse(int, VideoProfile, VideoProfile) + */ + public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; + + /** + * Session modify request rejected by remote user. + * @see #receiveSessionModifyResponse(int, VideoProfile, VideoProfile) + */ + public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; + + private static final int MSG_ADD_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; @@ -336,22 +497,63 @@ public abstract class Connection implements IConferenceable { private static final int MSG_REQUEST_CAMERA_CAPABILITIES = 9; private static final int MSG_REQUEST_CONNECTION_DATA_USAGE = 10; private static final int MSG_SET_PAUSE_IMAGE = 11; + private static final int MSG_REMOVE_VIDEO_CALLBACK = 12; - private final VideoProvider.VideoProviderHandler - mMessageHandler = new VideoProvider.VideoProviderHandler(); + private VideoProvider.VideoProviderHandler mMessageHandler; private final VideoProvider.VideoProviderBinder mBinder; - private IVideoCallback mVideoCallback; + + /** + * Stores a list of the video callbacks, keyed by IBinder. + * + * 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 ConcurrentHashMap<IBinder, IVideoCallback> mVideoCallbacks = + new ConcurrentHashMap<IBinder, IVideoCallback>(8, 0.9f, 1); /** * Default handler used to consolidate binder method calls onto a single thread. */ private final class VideoProviderHandler extends Handler { + public VideoProviderHandler() { + super(); + } + + public VideoProviderHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_SET_VIDEO_CALLBACK: - mVideoCallback = IVideoCallback.Stub.asInterface((IBinder) msg.obj); + case MSG_ADD_VIDEO_CALLBACK: { + IBinder binder = (IBinder) msg.obj; + IVideoCallback callback = IVideoCallback.Stub + .asInterface((IBinder) msg.obj); + if (callback == null) { + Log.w(this, "addVideoProvider - skipped; callback is null."); + break; + } + + if (mVideoCallbacks.containsKey(binder)) { + Log.i(this, "addVideoProvider - skipped; already present."); + break; + } + mVideoCallbacks.put(binder, callback); + break; + } + case MSG_REMOVE_VIDEO_CALLBACK: { + IBinder binder = (IBinder) msg.obj; + IVideoCallback callback = IVideoCallback.Stub + .asInterface((IBinder) msg.obj); + if (!mVideoCallbacks.containsKey(binder)) { + Log.i(this, "removeVideoProvider - skipped; not present."); + break; + } + mVideoCallbacks.remove(binder); break; + } case MSG_SET_CAMERA: onSetCamera((String) msg.obj); break; @@ -367,9 +569,16 @@ public abstract class Connection implements IConferenceable { case MSG_SET_ZOOM: onSetZoom((Float) msg.obj); break; - case MSG_SEND_SESSION_MODIFY_REQUEST: - onSendSessionModifyRequest((VideoProfile) msg.obj); + case MSG_SEND_SESSION_MODIFY_REQUEST: { + SomeArgs args = (SomeArgs) msg.obj; + try { + onSendSessionModifyRequest((VideoProfile) args.arg1, + (VideoProfile) args.arg2); + } finally { + args.recycle(); + } break; + } case MSG_SEND_SESSION_MODIFY_RESPONSE: onSendSessionModifyResponse((VideoProfile) msg.obj); break; @@ -380,7 +589,7 @@ public abstract class Connection implements IConferenceable { onRequestConnectionDataUsage(); break; case MSG_SET_PAUSE_IMAGE: - onSetPauseImage((String) msg.obj); + onSetPauseImage((Uri) msg.obj); break; default: break; @@ -392,9 +601,14 @@ public abstract class Connection implements IConferenceable { * IVideoProvider stub implementation. */ private final class VideoProviderBinder extends IVideoProvider.Stub { - public void setVideoCallback(IBinder videoCallbackBinder) { + public void addVideoCallback(IBinder videoCallbackBinder) { mMessageHandler.obtainMessage( - MSG_SET_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget(); + MSG_ADD_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget(); + } + + public void removeVideoCallback(IBinder videoCallbackBinder) { + mMessageHandler.obtainMessage( + MSG_REMOVE_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget(); } public void setCamera(String cameraId) { @@ -410,16 +624,19 @@ public abstract class Connection implements IConferenceable { } public void setDeviceOrientation(int rotation) { - mMessageHandler.obtainMessage(MSG_SET_DEVICE_ORIENTATION, rotation).sendToTarget(); + mMessageHandler.obtainMessage( + MSG_SET_DEVICE_ORIENTATION, rotation, 0).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 sendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = fromProfile; + args.arg2 = toProfile; + mMessageHandler.obtainMessage(MSG_SEND_SESSION_MODIFY_REQUEST, args).sendToTarget(); } public void sendSessionModifyResponse(VideoProfile responseProfile) { @@ -435,13 +652,25 @@ public abstract class Connection implements IConferenceable { mMessageHandler.obtainMessage(MSG_REQUEST_CONNECTION_DATA_USAGE).sendToTarget(); } - public void setPauseImage(String uri) { + public void setPauseImage(Uri uri) { mMessageHandler.obtainMessage(MSG_SET_PAUSE_IMAGE, uri).sendToTarget(); } } public VideoProvider() { mBinder = new VideoProvider.VideoProviderBinder(); + mMessageHandler = new VideoProvider.VideoProviderHandler(Looper.getMainLooper()); + } + + /** + * Creates an instance of the {@link VideoProvider}, specifying the looper to use. + * + * @param looper The looper. + * @hide + */ + public VideoProvider(Looper looper) { + mBinder = new VideoProvider.VideoProviderBinder(); + mMessageHandler = new VideoProvider.VideoProviderHandler(looper); } /** @@ -453,9 +682,17 @@ public abstract class Connection implements IConferenceable { } /** - * Sets the camera to be used for video recording in a video connection. + * Sets the camera to be used for the outgoing video. + * <p> + * The {@link VideoProvider} should respond by communicating the capabilities of the chosen + * camera via + * {@link VideoProvider#changeCameraCapabilities(VideoProfile.CameraCapabilities)}. + * <p> + * Sent from the {@link InCallService} via + * {@link InCallService.VideoCall#setCamera(String)}. * - * @param cameraId The id of the camera. + * @param cameraId The id of the camera (use ids as reported by + * {@link CameraManager#getCameraIdList()}). */ public abstract void onSetCamera(String cameraId); @@ -463,21 +700,30 @@ public abstract class Connection implements IConferenceable { * 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. + * <p> + * Sent from the {@link InCallService} via + * {@link InCallService.VideoCall#setPreviewSurface(Surface)}. * - * @param surface The surface. + * @param surface The {@link Surface}. */ public abstract void onSetPreviewSurface(Surface surface); /** * Sets the surface to be used for displaying the video received from the remote device. + * <p> + * Sent from the {@link InCallService} via + * {@link InCallService.VideoCall#setDisplaySurface(Surface)}. * - * @param surface The surface. + * @param surface The {@link 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. + * <p> + * Sent from the {@link InCallService} via + * {@link InCallService.VideoCall#setDeviceOrientation(int)}. * * @param rotation The device orientation, in degrees. */ @@ -485,144 +731,281 @@ public abstract class Connection implements IConferenceable { /** * Sets camera zoom ratio. + * <p> + * Sent from the {@link InCallService} via {@link InCallService.VideoCall#setZoom(float)}. * * @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 connection from audio to video, - * downgrade connection from video to audio, pause video. + * Issues a request to modify the properties of the current video session. + * <p> + * Example scenarios include: requesting an audio-only call to be upgraded to a + * bi-directional video call, turning on or off the user's camera, sending a pause signal + * when the {@link InCallService} is no longer the foreground application. + * <p> + * If the {@link VideoProvider} determines a request to be invalid, it should call + * {@link #receiveSessionModifyResponse(int, VideoProfile, VideoProfile)} to report the + * invalid request back to the {@link InCallService}. + * <p> + * Where a request requires confirmation from the user of the peer device, the + * {@link VideoProvider} must communicate the request to the peer device and handle the + * user's response. {@link #receiveSessionModifyResponse(int, VideoProfile, VideoProfile)} + * is used to inform the {@link InCallService} of the result of the request. + * <p> + * Sent from the {@link InCallService} via + * {@link InCallService.VideoCall#sendSessionModifyRequest(VideoProfile)}. * - * @param requestProfile The requested connection video properties. + * @param fromProfile The video profile prior to the request. + * @param toProfile The video profile with the requested changes made. */ - public abstract void onSendSessionModifyRequest(VideoProfile requestProfile); + public abstract void onSendSessionModifyRequest(VideoProfile fromProfile, + VideoProfile toProfile); - /**te - * Provides a response to a request to change the current connection session video - * properties. - * This is in response to a request the InCall UI has received via the InCall UI. + /** + * Provides a response to a request to change the current video session properties. + * <p> + * For example, if the peer requests and upgrade from an audio-only call to a bi-directional + * video call, could decline the request and keep the call as audio-only. + * In such a scenario, the {@code responseProfile} would have a video state of + * {@link VideoProfile#STATE_AUDIO_ONLY}. If the user had decided to accept the request, + * the video state would be {@link VideoProfile#STATE_BIDIRECTIONAL}. + * <p> + * Sent from the {@link InCallService} via + * {@link InCallService.VideoCall#sendSessionModifyResponse(VideoProfile)} in response to + * a {@link InCallService.VideoCall.Callback#onSessionModifyRequestReceived(VideoProfile)} + * callback. * - * @param responseProfile The response connection video properties. + * @param responseProfile The response video profile. */ 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. + * Issues a request to the {@link VideoProvider} to retrieve the camera capabilities. + * <p> + * The {@link VideoProvider} should respond by communicating the capabilities of the chosen + * camera via + * {@link VideoProvider#changeCameraCapabilities(VideoProfile.CameraCapabilities)}. + * <p> + * Sent from the {@link InCallService} via + * {@link InCallService.VideoCall#requestCameraCapabilities()}. */ public abstract void onRequestCameraCapabilities(); /** - * Issues a request to the video telephony framework to retrieve the cumulative data usage - * for the current connection. Data usage is reported back to the caller via the - * InCall UI. + * Issues a request to the {@link VideoProvider} to retrieve the current data usage for the + * video component of the current {@link Connection}. + * <p> + * The {@link VideoProvider} should respond by communicating current data usage, in bytes, + * via {@link VideoProvider#setCallDataUsage(long)}. + * <p> + * Sent from the {@link InCallService} via + * {@link InCallService.VideoCall#requestCallDataUsage()}. */ public abstract void onRequestConnectionDataUsage(); /** - * Provides the video telephony framework with the URI of an image to be displayed to remote - * devices when the video signal is paused. + * Provides the {@link VideoProvider} with the {@link Uri} of an image to be displayed to + * the peer device when the video signal is paused. + * <p> + * Sent from the {@link InCallService} via + * {@link InCallService.VideoCall#setPauseImage(Uri)}. * * @param uri URI of image to display. */ - public abstract void onSetPauseImage(String uri); + public abstract void onSetPauseImage(Uri uri); /** - * Invokes callback method defined in In-Call UI. + * Used to inform listening {@link InCallService} implementations when the + * {@link VideoProvider} receives a session modification request. + * <p> + * Received by the {@link InCallService} via + * {@link InCallService.VideoCall.Callback#onSessionModifyRequestReceived(VideoProfile)}, * - * @param videoProfile The requested video connection profile. + * @param videoProfile The requested video profile. + * @see #onSendSessionModifyRequest(VideoProfile, VideoProfile) */ public void receiveSessionModifyRequest(VideoProfile videoProfile) { - if (mVideoCallback != null) { - try { - mVideoCallback.receiveSessionModifyRequest(videoProfile); - } catch (RemoteException ignored) { + if (mVideoCallbacks != null) { + for (IVideoCallback callback : mVideoCallbacks.values()) { + try { + callback.receiveSessionModifyRequest(videoProfile); + } catch (RemoteException ignored) { + Log.w(this, "receiveSessionModifyRequest callback failed", ignored); + } } } } /** - * Invokes callback method defined in In-Call UI. + * Used to inform listening {@link InCallService} implementations when the + * {@link VideoProvider} receives a response to a session modification request. + * <p> + * Received by the {@link InCallService} via + * {@link InCallService.VideoCall.Callback#onSessionModifyResponseReceived(int, + * VideoProfile, VideoProfile)}. * * @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. + * {@link VideoProvider#SESSION_MODIFY_REQUEST_INVALID}, + * {@link VideoProvider#SESSION_MODIFY_REQUEST_TIMED_OUT}, + * {@link VideoProvider#SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE} + * @param requestedProfile The original request which was sent to the peer device. + * @param responseProfile The actual profile changes agreed to by the peer device. + * @see #onSendSessionModifyRequest(VideoProfile, VideoProfile) */ public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile, VideoProfile responseProfile) { - if (mVideoCallback != null) { - try { - mVideoCallback.receiveSessionModifyResponse( - status, requestedProfile, responseProfile); - } catch (RemoteException ignored) { + if (mVideoCallbacks != null) { + for (IVideoCallback callback : mVideoCallbacks.values()) { + try { + callback.receiveSessionModifyResponse(status, requestedProfile, + responseProfile); + } catch (RemoteException ignored) { + Log.w(this, "receiveSessionModifyResponse callback failed", ignored); + } } } } /** - * Invokes callback method defined in In-Call UI. + * Used to inform listening {@link InCallService} implementations when the + * {@link VideoProvider} reports a call session event. + * <p> + * Received by the {@link InCallService} via + * {@link InCallService.VideoCall.Callback#onCallSessionEvent(int)}. * - * 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. + * @param event The event. 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}, + * {@link VideoProvider#SESSION_EVENT_CAMERA_FAILURE}, + * {@link VideoProvider#SESSION_EVENT_CAMERA_READY}. */ public void handleCallSessionEvent(int event) { - if (mVideoCallback != null) { - try { - mVideoCallback.handleCallSessionEvent(event); - } catch (RemoteException ignored) { + if (mVideoCallbacks != null) { + for (IVideoCallback callback : mVideoCallbacks.values()) { + try { + callback.handleCallSessionEvent(event); + } catch (RemoteException ignored) { + Log.w(this, "handleCallSessionEvent callback failed", ignored); + } } } } /** - * Invokes callback method defined in In-Call UI. + * Used to inform listening {@link InCallService} implementations when the dimensions of the + * peer's video have changed. + * <p> + * This could occur if, for example, the peer rotates their device, changing the aspect + * ratio of the video, or if the user switches between the back and front cameras. + * <p> + * Received by the {@link InCallService} via + * {@link InCallService.VideoCall.Callback#onPeerDimensionsChanged(int, int)}. * * @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) { + if (mVideoCallbacks != null) { + for (IVideoCallback callback : mVideoCallbacks.values()) { + try { + callback.changePeerDimensions(width, height); + } catch (RemoteException ignored) { + Log.w(this, "changePeerDimensions callback failed", ignored); + } + } + } + } + + /** + * Used to inform listening {@link InCallService} implementations when the data usage of the + * video associated with the current {@link Connection} has changed. + * <p> + * This could be in response to a preview request via + * {@link #onRequestConnectionDataUsage()}, or as a periodic update by the + * {@link VideoProvider}. Where periodic updates of data usage are provided, they should be + * provided at most for every 1 MB of data transferred and no more than once every 10 sec. + * <p> + * Received by the {@link InCallService} via + * {@link InCallService.VideoCall.Callback#onCallDataUsageChanged(long)}. + * + * @param dataUsage The updated data usage (in bytes). Reported as the cumulative bytes + * used since the start of the call. + */ + public void setCallDataUsage(long dataUsage) { + if (mVideoCallbacks != null) { + for (IVideoCallback callback : mVideoCallbacks.values()) { + try { + callback.changeCallDataUsage(dataUsage); + } catch (RemoteException ignored) { + Log.w(this, "setCallDataUsage callback failed", ignored); + } } } } /** - * Invokes callback method defined in In-Call UI. + * @see #setCallDataUsage(long) + * + * @param dataUsage The updated data usage (in byes). + * @deprecated - Use {@link #setCallDataUsage(long)} instead. + * @hide + */ + public void changeCallDataUsage(long dataUsage) { + setCallDataUsage(dataUsage); + } + + /** + * Used to inform listening {@link InCallService} implementations when the capabilities of + * the current camera have changed. + * <p> + * The {@link VideoProvider} should call this in response to + * {@link VideoProvider#onRequestCameraCapabilities()}, or when the current camera is + * changed via {@link VideoProvider#onSetCamera(String)}. + * <p> + * Received by the {@link InCallService} via + * {@link InCallService.VideoCall.Callback#onCameraCapabilitiesChanged( + * VideoProfile.CameraCapabilities)}. * - * @param dataUsage The updated data usage. + * @param cameraCapabilities The new camera capabilities. */ - public void changeCallDataUsage(int dataUsage) { - if (mVideoCallback != null) { - try { - mVideoCallback.changeCallDataUsage(dataUsage); - } catch (RemoteException ignored) { + public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) { + if (mVideoCallbacks != null) { + for (IVideoCallback callback : mVideoCallbacks.values()) { + try { + callback.changeCameraCapabilities(cameraCapabilities); + } catch (RemoteException ignored) { + Log.w(this, "changeCameraCapabilities callback failed", ignored); + } } } } /** - * Invokes callback method defined in In-Call UI. + * Used to inform listening {@link InCallService} implementations when the video quality + * of the call has changed. + * <p> + * Received by the {@link InCallService} via + * {@link InCallService.VideoCall.Callback#onVideoQualityChanged(int)}. * - * @param cameraCapabilities The changed camera capabilities. + * @param videoQuality The updated video quality. Valid values: + * {@link VideoProfile#QUALITY_HIGH}, + * {@link VideoProfile#QUALITY_MEDIUM}, + * {@link VideoProfile#QUALITY_LOW}, + * {@link VideoProfile#QUALITY_DEFAULT}. */ - public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) { - if (mVideoCallback != null) { - try { - mVideoCallback.changeCameraCapabilities(cameraCapabilities); - } catch (RemoteException ignored) { + public void changeVideoQuality(int videoQuality) { + if (mVideoCallbacks != null) { + for (IVideoCallback callback : mVideoCallbacks.values()) { + try { + callback.changeVideoQuality(videoQuality); + } catch (RemoteException ignored) { + Log.w(this, "changeVideoQuality callback failed", ignored); + } } } } @@ -653,12 +1036,12 @@ public abstract class Connection implements IConferenceable { */ private final Set<Listener> mListeners = Collections.newSetFromMap( new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1)); - private final List<IConferenceable> mConferenceables = new ArrayList<>(); - private final List<IConferenceable> mUnmodifiableConferenceables = + private final List<Conferenceable> mConferenceables = new ArrayList<>(); + private final List<Conferenceable> mUnmodifiableConferenceables = Collections.unmodifiableList(mConferenceables); private int mState = STATE_NEW; - private AudioState mAudioState; + private CallAudioState mCallAudioState; private Uri mAddress; private int mAddressPresentation; private String mCallerDisplayName; @@ -672,6 +1055,7 @@ public abstract class Connection implements IConferenceable { private DisconnectCause mDisconnectCause; private Conference mConference; private ConnectionService mConnectionService; + private Bundle mExtras; /** * Create a new Connection. @@ -717,10 +1101,10 @@ public abstract class Connection implements IConferenceable { /** * Returns the video state of the connection. - * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY}, - * {@link VideoProfile.VideoState#BIDIRECTIONAL}, - * {@link VideoProfile.VideoState#TX_ENABLED}, - * {@link VideoProfile.VideoState#RX_ENABLED}. + * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, + * {@link VideoProfile#STATE_BIDIRECTIONAL}, + * {@link VideoProfile#STATE_TX_ENABLED}, + * {@link VideoProfile#STATE_RX_ENABLED}. * * @return The video state of the connection. * @hide @@ -733,9 +1117,25 @@ public abstract class Connection implements IConferenceable { * @return The audio state of the connection, 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. + * @deprecated Use {@link #getCallAudioState()} instead. + * @hide */ + @SystemApi + @Deprecated public final AudioState getAudioState() { - return mAudioState; + if (mCallAudioState == null) { + return null; + } + return new AudioState(mCallAudioState); + } + + /** + * @return The audio state of the connection, 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 CallAudioState getCallAudioState() { + return mCallAudioState; } /** @@ -769,6 +1169,13 @@ public abstract class Connection implements IConferenceable { } /** + * @return The extras associated with this connection. + */ + public final Bundle getExtras() { + return mExtras; + } + + /** * Assign a listener to be notified of state changes. * * @param l A listener. @@ -809,11 +1216,12 @@ public abstract class Connection implements IConferenceable { * @param state The new audio state. * @hide */ - final void setAudioState(AudioState state) { + final void setCallAudioState(CallAudioState state) { checkImmutable(); Log.d(this, "setAudioState %s", state); - mAudioState = state; - onAudioStateChanged(state); + mCallAudioState = state; + onAudioStateChanged(getAudioState()); + onCallAudioStateChanged(state); } /** @@ -849,11 +1257,6 @@ public abstract class Connection implements IConferenceable { return mConnectionCapabilities; } - /** @hide */ - @SystemApi @Deprecated public final int getCallCapabilities() { - return getConnectionCapabilities(); - } - /** * Sets the value of the {@link #getAddress()} property. * @@ -890,13 +1293,12 @@ public abstract class Connection implements IConferenceable { /** * 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}. + * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, + * {@link VideoProfile#STATE_BIDIRECTIONAL}, + * {@link VideoProfile#STATE_TX_ENABLED}, + * {@link VideoProfile#STATE_RX_ENABLED}. * * @param videoState The new video state. - * @hide */ public final void setVideoState(int videoState) { checkImmutable(); @@ -960,7 +1362,6 @@ public abstract class Connection implements IConferenceable { /** * Sets the video connection provider. * @param videoProvider The video provider. - * @hide */ public final void setVideoProvider(VideoProvider videoProvider) { checkImmutable(); @@ -970,7 +1371,6 @@ public abstract class Connection implements IConferenceable { } } - /** @hide */ public final VideoProvider getVideoProvider() { return mVideoProvider; } @@ -1011,14 +1411,11 @@ public abstract class Connection implements IConferenceable { /** * Informs listeners that this {@code Connection} has processed a character in the post-dial * started state. This is done when (a) the {@code Connection} is issuing a DTMF sequence; - * (b) it has encountered a "wait" character; and (c) it wishes to signal Telecom to play - * the corresponding DTMF tone locally. + * and (b) it wishes to signal Telecom to play the corresponding DTMF tone locally. * * @param nextChar The DTMF character that was just processed by the {@code Connection}. - * - * @hide */ - public final void setNextPostDialWaitChar(char nextChar) { + public final void setNextPostDialChar(char nextChar) { checkImmutable(); for (Listener l : mListeners) { l.onPostDialChar(this, nextChar); @@ -1041,11 +1438,6 @@ public abstract class Connection implements IConferenceable { } } - /** @hide */ - @SystemApi @Deprecated public final void setCallCapabilities(int connectionCapabilities) { - setConnectionCapabilities(connectionCapabilities); - } - /** * Sets the connection's capabilities as a bit mask of the {@code CAPABILITY_*} constants. * @@ -1121,9 +1513,9 @@ public abstract class Connection implements IConferenceable { * * @param conferenceables The conferenceables. */ - public final void setConferenceables(List<IConferenceable> conferenceables) { + public final void setConferenceables(List<Conferenceable> conferenceables) { clearConferenceableList(); - for (IConferenceable c : conferenceables) { + for (Conferenceable c : conferenceables) { // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a // small amount of items here. if (!mConferenceables.contains(c)) { @@ -1143,7 +1535,7 @@ public abstract class Connection implements IConferenceable { /** * Returns the connections or conferences with which this connection can be conferenced. */ - public final List<IConferenceable> getConferenceables() { + public final List<Conferenceable> getConferenceables() { return mUnmodifiableConferenceables; } @@ -1213,13 +1605,39 @@ public abstract class Connection implements IConferenceable { } /** + * Set some extras that can be associated with this {@code Connection}. No assumptions should + * be made as to how an In-Call UI or service will handle these extras. + * Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts. + * + * @param extras The extras associated with this {@code Connection}. + */ + public final void setExtras(@Nullable Bundle extras) { + checkImmutable(); + mExtras = extras; + for (Listener l : mListeners) { + l.onExtrasChanged(this, extras); + } + } + + /** * Notifies this Connection that the {@link #getAudioState()} property has a new value. * * @param state The new connection audio state. + * @deprecated Use {@link #onCallAudioStateChanged(CallAudioState)} instead. + * @hide */ + @SystemApi + @Deprecated public void onAudioStateChanged(AudioState state) {} /** + * Notifies this Connection that the {@link #getCallAudioState()} property has a new value. + * + * @param state The new connection audio state. + */ + public void onCallAudioStateChanged(CallAudioState state) {} + + /** * Notifies this Connection of an internal state change. This method is called after the * state is changed. * @@ -1278,7 +1696,6 @@ public abstract class Connection implements IConferenceable { * a request to accept. * * @param videoState The video state in which to answer the connection. - * @hide */ public void onAnswer(int videoState) {} @@ -1287,7 +1704,7 @@ public abstract class Connection implements IConferenceable { * a request to accept. */ public void onAnswer() { - onAnswer(VideoProfile.VideoState.AUDIO_ONLY); + onAnswer(VideoProfile.STATE_AUDIO_ONLY); } /** @@ -1408,7 +1825,7 @@ public abstract class Connection implements IConferenceable { } private final void clearConferenceableList() { - for (IConferenceable c : mConferenceables) { + for (Conferenceable c : mConferenceables) { if (c instanceof Connection) { Connection connection = (Connection) c; connection.removeConnectionListener(mConnectionDeathListener); @@ -1421,6 +1838,17 @@ public abstract class Connection implements IConferenceable { } /** + * Notifies listeners that the merge request failed. + * + * @hide + */ + protected final void notifyConferenceMergeFailed() { + for (Listener l : mListeners) { + l.onConferenceMergeFailed(this); + } + } + + /** * Notifies listeners of a change to conference participant(s). * * @param conferenceParticipants The participants. @@ -1435,6 +1863,7 @@ public abstract class Connection implements IConferenceable { /** * Notifies listeners that a conference call has been started. + * @hide */ protected void notifyConferenceStarted() { for (Listener l : mListeners) { diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java index f691c17..6863214 100644 --- a/telecomm/java/android/telecom/ConnectionRequest.java +++ b/telecomm/java/android/telecom/ConnectionRequest.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; @@ -25,9 +24,7 @@ import android.os.Parcelable; /** * Simple data container encapsulating a request to some entity to * create a new {@link Connection}. - * @hide */ -@SystemApi public final class ConnectionRequest implements Parcelable { // TODO: Token to limit recursive invocations @@ -45,7 +42,7 @@ public final class ConnectionRequest implements Parcelable { PhoneAccountHandle accountHandle, Uri handle, Bundle extras) { - this(accountHandle, handle, extras, VideoProfile.VideoState.AUDIO_ONLY); + this(accountHandle, handle, extras, VideoProfile.STATE_AUDIO_ONLY); } /** @@ -53,7 +50,6 @@ public final class ConnectionRequest implements Parcelable { * @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, @@ -92,13 +88,12 @@ public final class ConnectionRequest implements Parcelable { /** * 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}. + * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, + * {@link VideoProfile#STATE_BIDIRECTIONAL}, + * {@link VideoProfile#STATE_TX_ENABLED}, + * {@link VideoProfile#STATE_RX_ENABLED}. * * @return The video state for the connection. - * @hide */ public int getVideoState() { return mVideoState; diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index dfdc3e1..d2e7a74 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -17,11 +17,11 @@ package android.telecom; import android.annotation.SdkConstant; -import android.annotation.SystemApi; import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -41,8 +41,8 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** - * {@code ConnectionService} is an abstract service that should be implemented by any app which can - * make phone calls and want those calls to be integrated into the built-in phone app. + * An abstract service that should be implemented by any apps which can make phone calls (VoIP or + * otherwise) and want those calls to be integrated into the built-in phone app. * Once implemented, the {@code ConnectionService} needs two additional steps before it will be * integrated into the phone app: * <p> @@ -51,7 +51,7 @@ import java.util.concurrent.ConcurrentHashMap; * <pre> * <service android:name="com.example.package.MyConnectionService" * android:label="@string/some_label_for_my_connection_service" - * android:permission="android.permission.BIND_CONNECTION_SERVICE"> + * android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"> * <intent-filter> * <action android:name="android.telecom.ConnectionService" /> * </intent-filter> @@ -62,7 +62,7 @@ import java.util.concurrent.ConcurrentHashMap; * <br/> * See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information. * <p> - * Once registered and enabled by the user in the dialer settings, telecom will bind to a + * Once registered and enabled by the user in the phone app settings, telecom will bind to a * {@code ConnectionService} implementation when it wants that {@code ConnectionService} to place * a call or the service has indicated that is has an incoming call through * {@link TelecomManager#addNewIncomingCall}. The {@code ConnectionService} can then expect a call @@ -72,9 +72,7 @@ import java.util.concurrent.ConcurrentHashMap; * receives call-commands such as answer, reject, hold and disconnect. * <p> * When there are no more live calls, telecom will unbind from the {@code ConnectionService}. - * @hide */ -@SystemApi public abstract class ConnectionService extends Service { /** * The {@link Intent} that must be declared as handled by the service. @@ -93,7 +91,7 @@ public abstract class ConnectionService extends Service { 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_ON_CALL_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; @@ -150,7 +148,6 @@ public abstract class ConnectionService extends Service { } @Override - /** @hide */ public void answerVideo(String callId, int videoState) { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; @@ -184,11 +181,11 @@ public abstract class ConnectionService extends Service { } @Override - public void onAudioStateChanged(String callId, AudioState audioState) { + public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; - args.arg2 = audioState; - mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget(); + args.arg2 = callAudioState; + mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget(); } @Override @@ -308,12 +305,12 @@ public abstract class ConnectionService extends Service { case MSG_UNHOLD: unhold((String) msg.obj); break; - case MSG_ON_AUDIO_STATE_CHANGED: { + case MSG_ON_CALL_AUDIO_STATE_CHANGED: { SomeArgs args = (SomeArgs) msg.obj; try { String callId = (String) args.arg1; - AudioState audioState = (AudioState) args.arg2; - onAudioStateChanged(callId, audioState); + CallAudioState audioState = (CallAudioState) args.arg2; + onCallAudioStateChanged(callId, new CallAudioState(audioState)); } finally { args.recycle(); } @@ -415,6 +412,33 @@ public abstract class ConnectionService extends Service { Connection.capabilitiesToString(connectionCapabilities)); mAdapter.setConnectionCapabilities(id, connectionCapabilities); } + + @Override + public void onVideoStateChanged(Conference c, int videoState) { + String id = mIdByConference.get(c); + Log.d(this, "onVideoStateChanged set video state %d", videoState); + mAdapter.setVideoState(id, videoState); + } + + @Override + public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) { + String id = mIdByConference.get(c); + Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c, + videoProvider); + mAdapter.setVideoProvider(id, videoProvider); + } + + @Override + public void onStatusHintsChanged(Conference conference, StatusHints statusHints) { + String id = mIdByConference.get(conference); + mAdapter.setStatusHints(id, statusHints); + } + + @Override + public void onExtrasChanged(Conference conference, Bundle extras) { + String id = mIdByConference.get(conference); + mAdapter.setExtras(id, extras); + } }; private final Connection.Listener mConnectionListener = new Connection.Listener() { @@ -508,6 +532,8 @@ public abstract class ConnectionService extends Service { @Override public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) { String id = mIdByConnection.get(c); + Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c, + videoProvider); mAdapter.setVideoProvider(id, videoProvider); } @@ -525,7 +551,7 @@ public abstract class ConnectionService extends Service { @Override public void onConferenceablesChanged( - Connection connection, List<IConferenceable> conferenceables) { + Connection connection, List<Conferenceable> conferenceables) { mAdapter.setConferenceableConnections( mIdByConnection.get(connection), createIdList(conferenceables)); @@ -542,6 +568,22 @@ public abstract class ConnectionService extends Service { mAdapter.setIsConferenced(id, conferenceId); } } + + @Override + public void onConferenceMergeFailed(Connection connection) { + String id = mIdByConnection.get(connection); + if (id != null) { + mAdapter.onConferenceMergeFailed(id); + } + } + + @Override + public void onExtrasChanged(Connection connection, Bundle extras) { + String id = mIdByConnection.get(connection); + if (id != null) { + mAdapter.setExtras(id, extras); + } + } }; /** {@inheritDoc} */ @@ -611,7 +653,8 @@ public abstract class ConnectionService extends Service { connection.getAudioModeIsVoip(), connection.getStatusHints(), connection.getDisconnectCause(), - createIdList(connection.getConferenceables()))); + createIdList(connection.getConferenceables()), + connection.getExtras())); } private void abort(String callId) { @@ -661,12 +704,14 @@ public abstract class ConnectionService extends Service { } } - private void onAudioStateChanged(String callId, AudioState audioState) { - Log.d(this, "onAudioStateChanged %s %s", callId, audioState); + private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) { + Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState); if (mConnectionById.containsKey(callId)) { - findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState); + findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState( + callAudioState); } else { - findConferenceForAction(callId, "onAudioStateChanged").setAudioState(audioState); + findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState( + callAudioState); } } @@ -871,6 +916,8 @@ public abstract class ConnectionService extends Service { * @param conference The new conference object. */ public final void addConference(Conference conference) { + Log.d(this, "addConference: conference=%s", conference); + String id = addConferenceInternal(conference); if (id != null) { List<String> connectionIds = new ArrayList<>(2); @@ -884,8 +931,16 @@ public abstract class ConnectionService extends Service { conference.getState(), conference.getConnectionCapabilities(), connectionIds, - conference.getConnectTimeMillis()); + conference.getVideoProvider() == null ? + null : conference.getVideoProvider().getInterface(), + conference.getVideoState(), + conference.getConnectTimeMillis(), + conference.getStatusHints(), + conference.getExtras()); + mAdapter.addConferenceCall(id, parcelableConference); + mAdapter.setVideoProvider(id, conference.getVideoProvider()); + mAdapter.setVideoState(id, conference.getVideoState()); // Go through any child calls and set the parent. for (Connection connection : conference.getConnections()) { @@ -926,7 +981,8 @@ public abstract class ConnectionService extends Service { connection.getAudioModeIsVoip(), connection.getStatusHints(), connection.getDisconnectCause(), - emptyList); + emptyList, + connection.getExtras()); mAdapter.addExistingConnection(id, parcelableConnection); } } @@ -1146,14 +1202,14 @@ public abstract class ConnectionService extends Service { /** * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of - * {@link IConferenceable}s passed in. + * {@link Conferenceable}s passed in. * - * @param conferenceables The {@link IConferenceable} connections and conferences. + * @param conferenceables The {@link Conferenceable} connections and conferences. * @return List of string conference and call Ids. */ - private List<String> createIdList(List<IConferenceable> conferenceables) { + private List<String> createIdList(List<Conferenceable> conferenceables) { List<String> ids = new ArrayList<>(); - for (IConferenceable c : conferenceables) { + for (Conferenceable c : conferenceables) { // Only allow Connection and Conference conferenceables. if (c instanceof Connection) { Connection connection = (Connection) c; diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java index d026a28..4562514 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java @@ -17,6 +17,7 @@ package android.telecom; import android.net.Uri; +import android.os.Bundle; import android.os.IBinder.DeathRecipient; import android.os.RemoteException; @@ -47,6 +48,12 @@ final class ConnectionServiceAdapter implements DeathRecipient { } void addAdapter(IConnectionServiceAdapter adapter) { + for (IConnectionServiceAdapter it : mAdapters) { + if (it.asBinder() == adapter.asBinder()) { + Log.w(this, "Ignoring duplicate adapter addition."); + return; + } + } if (mAdapters.add(adapter)) { try { adapter.asBinder().linkToDeath(this, 0); @@ -57,8 +64,13 @@ final class ConnectionServiceAdapter implements DeathRecipient { } void removeAdapter(IConnectionServiceAdapter adapter) { - if (adapter != null && mAdapters.remove(adapter)) { - adapter.asBinder().unlinkToDeath(this, 0); + if (adapter != null) { + for (IConnectionServiceAdapter it : mAdapters) { + if (it.asBinder() == adapter.asBinder() && mAdapters.remove(it)) { + adapter.asBinder().unlinkToDeath(this, 0); + break; + } + } } } @@ -203,6 +215,21 @@ final class ConnectionServiceAdapter implements DeathRecipient { } /** + * Indicates that the merge request on this call has failed. + * + * @param callId The unique ID of the call being conferenced. + */ + void onConferenceMergeFailed(String callId) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + Log.d(this, "merge failed for call %s", callId); + adapter.setConferenceMergeFailed(callId); + } catch (RemoteException ignored) { + } + } + } + + /** * Indicates that the call no longer exists. Can be used with either a call or a conference * call. * @@ -326,10 +353,10 @@ final class ConnectionServiceAdapter implements DeathRecipient { /** * 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}. + * Valid values: {@link VideoProfile#STATE_BIDIRECTIONAL}, + * {@link VideoProfile#STATE_AUDIO_ONLY}, + * {@link VideoProfile#STATE_TX_ENABLED}, + * {@link VideoProfile#STATE_RX_ENABLED}. * * @param callId The unique ID of the call to set the video state for. * @param videoState The video state. @@ -369,4 +396,20 @@ final class ConnectionServiceAdapter implements DeathRecipient { } } } + + /** + * Sets extras associated with a connection. + * + * @param callId The unique ID of the call. + * @param extras The extras to associate with this call. + */ + void setExtras(String callId, Bundle extras) { + Log.v(this, "setExtras: %s", extras); + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setExtras(callId, extras); + } catch (RemoteException ignored) { + } + } + } } diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java index 429f296..293dc11 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java @@ -17,6 +17,7 @@ package android.telecom; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.RemoteException; @@ -59,6 +60,8 @@ final class ConnectionServiceAdapterServant { private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 20; private static final int MSG_ADD_EXISTING_CONNECTION = 21; private static final int MSG_ON_POST_DIAL_CHAR = 22; + private static final int MSG_SET_CONFERENCE_MERGE_FAILED = 23; + private static final int MSG_SET_EXTRAS = 24; private final IConnectionServiceAdapter mDelegate; @@ -220,6 +223,23 @@ final class ConnectionServiceAdapterServant { } break; } + case MSG_SET_CONFERENCE_MERGE_FAILED: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setConferenceMergeFailed((String) args.arg1); + } finally { + args.recycle(); + } + break; + } + case MSG_SET_EXTRAS: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setExtras((String) args.arg1, (Bundle) args.arg2); + } finally { + args.recycle(); + } + } } } }; @@ -280,6 +300,13 @@ final class ConnectionServiceAdapterServant { } @Override + public void setConferenceMergeFailed(String callId) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + mHandler.obtainMessage(MSG_SET_CONFERENCE_MERGE_FAILED, args).sendToTarget(); + } + + @Override public void setIsConferenced(String callId, String conferenceCallId) { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; @@ -384,6 +411,14 @@ final class ConnectionServiceAdapterServant { args.arg2 = connection; mHandler.obtainMessage(MSG_ADD_EXISTING_CONNECTION, args).sendToTarget(); } + + @Override + public final void setExtras(String connectionId, Bundle extras) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionId; + args.arg2 = extras; + mHandler.obtainMessage(MSG_SET_EXTRAS, args).sendToTarget(); + } }; public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) { diff --git a/telecomm/java/android/telecom/DefaultDialerManager.java b/telecomm/java/android/telecom/DefaultDialerManager.java new file mode 100644 index 0000000..3d49308 --- /dev/null +++ b/telecomm/java/android/telecom/DefaultDialerManager.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2015 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.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.provider.Settings; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class for managing the default dialer application that will receive incoming calls, and be + * allowed to make emergency outgoing calls. + * + * @hide + */ +public class DefaultDialerManager { + private static final String TAG = "DefaultDialerManager"; + + /** + * Sets the specified package name as the default dialer application for the current user. + * The caller of this method needs to have permission to write to secure settings and + * manage users on the device. + * + * @return {@code true} if the default dialer application was successfully changed, + * {@code false} otherwise. + * + * @hide + * */ + public static boolean setDefaultDialerApplication(Context context, String packageName) { + return setDefaultDialerApplication(context, packageName, ActivityManager.getCurrentUser()); + } + + /** + * Sets the specified package name as the default dialer application for the specified user. + * The caller of this method needs to have permission to write to secure settings and + * manage users on the device. + * + * @return {@code true} if the default dialer application was successfully changed, + * {@code false} otherwise. + * + * @hide + * */ + public static boolean setDefaultDialerApplication(Context context, String packageName, + int user) { + // Get old package name + String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(), + Settings.Secure.DIALER_DEFAULT_APPLICATION, user); + + if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) { + // No change + return false; + } + + // Only make the change if the new package belongs to a valid phone application + List<String> packageNames = getInstalledDialerApplications(context); + + if (packageNames.contains(packageName)) { + // Update the secure setting. + Settings.Secure.putStringForUser(context.getContentResolver(), + Settings.Secure.DIALER_DEFAULT_APPLICATION, packageName, user); + return true; + } + return false; + } + + /** + * Returns the installed dialer application for the current user that will be used to receive + * incoming calls, and is allowed to make emergency calls. + * + * The application will be returned in order of preference: + * 1) User selected phone application (if still installed) + * 2) Pre-installed system dialer (if not disabled) + * 3) Null + * + * The caller of this method needs to have permission to manage users on the device. + * + * @hide + * */ + public static String getDefaultDialerApplication(Context context) { + return getDefaultDialerApplication(context, ActivityManager.getCurrentUser()); + } + + /** + * Returns the installed dialer application for the specified user that will be used to receive + * incoming calls, and is allowed to make emergency calls. + * + * The application will be returned in order of preference: + * 1) User selected phone application (if still installed) + * 2) Pre-installed system dialer (if not disabled) + * 3) Null + * + * The caller of this method needs to have permission to manage users on the device. + * + * @hide + * */ + public static String getDefaultDialerApplication(Context context, int user) { + String defaultPackageName = Settings.Secure.getStringForUser(context.getContentResolver(), + Settings.Secure.DIALER_DEFAULT_APPLICATION, user); + + final List<String> packageNames = getInstalledDialerApplications(context); + + // Verify that the default dialer has not been disabled or uninstalled. + if (packageNames.contains(defaultPackageName)) { + return defaultPackageName; + } + + // No user-set dialer found, fallback to system dialer + String systemDialerPackageName = getTelecomManager(context).getSystemDialerPackage(); + + if (TextUtils.isEmpty(systemDialerPackageName)) { + // No system dialer configured at build time + return null; + } + + if (packageNames.contains(systemDialerPackageName)) { + return systemDialerPackageName; + } else { + return null; + } + } + + /** + * Returns a list of installed and available dialer applications. + * + * In order to appear in the list, a dialer application must implement an intent-filter with + * the DIAL intent for the following schemes: + * + * 1) Empty scheme + * 2) tel Uri scheme + * + * @hide + **/ + public static List<String> getInstalledDialerApplications(Context context) { + PackageManager packageManager = context.getPackageManager(); + + // Get the list of apps registered for the DIAL intent with empty scheme + Intent intent = new Intent(Intent.ACTION_DIAL); + List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0); + + List<String> packageNames = new ArrayList<>(); + + for (ResolveInfo resolveInfo : resolveInfoList) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo != null && !packageNames.contains(activityInfo.packageName)) { + packageNames.add(activityInfo.packageName); + } + } + + final Intent dialIntentWithTelScheme = new Intent(Intent.ACTION_DIAL); + dialIntentWithTelScheme.setData(Uri.fromParts(PhoneAccount.SCHEME_TEL, "", null)); + return filterByIntent(context, packageNames, dialIntentWithTelScheme); + } + + /** + * Determines if the package name belongs to the user-selected default dialer or the preloaded + * system dialer, and thus should be allowed to perform certain privileged operations. + * + * @param context A valid context. + * @param packageName of the package to check for. + * + * @return {@code true} if the provided package name corresponds to the user-selected default + * dialer or the preloaded system dialer, {@code false} otherwise. + * + * @hide + */ + public static boolean isDefaultOrSystemDialer(Context context, String packageName) { + if (TextUtils.isEmpty(packageName)) { + return false; + } + final TelecomManager tm = getTelecomManager(context); + return packageName.equals(tm.getDefaultDialerPackage()) + || packageName.equals(tm.getSystemDialerPackage()); + } + + /** + * Filter a given list of package names for those packages that contain an activity that has + * an intent filter for a given intent. + * + * @param context A valid context + * @param packageNames List of package names to filter. + * @return The filtered list. + */ + private static List<String> filterByIntent(Context context, List<String> packageNames, + Intent intent) { + if (packageNames == null || packageNames.isEmpty()) { + return new ArrayList<>(); + } + + final List<String> result = new ArrayList<>(); + final List<ResolveInfo> resolveInfoList = + context.getPackageManager().queryIntentActivities(intent, 0); + final int length = resolveInfoList.size(); + for (int i = 0; i < length; i++) { + final ActivityInfo info = resolveInfoList.get(i).activityInfo; + if (info != null && packageNames.contains(info.packageName) + && !result.contains(info.packageName)) { + result.add(info.packageName); + } + } + + return result; + } + + + private static TelecomManager getTelecomManager(Context context) { + return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); + } +} diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java index 130d676..3a7faf6 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.media.ToneGenerator; @@ -30,9 +29,7 @@ import java.util.Objects; * user. It is the responsibility of the {@link ConnectionService} to provide localized versions of * the label and description. It also may contain a reason for the disconnect, which is intended for * logging and not for display to the user. - * @hide */ -@SystemApi public final class DisconnectCause implements Parcelable { /** Disconnected because of an unknown or unspecified reason. */ @@ -133,8 +130,10 @@ public final class DisconnectCause implements Parcelable { /** * 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. + * in the user interface. If not null, it is expected that the In-Call UI should display this + * text where it would normally display the call state ("Dialing", "Disconnected") and is + * therefore expected to be relatively small. The {@link ConnectionService } is responsible for + * providing and localizing this label. If there is no string provided, returns null. * * @return The disconnect label. */ @@ -144,8 +143,11 @@ public final class DisconnectCause implements Parcelable { /** * 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. + * in the user interface. This optional text is generally a longer and more descriptive version + * of {@link #getLabel}, however it can exist even if {@link #getLabel} is empty. The In-Call UI + * should display this relatively prominently; the traditional implementation displays this as + * an alert dialog. The {@link ConnectionService} is responsible for providing and localizing + * this message. If there is no string provided, returns null. * * @return The disconnect description. */ diff --git a/telecomm/java/android/telecom/GatewayInfo.java b/telecomm/java/android/telecom/GatewayInfo.java index 5b8e4ab..928570e 100644 --- a/telecomm/java/android/telecom/GatewayInfo.java +++ b/telecomm/java/android/telecom/GatewayInfo.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -34,17 +33,13 @@ import android.text.TextUtils; * <li> Call the appropriate gateway address. * <li> Display information about how the call is being routed to the user. * </ol> - * @hide */ -@SystemApi 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; diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java index 62b8dea..0cf7212 100644 --- a/telecomm/java/android/telecom/InCallAdapter.java +++ b/telecomm/java/android/telecom/InCallAdapter.java @@ -119,7 +119,7 @@ public final class InCallAdapter { } /** - * Sets the audio route (speaker, bluetooth, etc...). See {@link AudioState}. + * Sets the audio route (speaker, bluetooth, etc...). See {@link CallAudioState}. * * @param route The audio route to use. */ diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index a85e84d..19c613d 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -16,10 +16,12 @@ package android.telecom; -import android.annotation.SystemApi; import android.annotation.SdkConstant; +import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; +import android.hardware.camera2.CameraManager; +import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -31,15 +33,31 @@ import com.android.internal.telecom.IInCallAdapter; import com.android.internal.telecom.IInCallService; import java.lang.String; +import java.util.Collections; +import java.util.List; /** * 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} + * and uses it to notify the in-call app of any live and recently disconnected calls. An app must + * first be set as the default phone app (See {@link TelecomManager#getDefaultDialerPackage()}) + * before the telecom service will bind to its {@code InCallService} implementation. + * <p> + * Below is an example manifest registration for an {@code InCallService}. The meta-data + * ({@link TelecomManager#METADATA_IN_CALL_SERVICE_UI}) indicates that this particular + * {@code InCallService} implementation intends to replace the built-in in-call UI. + * <pre> + * {@code + * <service android:name="your.package.YourInCallServiceImplementation" + * android:permission="android.permission.BIND_IN_CALL_SERVICE"> + * <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" /> + * <intent-filter> + * <action android:name="android.telecom.InCallService"/> + * </intent-filter> + * </service> + * } + * </pre> */ -@SystemApi public abstract class InCallService extends Service { /** @@ -52,7 +70,7 @@ public abstract class InCallService extends Service { 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_ON_CALL_AUDIO_STATE_CHANGED = 5; private static final int MSG_BRING_TO_FOREGROUND = 6; private static final int MSG_ON_CAN_ADD_CALL_CHANGED = 7; @@ -67,6 +85,7 @@ public abstract class InCallService extends Service { switch (msg.what) { case MSG_SET_IN_CALL_ADAPTER: mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj)); + mPhone.addListener(mPhoneListener); onPhoneCreated(mPhone); break; case MSG_ADD_CALL: @@ -86,8 +105,8 @@ public abstract class InCallService extends Service { } break; } - case MSG_ON_AUDIO_STATE_CHANGED: - mPhone.internalAudioStateChanged((AudioState) msg.obj); + case MSG_ON_CALL_AUDIO_STATE_CHANGED: + mPhone.internalCallAudioStateChanged((CallAudioState) msg.obj); break; case MSG_BRING_TO_FOREGROUND: mPhone.internalBringToForeground(msg.arg1 == 1); @@ -132,8 +151,8 @@ public abstract class InCallService extends Service { } @Override - public void onAudioStateChanged(AudioState audioState) { - mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, audioState).sendToTarget(); + public void onCallAudioStateChanged(CallAudioState callAudioState) { + mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, callAudioState).sendToTarget(); } @Override @@ -148,6 +167,43 @@ public abstract class InCallService extends Service { } } + private Phone.Listener mPhoneListener = new Phone.Listener() { + /** ${inheritDoc} */ + @Override + public void onAudioStateChanged(Phone phone, AudioState audioState) { + InCallService.this.onAudioStateChanged(audioState); + } + + public void onCallAudioStateChanged(Phone phone, CallAudioState callAudioState) { + InCallService.this.onCallAudioStateChanged(callAudioState); + }; + + /** ${inheritDoc} */ + @Override + public void onBringToForeground(Phone phone, boolean showDialpad) { + InCallService.this.onBringToForeground(showDialpad); + } + + /** ${inheritDoc} */ + @Override + public void onCallAdded(Phone phone, Call call) { + InCallService.this.onCallAdded(call); + } + + /** ${inheritDoc} */ + @Override + public void onCallRemoved(Phone phone, Call call) { + InCallService.this.onCallRemoved(call); + } + + /** ${inheritDoc} */ + @Override + public void onCanAddCallChanged(Phone phone, boolean canAddCall) { + InCallService.this.onCanAddCallChanged(canAddCall); + } + + }; + private Phone mPhone; public InCallService() { @@ -165,8 +221,14 @@ public abstract class InCallService extends Service { mPhone = null; oldPhone.destroy(); + // destroy sets all the calls to disconnected if any live ones still exist. Therefore, + // it is important to remove the Listener *after* the call to destroy so that + // InCallService.on* callbacks are appropriately called. + oldPhone.removeListener(mPhoneListener); + onPhoneDestroyed(oldPhone); } + return false; } @@ -176,19 +238,92 @@ public abstract class InCallService extends Service { * @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}. + * @hide + * @deprecated Use direct methods on InCallService instead of {@link Phone}. */ + @SystemApi + @Deprecated public Phone getPhone() { return mPhone; } /** + * Obtains the current list of {@code Call}s to be displayed by this in-call service. + * + * @return A list of the relevant {@code Call}s. + */ + public final List<Call> getCalls() { + return mPhone == null ? Collections.<Call>emptyList() : mPhone.getCalls(); + } + + /** + * Returns if the device can support additional calls. + * + * @return Whether the phone supports adding more calls. + */ + public final boolean canAddCall() { + return mPhone == null ? false : mPhone.canAddCall(); + } + + /** + * Obtains the current phone call audio state. + * + * @return An object encapsulating the audio state. Returns null if the service is not + * fully initialized. + * @deprecated Use {@link #getCallAudioState()} instead. + * @hide + */ + @Deprecated + public final AudioState getAudioState() { + return mPhone == null ? null : mPhone.getAudioState(); + } + + /** + * Obtains the current phone call audio state. + * + * @return An object encapsulating the audio state. Returns null if the service is not + * fully initialized. + */ + public final CallAudioState getCallAudioState() { + return mPhone == null ? null : mPhone.getCallAudioState(); + } + + /** + * Sets the microphone mute state. When this request is honored, there will be change to + * the {@link #getCallAudioState()}. + * + * @param state {@code true} if the microphone should be muted; {@code false} otherwise. + */ + public final void setMuted(boolean state) { + if (mPhone != null) { + mPhone.setMuted(state); + } + } + + /** + * Sets the audio route (speaker, bluetooth, etc...). When this request is honored, there will + * be change to the {@link #getCallAudioState()}. + * + * @param route The audio route to use. + */ + public final void setAudioRoute(int route) { + if (mPhone != null) { + mPhone.setAudioRoute(route); + } + } + + /** * 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}. + * @hide + * @deprecated Use direct methods on InCallService instead of {@link Phone}. */ + @SystemApi + @Deprecated public void onPhoneCreated(Phone phone) { } @@ -199,28 +334,114 @@ public abstract class InCallService extends Service { * call to {@link #onPhoneCreated(Phone)}. * * @param phone The {@code Phone} object associated with this {@code InCallService}. + * @hide + * @deprecated Use direct methods on InCallService instead of {@link Phone}. */ + @SystemApi + @Deprecated public void onPhoneDestroyed(Phone phone) { } /** - * Class to invoke functionality related to video calls. + * Called when the audio state changes. + * + * @param audioState The new {@link AudioState}. + * @deprecated Use {@link #onCallAudioStateChanged(CallAudioState) instead}. * @hide */ + @Deprecated + public void onAudioStateChanged(AudioState audioState) { + } + + /** + * Called when the audio state changes. + * + * @param audioState The new {@link CallAudioState}. + */ + public void onCallAudioStateChanged(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 showDialpad If true, put up the dialpad when the screen is shown. + */ + public void onBringToForeground(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 call A newly added {@code Call}. + */ + public void onCallAdded(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 call A newly removed {@code Call}. + */ + public void onCallRemoved(Call call) { + } + + /** + * Called when the ability to add more calls changes. If the phone cannot + * support more calls then {@code canAddCall} is set to {@code false}. If it can, then it + * is set to {@code true}. This can be used to control the visibility of UI to add more calls. + * + * @param canAddCall Indicates whether an additional call can be added. + */ + public void onCanAddCallChanged(boolean canAddCall) { + } + + /** + * Used to issue commands to the {@link Connection.VideoProvider} associated with a + * {@link Call}. + */ public static abstract class VideoCall { + /** @hide */ + public abstract void destroy(); + /** - * Sets a listener to invoke callback methods in the InCallUI after performing video - * telephony actions. + * Registers a callback to receive commands and state changes for video calls. * - * @param videoCallListener The call video client. + * @param callback The video call callback. */ - public abstract void setVideoCallListener(VideoCall.Listener videoCallListener); + public abstract void registerCallback(VideoCall.Callback callback); /** - * Sets the camera to be used for video recording in a video call. + * Registers a callback to receive commands and state changes for video calls. * - * @param cameraId The id of the camera. + * @param callback The video call callback. + * @param handler A handler which commands and status changes will be delivered to. + */ + public abstract void registerCallback(VideoCall.Callback callback, Handler handler); + + /** + * Clears the video call callback set via {@link #registerCallback}. + * + * @param callback The video call callback to clear. + */ + public abstract void unregisterCallback(VideoCall.Callback callback); + + /** + * Sets the camera to be used for the outgoing video. + * <p> + * Handled by {@link Connection.VideoProvider#onSetCamera(String)}. + * + * @param cameraId The id of the camera (use ids as reported by + * {@link CameraManager#getCameraIdList()}). */ public abstract void setCamera(String cameraId); @@ -228,21 +449,27 @@ public abstract class InCallService extends Service { * 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. + * <p> + * Handled by {@link Connection.VideoProvider#onSetPreviewSurface(Surface)}. * - * @param surface The surface. + * @param surface The {@link Surface}. */ public abstract void setPreviewSurface(Surface surface); /** * Sets the surface to be used for displaying the video received from the remote device. + * <p> + * Handled by {@link Connection.VideoProvider#onSetDisplaySurface(Surface)}. * - * @param surface The surface. + * @param surface The {@link 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. + * <p> + * Handled by {@link Connection.VideoProvider#onSetDeviceOrientation(int)}. * * @param rotation The device orientation, in degrees. */ @@ -250,110 +477,146 @@ public abstract class InCallService extends Service { /** * Sets camera zoom ratio. + * <p> + * Handled by {@link Connection.VideoProvider#onSetZoom(float)}. * * @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. + * Issues a request to modify the properties of the current video session. + * <p> + * Example scenarios include: requesting an audio-only call to be upgraded to a + * bi-directional video call, turning on or off the user's camera, sending a pause signal + * when the {@link InCallService} is no longer the foreground application. + * <p> + * Handled by + * {@link Connection.VideoProvider#onSendSessionModifyRequest(VideoProfile, VideoProfile)}. * * @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}. + * Provides a response to a request to change the current call video session + * properties. This should be called in response to a request the {@link InCallService} has + * received via {@link VideoCall.Callback#onSessionModifyRequestReceived}. + * <p> + * Handled by + * {@link Connection.VideoProvider#onSendSessionModifyResponse(VideoProfile)}. * * @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)}. + * Issues a request to the {@link Connection.VideoProvider} to retrieve the capabilities + * of the current camera. The current camera is selected using + * {@link VideoCall#setCamera(String)}. + * <p> + * Camera capabilities are reported to the caller via + * {@link VideoCall.Callback#onCameraCapabilitiesChanged(VideoProfile.CameraCapabilities)}. + * <p> + * Handled by {@link Connection.VideoProvider#onRequestCameraCapabilities()}. */ 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}. + * Issues a request to the {@link Connection.VideoProvider} to retrieve the cumulative data + * usage for the video component of the current call (in bytes). Data usage is reported + * to the caller via {@link VideoCall.Callback#onCallDataUsageChanged}. + * <p> + * Handled by {@link Connection.VideoProvider#onRequestConnectionDataUsage()}. */ 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. + * Provides the {@link Connection.VideoProvider} with the {@link Uri} of an image to be + * displayed to the peer device when the video signal is paused. + * <p> + * Handled by {@link Connection.VideoProvider#onSetPauseImage(Uri)}. * * @param uri URI of image to display. */ - public abstract void setPauseImage(String uri); + public abstract void setPauseImage(Uri uri); /** - * Listener class which invokes callbacks after video call actions occur. - * @hide + * The {@link InCallService} extends this class to provide a means of receiving callbacks + * from the {@link Connection.VideoProvider}. + * <p> + * When the {@link InCallService} receives the + * {@link Call.Callback#onVideoCallChanged(Call, VideoCall)} callback, it should create an + * instance its {@link VideoCall.Callback} implementation and set it on the + * {@link VideoCall} using {@link VideoCall#registerCallback(Callback)}. */ - public static abstract class Listener { + public static abstract class Callback { /** - * 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. + * Called when the {@link Connection.VideoProvider} receives a session modification + * request from the peer device. + * <p> + * The {@link InCallService} may potentially prompt the user to confirm whether they + * wish to accept the request, or decide to automatically accept the request. In either + * case the {@link InCallService} should call + * {@link VideoCall#sendSessionModifyResponse(VideoProfile)} to indicate the video + * profile agreed upon. + * <p> + * Callback originates from + * {@link Connection.VideoProvider#receiveSessionModifyRequest(VideoProfile)}. * - * @param videoProfile The requested video call profile. + * @param videoProfile The requested video 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}. + * Called when the {@link Connection.VideoProvider} receives a response to a session + * modification request previously sent to the peer device. + * <p> + * The new video state should not be considered active by the {@link InCallService} + * until the {@link Call} video state changes (the + * {@link Call.Callback#onDetailsChanged(Call, Call.Details)} callback is triggered + * when the video state changes). + * <p> + * Callback originates from + * {@link Connection.VideoProvider#receiveSessionModifyResponse(int, VideoProfile, + * VideoProfile)}. * * @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. + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_TIMED_OUT}, + * {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE}. + * @param requestedProfile The original request which was sent to the peer device. + * @param responseProfile The actual profile changes made by the peer 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} + * Handles events related to the current video session which the {@link InCallService} + * may wish to handle. These are separate from requested changes to the session due to + * the underlying protocol or connection. + * <p> + * Callback originates from + * {@link Connection.VideoProvider#handleCallSessionEvent(int)}. * - * @param event The event. + * @param event The event. 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}. */ 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. + * Handles a change to the video dimensions from the peer device. This could happen if, + * for example, the peer changes orientation of their device, or switches cameras. + * <p> + * Callback originates from + * {@link Connection.VideoProvider#changePeerDimensions(int, int)}. * * @param width The updated peer video width. * @param height The updated peer video height. @@ -361,19 +624,47 @@ public abstract class InCallService extends Service { public abstract void onPeerDimensionsChanged(int width, int height); /** - * Handles an update to the total data used for the current session. + * Handles a change to the video quality. + * <p> + * Callback originates from {@link Connection.VideoProvider#changeVideoQuality(int)}. + * + * @param videoQuality The updated peer video quality. Valid values: + * {@link VideoProfile#QUALITY_HIGH}, + * {@link VideoProfile#QUALITY_MEDIUM}, + * {@link VideoProfile#QUALITY_LOW}, + * {@link VideoProfile#QUALITY_DEFAULT}. + */ + public abstract void onVideoQualityChanged(int videoQuality); + + /** + * Handles an update to the total data used for the current video session. + * <p> + * Used by the {@link Connection.VideoProvider} in response to + * {@link VideoCall#requestCallDataUsage()}. May also be called periodically by the + * {@link Connection.VideoProvider}. + * <p> + * Callback originates from {@link Connection.VideoProvider#setCallDataUsage(long)}. * - * @param dataUsage The updated data usage. + * @param dataUsage The updated data usage (in bytes). */ - public abstract void onCallDataUsageChanged(int dataUsage); + public abstract void onCallDataUsageChanged(long dataUsage); /** - * Handles a change in camera capabilities. + * Handles a change in the capabilities of the currently selected camera. + * <p> + * Used by the {@link Connection.VideoProvider} in response to + * {@link VideoCall#requestCameraCapabilities()}. The {@link Connection.VideoProvider} + * may also report the camera capabilities after a call to + * {@link VideoCall#setCamera(String)}. + * <p> + * Callback originates from + * {@link Connection.VideoProvider#changeCameraCapabilities( + * VideoProfile.CameraCapabilities)}. * * @param cameraCapabilities The changed camera capabilities. */ public abstract void onCameraCapabilitiesChanged( - CameraCapabilities cameraCapabilities); + VideoProfile.CameraCapabilities cameraCapabilities); } } } diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index c5c3d11..8cf4aeb 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -46,6 +46,7 @@ public final class ParcelableCall implements Parcelable { private final int mCallerDisplayNamePresentation; private final GatewayInfo mGatewayInfo; private final PhoneAccountHandle mAccountHandle; + private final boolean mIsVideoCallProviderChanged; private final IVideoProvider mVideoCallProvider; private InCallService.VideoCall mVideoCall; private final String mParentCallId; @@ -53,6 +54,7 @@ public final class ParcelableCall implements Parcelable { private final StatusHints mStatusHints; private final int mVideoState; private final List<String> mConferenceableCallIds; + private final Bundle mIntentExtras; private final Bundle mExtras; public ParcelableCall( @@ -69,12 +71,14 @@ public final class ParcelableCall implements Parcelable { int callerDisplayNamePresentation, GatewayInfo gatewayInfo, PhoneAccountHandle accountHandle, + boolean isVideoCallProviderChanged, IVideoProvider videoCallProvider, String parentCallId, List<String> childCallIds, StatusHints statusHints, int videoState, List<String> conferenceableCallIds, + Bundle intentExtras, Bundle extras) { mId = id; mState = state; @@ -89,12 +93,14 @@ public final class ParcelableCall implements Parcelable { mCallerDisplayNamePresentation = callerDisplayNamePresentation; mGatewayInfo = gatewayInfo; mAccountHandle = accountHandle; + mIsVideoCallProviderChanged = isVideoCallProviderChanged; mVideoCallProvider = videoCallProvider; mParentCallId = parentCallId; mChildCallIds = childCallIds; mStatusHints = statusHints; mVideoState = videoState; mConferenceableCallIds = Collections.unmodifiableList(conferenceableCallIds); + mIntentExtras = intentExtras; mExtras = extras; } @@ -175,10 +181,10 @@ public final class ParcelableCall implements Parcelable { * Returns an object for remotely communicating through the video call provider's binder. * @return The video call. */ - public InCallService.VideoCall getVideoCall() { + public InCallService.VideoCall getVideoCall(Call call) { if (mVideoCall == null && mVideoCallProvider != null) { try { - mVideoCall = new VideoCallImpl(mVideoCallProvider); + mVideoCall = new VideoCallImpl(mVideoCallProvider, call); } catch (RemoteException ignored) { // Ignore RemoteException. } @@ -224,7 +230,7 @@ public final class ParcelableCall implements Parcelable { } /** - * Any extras to pass with the call + * Any extras associated with this call. * * @return a bundle of extras */ @@ -232,6 +238,27 @@ public final class ParcelableCall implements Parcelable { return mExtras; } + /** + * Extras passed in as part of the original call intent. + * + * @return The intent extras. + */ + public Bundle getIntentExtras() { + return mIntentExtras; + } + + /** + * Indicates to the receiver of the {@link ParcelableCall} whether a change has occurred in the + * {@link android.telecom.InCallService.VideoCall} associated with this call. Since + * {@link #getVideoCall()} creates a new {@link VideoCallImpl}, it is useful to know whether + * the provider has changed (which can influence whether it is accessed). + * + * @return {@code true} if the video call changed, {@code false} otherwise. + */ + public boolean isVideoCallProviderChanged() { + return mIsVideoCallProviderChanged; + } + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ public static final Parcelable.Creator<ParcelableCall> CREATOR = new Parcelable.Creator<ParcelableCall> () { @@ -252,6 +279,7 @@ public final class ParcelableCall implements Parcelable { int callerDisplayNamePresentation = source.readInt(); GatewayInfo gatewayInfo = source.readParcelable(classLoader); PhoneAccountHandle accountHandle = source.readParcelable(classLoader); + boolean isVideoCallProviderChanged = source.readByte() == 1; IVideoProvider videoCallProvider = IVideoProvider.Stub.asInterface(source.readStrongBinder()); String parentCallId = source.readString(); @@ -261,7 +289,8 @@ public final class ParcelableCall implements Parcelable { int videoState = source.readInt(); List<String> conferenceableCallIds = new ArrayList<>(); source.readList(conferenceableCallIds, classLoader); - Bundle extras = source.readParcelable(classLoader); + Bundle intentExtras = source.readBundle(classLoader); + Bundle extras = source.readBundle(classLoader); return new ParcelableCall( id, state, @@ -276,12 +305,14 @@ public final class ParcelableCall implements Parcelable { callerDisplayNamePresentation, gatewayInfo, accountHandle, + isVideoCallProviderChanged, videoCallProvider, parentCallId, childCallIds, statusHints, videoState, conferenceableCallIds, + intentExtras, extras); } @@ -313,6 +344,7 @@ public final class ParcelableCall implements Parcelable { destination.writeInt(mCallerDisplayNamePresentation); destination.writeParcelable(mGatewayInfo, 0); destination.writeParcelable(mAccountHandle, 0); + destination.writeByte((byte) (mIsVideoCallProviderChanged ? 1 : 0)); destination.writeStrongBinder( mVideoCallProvider != null ? mVideoCallProvider.asBinder() : null); destination.writeString(mParentCallId); @@ -320,7 +352,8 @@ public final class ParcelableCall implements Parcelable { destination.writeParcelable(mStatusHints, 0); destination.writeInt(mVideoState); destination.writeList(mConferenceableCallIds); - destination.writeParcelable(mExtras, 0); + destination.writeBundle(mIntentExtras); + destination.writeBundle(mExtras); } @Override diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java index dcc2713..870f5ee 100644 --- a/telecomm/java/android/telecom/ParcelableConference.java +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -16,12 +16,15 @@ package android.telecom; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; import java.util.List; +import com.android.internal.telecom.IVideoProvider; + /** * A parcelable representation of a conference connection. * @hide @@ -32,28 +35,32 @@ public final class ParcelableConference implements Parcelable { private int mState; private int mConnectionCapabilities; private List<String> mConnectionIds; - private long mConnectTimeMillis; + private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; + private final IVideoProvider mVideoProvider; + private final int mVideoState; + private StatusHints mStatusHints; + private Bundle mExtras; public ParcelableConference( PhoneAccountHandle phoneAccount, int state, int connectionCapabilities, - List<String> connectionIds) { + List<String> connectionIds, + IVideoProvider videoProvider, + int videoState, + long connectTimeMillis, + StatusHints statusHints, + Bundle extras) { mPhoneAccount = phoneAccount; mState = state; mConnectionCapabilities = connectionCapabilities; mConnectionIds = connectionIds; mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; - } - - public ParcelableConference( - PhoneAccountHandle phoneAccount, - int state, - int connectionCapabilities, - List<String> connectionIds, - long connectTimeMillis) { - this(phoneAccount, state, connectionCapabilities, connectionIds); + mVideoProvider = videoProvider; + mVideoState = videoState; mConnectTimeMillis = connectTimeMillis; + mStatusHints = statusHints; + mExtras = extras; } @Override @@ -69,6 +76,10 @@ public final class ParcelableConference implements Parcelable { .append(mConnectTimeMillis) .append(", children: ") .append(mConnectionIds) + .append(", VideoState: ") + .append(mVideoState) + .append(", VideoProvider: ") + .append(mVideoProvider) .toString(); } @@ -91,6 +102,21 @@ public final class ParcelableConference implements Parcelable { public long getConnectTimeMillis() { return mConnectTimeMillis; } + public IVideoProvider getVideoProvider() { + return mVideoProvider; + } + + public int getVideoState() { + return mVideoState; + } + + public StatusHints getStatusHints() { + return mStatusHints; + } + + public Bundle getExtras() { + return mExtras; + } public static final Parcelable.Creator<ParcelableConference> CREATOR = new Parcelable.Creator<ParcelableConference> () { @@ -103,9 +129,14 @@ public final class ParcelableConference implements Parcelable { List<String> connectionIds = new ArrayList<>(2); source.readList(connectionIds, classLoader); long connectTimeMillis = source.readLong(); + IVideoProvider videoCallProvider = + IVideoProvider.Stub.asInterface(source.readStrongBinder()); + int videoState = source.readInt(); + StatusHints statusHints = source.readParcelable(classLoader); + Bundle extras = source.readBundle(classLoader); return new ParcelableConference(phoneAccount, state, capabilities, connectionIds, - connectTimeMillis); + videoCallProvider, videoState, connectTimeMillis, statusHints, extras); } @Override @@ -128,5 +159,10 @@ public final class ParcelableConference implements Parcelable { destination.writeInt(mConnectionCapabilities); destination.writeList(mConnectionIds); destination.writeLong(mConnectTimeMillis); + destination.writeStrongBinder( + mVideoProvider != null ? mVideoProvider.asBinder() : null); + destination.writeInt(mVideoState); + destination.writeParcelable(mStatusHints, 0); + destination.writeBundle(mExtras); } } diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java index 552e250..683ab6a 100644 --- a/telecomm/java/android/telecom/ParcelableConnection.java +++ b/telecomm/java/android/telecom/ParcelableConnection.java @@ -17,6 +17,7 @@ package android.telecom; import android.net.Uri; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -46,6 +47,7 @@ public final class ParcelableConnection implements Parcelable { private final StatusHints mStatusHints; private final DisconnectCause mDisconnectCause; private final List<String> mConferenceableConnectionIds; + private final Bundle mExtras; /** @hide */ public ParcelableConnection( @@ -62,7 +64,8 @@ public final class ParcelableConnection implements Parcelable { boolean isVoipAudioMode, StatusHints statusHints, DisconnectCause disconnectCause, - List<String> conferenceableConnectionIds) { + List<String> conferenceableConnectionIds, + Bundle extras) { mPhoneAccount = phoneAccount; mState = state; mConnectionCapabilities = capabilities; @@ -76,7 +79,8 @@ public final class ParcelableConnection implements Parcelable { mIsVoipAudioMode = isVoipAudioMode; mStatusHints = statusHints; mDisconnectCause = disconnectCause; - this.mConferenceableConnectionIds = conferenceableConnectionIds; + mConferenceableConnectionIds = conferenceableConnectionIds; + mExtras = extras; } public PhoneAccountHandle getPhoneAccount() { @@ -136,15 +140,21 @@ public final class ParcelableConnection implements Parcelable { return mConferenceableConnectionIds; } + public final Bundle getExtras() { + return mExtras; + } + @Override public String toString() { return new StringBuilder() .append("ParcelableConnection [act:") .append(mPhoneAccount) - .append(", state:") + .append("], state:") .append(mState) .append(", capabilities:") .append(Connection.capabilitiesToString(mConnectionCapabilities)) + .append(", extras:") + .append(mExtras) .toString(); } @@ -170,6 +180,7 @@ public final class ParcelableConnection implements Parcelable { DisconnectCause disconnectCause = source.readParcelable(classLoader); List<String> conferenceableConnectionIds = new ArrayList<>(); source.readStringList(conferenceableConnectionIds); + Bundle extras = source.readBundle(classLoader); return new ParcelableConnection( phoneAccount, @@ -185,7 +196,8 @@ public final class ParcelableConnection implements Parcelable { audioModeIsVoip, statusHints, disconnectCause, - conferenceableConnectionIds); + conferenceableConnectionIds, + extras); } @Override @@ -218,5 +230,6 @@ public final class ParcelableConnection implements Parcelable { destination.writeParcelable(mStatusHints, 0); destination.writeParcelable(mDisconnectCause, 0); destination.writeStringList(mConferenceableConnectionIds); + destination.writeBundle(mExtras); } } diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index 6344181..8eb091b 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -28,9 +28,11 @@ import java.util.concurrent.CopyOnWriteArrayList; /** * A unified virtual device providing a means of voice (and other) communication on a device. * - * {@hide} + * @hide + * @deprecated Use {@link InCallService} directly instead of using this class. */ @SystemApi +@Deprecated public final class Phone { public abstract static class Listener { @@ -39,10 +41,21 @@ public final class Phone { * * @param phone The {@code Phone} calling this method. * @param audioState The new {@link AudioState}. + * + * @deprecated Use {@link #onCallAudioStateChanged(Phone, CallAudioState)} instead. */ + @Deprecated public void onAudioStateChanged(Phone phone, AudioState audioState) { } /** + * Called when the audio state changes. + * + * @param phone The {@code Phone} calling this method. + * @param callAudioState The new {@link CallAudioState}. + */ + public void onCallAudioStateChanged(Phone phone, CallAudioState callAudioState) { } + + /** * 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. @@ -98,18 +111,16 @@ public final class Phone { private final InCallAdapter mInCallAdapter; - private AudioState mAudioState; + private CallAudioState mCallAudioState; private final List<Listener> mListeners = new CopyOnWriteArrayList<>(); private boolean mCanAddCall = true; - /** {@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); @@ -119,14 +130,17 @@ public final class Phone { fireCallAdded(call); } - /** {@hide} */ final void internalRemoveCall(Call call) { mCallByTelecomCallId.remove(call.internalGetCallId()); mCalls.remove(call); + + InCallService.VideoCall videoCall = call.getVideoCall(); + if (videoCall != null) { + videoCall.destroy(); + } fireCallRemoved(call); } - /** {@hide} */ final void internalUpdateCall(ParcelableCall parcelableCall) { Call call = mCallByTelecomCallId.get(parcelableCall.getId()); if (call != null) { @@ -135,7 +149,6 @@ public final class Phone { } } - /** {@hide} */ final void internalSetPostDialWait(String telecomId, String remaining) { Call call = mCallByTelecomCallId.get(telecomId); if (call != null) { @@ -143,25 +156,21 @@ public final class Phone { } } - /** {@hide} */ - final void internalAudioStateChanged(AudioState audioState) { - if (!Objects.equals(mAudioState, audioState)) { - mAudioState = audioState; - fireAudioStateChanged(audioState); + final void internalCallAudioStateChanged(CallAudioState callAudioState) { + if (!Objects.equals(mCallAudioState, callAudioState)) { + mCallAudioState = callAudioState; + fireCallAudioStateChanged(callAudioState); } } - /** {@hide} */ final Call internalGetCallByTelecomId(String telecomId) { return mCallByTelecomCallId.get(telecomId); } - /** {@hide} */ final void internalBringToForeground(boolean showDialpad) { fireBringToForeground(showDialpad); } - /** {@hide} */ final void internalSetCanAddCall(boolean canAddCall) { if (mCanAddCall != canAddCall) { mCanAddCall = canAddCall; @@ -171,10 +180,13 @@ public final class Phone { /** * Called to destroy the phone and cleanup any lingering calls. - * @hide */ final void destroy() { for (Call call : mCalls) { + InCallService.VideoCall videoCall = call.getVideoCall(); + if (videoCall != null) { + videoCall.destroy(); + } if (call.getState() != Call.STATE_DISCONNECTED) { call.internalSetDisconnected(); } @@ -244,6 +256,8 @@ public final class Phone { * 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. + * + * @hide */ public final void setProximitySensorOn() { mInCallAdapter.turnProximitySensorOn(); @@ -257,6 +271,8 @@ public final class Phone { * @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. + * + * @hide */ public final void setProximitySensorOff(boolean screenOnImmediately) { mInCallAdapter.turnProximitySensorOff(screenOnImmediately); @@ -266,9 +282,20 @@ public final class Phone { * Obtains the current phone call audio state of the {@code Phone}. * * @return An object encapsulating the audio state. + * @deprecated Use {@link #getCallAudioState()} instead. */ + @Deprecated public final AudioState getAudioState() { - return mAudioState; + return new AudioState(mCallAudioState); + } + + /** + * Obtains the current phone call audio state of the {@code Phone}. + * + * @return An object encapsulating the audio state. + */ + public final CallAudioState getCallAudioState() { + return mCallAudioState; } private void fireCallAdded(Call call) { @@ -283,9 +310,10 @@ public final class Phone { } } - private void fireAudioStateChanged(AudioState audioState) { + private void fireCallAudioStateChanged(CallAudioState audioState) { for (Listener listener : mListeners) { - listener.onAudioStateChanged(this, audioState); + listener.onCallAudioStateChanged(this, audioState); + listener.onAudioStateChanged(this, new AudioState(audioState)); } } diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 052a481..df6fa2e 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -26,6 +26,7 @@ import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -40,16 +41,14 @@ import java.util.MissingResourceException; /** * Represents a distinct method to place or receive a phone call. Apps which can place calls and * want those calls to be integrated into the dialer and in-call UI should build an instance of - * this class and register it with the system using {@link TelecomManager#registerPhoneAccount}. + * this class and register it with the system using {@link TelecomManager}. * <p> * {@link TelecomManager} uses registered {@link PhoneAccount}s to present the user with * alternative options when placing a phone call. When building a {@link PhoneAccount}, the app - * should supply a valid {@link PhoneAccountHandle} that references the {@link ConnectionService} + * should supply a valid {@link PhoneAccountHandle} that references the connection service * implementation Telecom will use to interact with the app. - * @hide */ -@SystemApi -public class PhoneAccount implements Parcelable { +public final class PhoneAccount implements Parcelable { /** * Flag indicating that this {@code PhoneAccount} can act as a connection manager for @@ -74,7 +73,6 @@ public class PhoneAccount implements Parcelable { * <p> * See {@link #getCapabilities} * <p> - * {@hide} */ public static final int CAPABILITY_CALL_PROVIDER = 0x2; @@ -92,7 +90,6 @@ public class PhoneAccount implements Parcelable { * 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; @@ -111,6 +108,7 @@ public class PhoneAccount implements Parcelable { * See {@link #getCapabilities} * @hide */ + @SystemApi public static final int CAPABILITY_MULTI_USER = 0x20; /** @@ -130,6 +128,7 @@ public class PhoneAccount implements Parcelable { /** * Indicating no icon tint is set. + * @hide */ public static final int NO_ICON_TINT = 0; @@ -147,14 +146,12 @@ public class PhoneAccount implements Parcelable { private final Uri mAddress; private final Uri mSubscriptionAddress; private final int mCapabilities; - private final int mIconResId; - private final String mIconPackageName; - private final Bitmap mIconBitmap; - private final int mIconTint; private final int mHighlightColor; private final CharSequence mLabel; private final CharSequence mShortDescription; private final List<String> mSupportedUriSchemes; + private final Icon mIcon; + private boolean mIsEnabled; /** * Helper class for creating a {@link PhoneAccount}. @@ -164,14 +161,12 @@ public class PhoneAccount implements Parcelable { private Uri mAddress; private Uri mSubscriptionAddress; private int mCapabilities; - private int mIconResId; - private String mIconPackageName; - private Bitmap mIconBitmap; - private int mIconTint = NO_ICON_TINT; private int mHighlightColor = NO_HIGHLIGHT_COLOR; private CharSequence mLabel; private CharSequence mShortDescription; private List<String> mSupportedUriSchemes = new ArrayList<String>(); + private Icon mIcon; + private boolean mIsEnabled = false; /** * Creates a builder with the specified {@link PhoneAccountHandle} and label. @@ -192,20 +187,12 @@ public class PhoneAccount implements Parcelable { mAddress = phoneAccount.getAddress(); mSubscriptionAddress = phoneAccount.getSubscriptionAddress(); mCapabilities = phoneAccount.getCapabilities(); - mIconResId = phoneAccount.getIconResId(); - mIconPackageName = phoneAccount.getIconPackageName(); - mIconBitmap = phoneAccount.getIconBitmap(); - mIconTint = phoneAccount.getIconTint(); mHighlightColor = phoneAccount.getHighlightColor(); mLabel = phoneAccount.getLabel(); mShortDescription = phoneAccount.getShortDescription(); mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes()); - } - - /** @hide */ - public Builder setAccountHandle(PhoneAccountHandle accountHandle) { - mAccountHandle = accountHandle; - return this; + mIcon = phoneAccount.getIcon(); + mIsEnabled = phoneAccount.isEnabled(); } /** @@ -242,65 +229,12 @@ public class PhoneAccount implements Parcelable { } /** - * Sets the icon. See {@link PhoneAccount#createIconDrawable}. - * - * @param packageContext The package from which to load an icon. - * @param iconResId The resource in {@code iconPackageName} representing the icon. - * @return The builder. - */ - public Builder setIcon(Context packageContext, int iconResId) { - return setIcon(packageContext.getPackageName(), iconResId); - } - - /** - * Sets the icon. See {@link PhoneAccount#createIconDrawable}. - * - * @param iconPackageName The package from which to load an icon. - * @param iconResId The resource in {@code iconPackageName} representing the icon. - * @return The builder. - */ - public Builder setIcon(String iconPackageName, int iconResId) { - return setIcon(iconPackageName, iconResId, NO_ICON_TINT); - } - - /** - * Sets the icon. See {@link PhoneAccount#createIconDrawable}. + * Sets the icon. See {@link PhoneAccount#getIcon}. * - * @param packageContext The package from which to load an icon. - * @param iconResId The resource in {@code iconPackageName} representing the icon. - * @param iconTint A color with which to tint this icon. - * @return The builder. + * @param icon The icon to set. */ - public Builder setIcon(Context packageContext, int iconResId, int iconTint) { - return setIcon(packageContext.getPackageName(), iconResId, iconTint); - } - - /** - * Sets the icon. See {@link PhoneAccount#createIconDrawable}. - * - * @param iconPackageName The package from which to load an icon. - * @param iconResId The resource in {@code iconPackageName} representing the icon. - * @param iconTint A color with which to tint this icon. - * @return The builder. - */ - public Builder setIcon(String iconPackageName, int iconResId, int iconTint) { - this.mIconPackageName = iconPackageName; - this.mIconResId = iconResId; - this.mIconTint = iconTint; - return this; - } - - /** - * Sets the icon. See {@link PhoneAccount#createIconDrawable}. - * - * @param iconBitmap The icon bitmap. - * @return The builder. - */ - public Builder setIcon(Bitmap iconBitmap) { - this.mIconBitmap = iconBitmap; - this.mIconPackageName = null; - this.mIconResId = NO_RESOURCE_ID; - this.mIconTint = NO_ICON_TINT; + public Builder setIcon(Icon icon) { + mIcon = icon; return this; } @@ -331,7 +265,6 @@ public class PhoneAccount implements Parcelable { * * @param uriScheme The URI scheme. * @return The builder. - * @hide */ public Builder addSupportedUriScheme(String uriScheme) { if (!TextUtils.isEmpty(uriScheme) && !mSupportedUriSchemes.contains(uriScheme)) { @@ -358,6 +291,18 @@ public class PhoneAccount implements Parcelable { } /** + * Sets the enabled state of the phone account. + * + * @param isEnabled The enabled state. + * @return The builder. + * @hide + */ + public Builder setIsEnabled(boolean isEnabled) { + mIsEnabled = isEnabled; + return this; + } + + /** * Creates an instance of a {@link PhoneAccount} based on the current builder settings. * * @return The {@link PhoneAccount}. @@ -373,14 +318,12 @@ public class PhoneAccount implements Parcelable { mAddress, mSubscriptionAddress, mCapabilities, - mIconResId, - mIconPackageName, - mIconBitmap, - mIconTint, + mIcon, mHighlightColor, mLabel, mShortDescription, - mSupportedUriSchemes); + mSupportedUriSchemes, + mIsEnabled); } } @@ -389,26 +332,22 @@ public class PhoneAccount implements Parcelable { Uri address, Uri subscriptionAddress, int capabilities, - int iconResId, - String iconPackageName, - Bitmap iconBitmap, - int iconTint, + Icon icon, int highlightColor, CharSequence label, CharSequence shortDescription, - List<String> supportedUriSchemes) { + List<String> supportedUriSchemes, + boolean isEnabled) { mAccountHandle = account; mAddress = address; mSubscriptionAddress = subscriptionAddress; mCapabilities = capabilities; - mIconResId = iconResId; - mIconPackageName = iconPackageName; - mIconBitmap = iconBitmap; - mIconTint = iconTint; + mIcon = icon; mHighlightColor = highlightColor; mLabel = label; mShortDescription = shortDescription; mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes); + mIsEnabled = isEnabled; } public static Builder builder( @@ -421,7 +360,6 @@ public class PhoneAccount implements Parcelable { * Returns a builder initialized with the current {@link PhoneAccount} instance. * * @return The builder. - * @hide */ public Builder toBuilder() { return new Builder(this); } @@ -474,7 +412,7 @@ public class PhoneAccount implements Parcelable { * bit mask. * * @param capability The capabilities to check. - * @return {@code True} if the phone account has the capability. + * @return {@code true} if the phone account has the capability. */ public boolean hasCapabilities(int capability) { return (mCapabilities & capability) == capability; @@ -508,11 +446,30 @@ public class PhoneAccount implements Parcelable { } /** + * The icon to represent this {@code PhoneAccount}. + * + * @return The icon. + */ + public Icon getIcon() { + return mIcon; + } + + /** + * Indicates whether the user has enabled this {@code PhoneAccount} or not. This value is only + * populated for {@code PhoneAccount}s returned by {@link TelecomManager#getPhoneAccount}. + * + * @return {@code true} if the account is enabled by the user, {@code false} otherwise. + */ + public boolean isEnabled() { + return mIsEnabled; + } + + /** * 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 + * @return {@code true} if the {@code PhoneAccount} supports calls to/from addresses with the * specified URI scheme. */ public boolean supportsUriScheme(String uriScheme) { @@ -529,59 +486,6 @@ public class PhoneAccount implements Parcelable { } /** - * The icon resource ID for the icon of this {@code PhoneAccount}. - * <p> - * Creators of a {@code PhoneAccount} who possess the icon in static resources should prefer - * this method of indicating the icon rather than using {@link #getIconBitmap()}, since it - * leads to less resource usage. - * <p> - * Clients wishing to display a {@code PhoneAccount} should use {@link #createIconDrawable(Context)}. - * - * @return A resource ID. - */ - public int getIconResId() { - return mIconResId; - } - - /** - * The package name from which to load the icon of this {@code PhoneAccount}. - * <p> - * If this property is {@code null}, the resource {@link #getIconResId()} will be loaded from - * the package in the {@link ComponentName} of the {@link #getAccountHandle()}. - * <p> - * Clients wishing to display a {@code PhoneAccount} should use {@link #createIconDrawable(Context)}. - * - * @return A package name. - */ - public String getIconPackageName() { - return mIconPackageName; - } - - /** - * A tint to apply to the icon of this {@code PhoneAccount}. - * - * @return A hexadecimal color value. - */ - public int getIconTint() { - return mIconTint; - } - - /** - * A literal icon bitmap to represent this {@code PhoneAccount} in a user interface. - * <p> - * If this property is specified, it is to be considered the preferred icon. Otherwise, the - * resource specified by {@link #getIconResId()} should be used. - * <p> - * Clients wishing to display a {@code PhoneAccount} should use - * {@link #createIconDrawable(Context)}. - * - * @return A bitmap. - */ - public Bitmap getIconBitmap() { - return mIconBitmap; - } - - /** * A highlight color to use in displaying information about this {@code PhoneAccount}. * * @return A hexadecimal color value. @@ -591,38 +495,11 @@ public class PhoneAccount implements Parcelable { } /** - * Builds and returns an icon {@code Drawable} to represent this {@code PhoneAccount} in a user - * interface. Uses the properties {@link #getIconResId()}, {@link #getIconPackageName()}, and - * {@link #getIconBitmap()} as necessary. - * - * @param context A {@code Context} to use for loading {@code Drawable}s. - * - * @return An icon for this {@code PhoneAccount}. + * Sets the enabled state of the phone account. + * @hide */ - public Drawable createIconDrawable(Context context) { - if (mIconBitmap != null) { - return new BitmapDrawable(context.getResources(), mIconBitmap); - } - - if (mIconResId != 0) { - try { - Context packageContext = context.createPackageContext(mIconPackageName, 0); - try { - Drawable iconDrawable = packageContext.getDrawable(mIconResId); - if (mIconTint != NO_ICON_TINT) { - iconDrawable.setTint(mIconTint); - } - return iconDrawable; - } catch (NotFoundException | MissingResourceException e) { - Log.e(this, e, "Cannot find icon %d in package %s", - mIconResId, mIconPackageName); - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(this, "Cannot find package %s", mIconPackageName); - } - } - - return new ColorDrawable(Color.TRANSPARENT); + public void setIsEnabled(boolean isEnabled) { + mIsEnabled = isEnabled; } // @@ -655,19 +532,18 @@ public class PhoneAccount implements Parcelable { mSubscriptionAddress.writeToParcel(out, flags); } out.writeInt(mCapabilities); - out.writeInt(mIconResId); - out.writeString(mIconPackageName); - if (mIconBitmap == null) { - out.writeInt(0); - } else { - out.writeInt(1); - mIconBitmap.writeToParcel(out, flags); - } - out.writeInt(mIconTint); out.writeInt(mHighlightColor); out.writeCharSequence(mLabel); out.writeCharSequence(mShortDescription); out.writeStringList(mSupportedUriSchemes); + + if (mIcon == null) { + out.writeInt(0); + } else { + out.writeInt(1); + mIcon.writeToParcel(out, flags); + } + out.writeByte((byte) (mIsEnabled ? 1 : 0)); } public static final Creator<PhoneAccount> CREATOR @@ -700,23 +576,23 @@ public class PhoneAccount implements Parcelable { mSubscriptionAddress = null; } mCapabilities = in.readInt(); - mIconResId = in.readInt(); - mIconPackageName = in.readString(); - if (in.readInt() > 0) { - mIconBitmap = Bitmap.CREATOR.createFromParcel(in); - } else { - mIconBitmap = null; - } - mIconTint = in.readInt(); mHighlightColor = in.readInt(); mLabel = in.readCharSequence(); mShortDescription = in.readCharSequence(); mSupportedUriSchemes = Collections.unmodifiableList(in.createStringArrayList()); + if (in.readInt() > 0) { + mIcon = Icon.CREATOR.createFromParcel(in); + } else { + mIcon = null; + } + mIsEnabled = in.readByte() == 1; } @Override public String toString() { - StringBuilder sb = new StringBuilder().append("[PhoneAccount: ") + StringBuilder sb = new StringBuilder().append("[[") + .append(mIsEnabled ? 'X' : ' ') + .append("] PhoneAccount: ") .append(mAccountHandle) .append(" Capabilities: ") .append(mCapabilities) diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java index 97af41a..6dc6e9c 100644 --- a/telecomm/java/android/telecom/PhoneAccountHandle.java +++ b/telecomm/java/android/telecom/PhoneAccountHandle.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; @@ -29,17 +28,14 @@ import java.util.Objects; * The unique identifier for a {@link PhoneAccount}. A {@code PhoneAccountHandle} is made of two * parts: * <ul> - * <li>The component name of the associated {@link ConnectionService}.</li> + * <li>The component name of the associated connection service.</li> * <li>A string identifier that is unique across {@code PhoneAccountHandle}s with the same * component name.</li> * </ul> * - * See {@link PhoneAccount}, - * {@link TelecomManager#registerPhoneAccount TelecomManager.registerPhoneAccount}. - * @hide + * See {@link PhoneAccount}, {@link TelecomManager}. */ -@SystemApi -public class PhoneAccountHandle implements Parcelable { +public final class PhoneAccountHandle implements Parcelable { private final ComponentName mComponentName; private final String mId; private final UserHandle mUserHandle; @@ -50,7 +46,6 @@ public class PhoneAccountHandle implements Parcelable { this(componentName, id, Process.myUserHandle()); } - /** @hide */ public PhoneAccountHandle( ComponentName componentName, String id, @@ -61,8 +56,8 @@ public class PhoneAccountHandle implements Parcelable { } /** - * The {@code ComponentName} of the {@link android.telecom.ConnectionService} which is - * responsible for making phone calls using this {@code PhoneAccountHandle}. + * The {@code ComponentName} of the connection service which is responsible for making phone + * calls using this {@code PhoneAccountHandle}. * * @return A suitable {@code ComponentName}. */ @@ -72,9 +67,9 @@ public class PhoneAccountHandle implements Parcelable { /** * A string that uniquely distinguishes this particular {@code PhoneAccountHandle} from all the - * others supported by the {@link ConnectionService} that created it. + * others supported by the connection service that created it. * <p> - * A {@code ConnectionService} must select identifiers that are stable for the lifetime of + * A connection service 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, @@ -82,6 +77,9 @@ public class PhoneAccountHandle implements Parcelable { * ({@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. * + * Important: A non-unique identifier could cause non-deterministic call-log backup/restore + * behavior. + * * @return A service-specific unique identifier for this {@code PhoneAccountHandle}. */ public String getId() { @@ -90,7 +88,6 @@ public class PhoneAccountHandle implements Parcelable { /** * @return the {@link UserHandle} to use when connecting to this PhoneAccount. - * @hide */ public UserHandle getUserHandle() { return mUserHandle; diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java index a8879ae..ae5cd46 100644 --- a/telecomm/java/android/telecom/RemoteConference.java +++ b/telecomm/java/android/telecom/RemoteConference.java @@ -18,7 +18,10 @@ package android.telecom; import com.android.internal.telecom.IConnectionService; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import java.util.ArrayList; @@ -29,30 +32,97 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; /** - * Represents a conference call which can contain any number of {@link Connection} objects. - * @hide + * A conference provided to a {@link ConnectionService} by another {@code ConnectionService} through + * {@link ConnectionService#conferenceRemoteConnections}. Once created, a {@code RemoteConference} + * can be used to control the conference call or monitor changes through + * {@link RemoteConnection.Callback}. + * + * @see ConnectionService#onRemoteConferenceAdded */ -@SystemApi public final class RemoteConference { + /** + * Callback base class for {@link RemoteConference}. + */ public abstract static class Callback { + /** + * Invoked when the state of this {@code RemoteConferece} has changed. See + * {@link #getState()}. + * + * @param conference The {@code RemoteConference} invoking this method. + * @param oldState The previous state of the {@code RemoteConference}. + * @param newState The new state of the {@code RemoteConference}. + */ public void onStateChanged(RemoteConference conference, int oldState, int newState) {} + + /** + * Invoked when this {@code RemoteConference} is disconnected. + * + * @param conference The {@code RemoteConference} invoking this method. + * @param disconnectCause The ({@see DisconnectCause}) associated with this failed + * conference. + */ public void onDisconnected(RemoteConference conference, DisconnectCause disconnectCause) {} + + /** + * Invoked when a {@link RemoteConnection} is added to the conference call. + * + * @param conference The {@code RemoteConference} invoking this method. + * @param connection The {@link RemoteConnection} being added. + */ public void onConnectionAdded(RemoteConference conference, RemoteConnection connection) {} + + /** + * Invoked when a {@link RemoteConnection} is removed from the conference call. + * + * @param conference The {@code RemoteConference} invoking this method. + * @param connection The {@link RemoteConnection} being removed. + */ public void onConnectionRemoved(RemoteConference conference, RemoteConnection connection) {} + + /** + * Indicates that the call capabilities of this {@code RemoteConference} have changed. + * See {@link #getConnectionCapabilities()}. + * + * @param conference The {@code RemoteConference} invoking this method. + * @param connectionCapabilities The new capabilities of the {@code RemoteConference}. + */ public void onConnectionCapabilitiesChanged( RemoteConference conference, int connectionCapabilities) {} + + /** + * Invoked when the set of {@link RemoteConnection}s which can be added to this conference + * call have changed. + * + * @param conference The {@code RemoteConference} invoking this method. + * @param conferenceableConnections The list of conferenceable {@link RemoteConnection}s. + */ public void onConferenceableConnectionsChanged( RemoteConference conference, List<RemoteConnection> conferenceableConnections) {} + + /** + * Indicates that this {@code RemoteConference} has been destroyed. No further requests + * should be made to the {@code RemoteConference}, and references to it should be cleared. + * + * @param conference The {@code RemoteConference} invoking this method. + */ public void onDestroyed(RemoteConference conference) {} + + /** + * Handles changes to the {@code RemoteConference} extras. + * + * @param conference The {@code RemoteConference} invoking this method. + * @param extras The extras containing other information associated with the conference. + */ + public void onExtrasChanged(RemoteConference conference, @Nullable Bundle extras) {} } private final String mId; private final IConnectionService mConnectionService; - private final Set<Callback> mCallbacks = new CopyOnWriteArraySet<>(); + private final Set<CallbackRecord<Callback>> mCallbackRecords = new CopyOnWriteArraySet<>(); private final List<RemoteConnection> mChildConnections = new CopyOnWriteArrayList<>(); private final List<RemoteConnection> mUnmodifiableChildConnections = Collections.unmodifiableList(mChildConnections); @@ -63,30 +133,38 @@ public final class RemoteConference { private int mState = Connection.STATE_NEW; private DisconnectCause mDisconnectCause; private int mConnectionCapabilities; + private Bundle mExtras; - /** {@hide} */ + /** @hide */ RemoteConference(String id, IConnectionService connectionService) { mId = id; mConnectionService = connectionService; } - /** {@hide} */ + /** @hide */ String getId() { return mId; } - /** {@hide} */ + /** @hide */ void setDestroyed() { for (RemoteConnection connection : mChildConnections) { connection.setConference(null); } - for (Callback c : mCallbacks) { - c.onDestroyed(this); + for (CallbackRecord<Callback> record : mCallbackRecords) { + final RemoteConference conference = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onDestroyed(conference); + } + }); } } - /** {@hide} */ - void setState(int newState) { + /** @hide */ + void setState(final int newState) { if (newState != Connection.STATE_ACTIVE && newState != Connection.STATE_HOLDING && newState != Connection.STATE_DISCONNECTED) { @@ -96,42 +174,71 @@ public final class RemoteConference { } if (mState != newState) { - int oldState = mState; + final int oldState = mState; mState = newState; - for (Callback c : mCallbacks) { - c.onStateChanged(this, oldState, newState); + for (CallbackRecord<Callback> record : mCallbackRecords) { + final RemoteConference conference = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onStateChanged(conference, oldState, newState); + } + }); } } } - /** {@hide} */ - void addConnection(RemoteConnection connection) { + /** @hide */ + void addConnection(final RemoteConnection connection) { if (!mChildConnections.contains(connection)) { mChildConnections.add(connection); connection.setConference(this); - for (Callback c : mCallbacks) { - c.onConnectionAdded(this, connection); + for (CallbackRecord<Callback> record : mCallbackRecords) { + final RemoteConference conference = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onConnectionAdded(conference, connection); + } + }); } } } - /** {@hide} */ - void removeConnection(RemoteConnection connection) { + /** @hide */ + void removeConnection(final RemoteConnection connection) { if (mChildConnections.contains(connection)) { mChildConnections.remove(connection); connection.setConference(null); - for (Callback c : mCallbacks) { - c.onConnectionRemoved(this, connection); + for (CallbackRecord<Callback> record : mCallbackRecords) { + final RemoteConference conference = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onConnectionRemoved(conference, connection); + } + }); } } } - /** {@hide} */ - void setConnectionCapabilities(int connectionCapabilities) { + /** @hide */ + void setConnectionCapabilities(final int connectionCapabilities) { if (mConnectionCapabilities != connectionCapabilities) { mConnectionCapabilities = connectionCapabilities; - for (Callback c : mCallbacks) { - c.onConnectionCapabilitiesChanged(this, mConnectionCapabilities); + for (CallbackRecord<Callback> record : mCallbackRecords) { + final RemoteConference conference = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onConnectionCapabilitiesChanged( + conference, mConnectionCapabilities); + } + }); } } } @@ -140,39 +247,92 @@ public final class RemoteConference { void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) { mConferenceableConnections.clear(); mConferenceableConnections.addAll(conferenceableConnections); - for (Callback c : mCallbacks) { - c.onConferenceableConnectionsChanged(this, mUnmodifiableConferenceableConnections); + for (CallbackRecord<Callback> record : mCallbackRecords) { + final RemoteConference conference = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onConferenceableConnectionsChanged( + conference, mUnmodifiableConferenceableConnections); + } + }); } } - /** {@hide} */ - void setDisconnected(DisconnectCause disconnectCause) { + /** @hide */ + void setDisconnected(final DisconnectCause disconnectCause) { if (mState != Connection.STATE_DISCONNECTED) { mDisconnectCause = disconnectCause; setState(Connection.STATE_DISCONNECTED); - for (Callback c : mCallbacks) { - c.onDisconnected(this, disconnectCause); + for (CallbackRecord<Callback> record : mCallbackRecords) { + final RemoteConference conference = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onDisconnected(conference, disconnectCause); + } + }); } } } + /** @hide */ + void setExtras(final Bundle extras) { + mExtras = extras; + for (CallbackRecord<Callback> record : mCallbackRecords) { + final RemoteConference conference = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onExtrasChanged(conference, extras); + } + }); + } + } + + /** + * Returns the list of {@link RemoteConnection}s contained in this conference. + * + * @return A list of child connections. + */ public final List<RemoteConnection> 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; } - /** @hide */ - @Deprecated public final int getCallCapabilities() { - return getConnectionCapabilities(); - } - + /** + * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class + * {@link Connection} for valid values. + * + * @return A bitmask of the capabilities of the conference call. + */ public final int getConnectionCapabilities() { return mConnectionCapabilities; } + /** + * Obtain the extras associated with this {@code RemoteConnection}. + * + * @return The extras for this connection. + */ + public final Bundle getExtras() { + return mExtras; + } + + /** + * Disconnects the conference call as well as the child {@link RemoteConnection}s. + */ public void disconnect() { try { mConnectionService.disconnect(mId); @@ -180,6 +340,13 @@ public final class RemoteConference { } } + /** + * Removes the specified {@link RemoteConnection} from the conference. This causes the + * {@link RemoteConnection} to become a standalone connection. This is a no-op if the + * {@link RemoteConnection} does not belong to this conference. + * + * @param connection The remote-connection to remove. + */ public void separate(RemoteConnection connection) { if (mChildConnections.contains(connection)) { try { @@ -189,6 +356,16 @@ public final class RemoteConference { } } + /** + * Merges all {@link RemoteConnection}s of this conference into a single call. This should be + * invoked only if the conference contains the capability + * {@link Connection#CAPABILITY_MERGE_CONFERENCE}, otherwise it is a no-op. The presence of said + * capability indicates that the connections of this conference, despite being part of the + * same conference object, are yet to have their audio streams merged; this is a common pattern + * for CDMA conference calls, but the capability is not used for GSM and SIP conference calls. + * Invoking this method will cause the unmerged child connections to merge their audio + * streams. + */ public void merge() { try { mConnectionService.mergeConference(mId); @@ -196,6 +373,15 @@ public final class RemoteConference { } } + /** + * Swaps the active audio stream between the conference's child {@link RemoteConnection}s. + * This should be invoked only if the conference contains the capability + * {@link Connection#CAPABILITY_SWAP_CONFERENCE}, otherwise it is a no-op. This is only used by + * {@link ConnectionService}s that create conferences for connections that do not yet have + * their audio streams merged; this is a common pattern for CDMA conference calls, but the + * capability is not used for GSM and SIP conference calls. Invoking this method will change the + * active audio stream to a different child connection. + */ public void swap() { try { mConnectionService.swapConference(mId); @@ -203,6 +389,9 @@ public final class RemoteConference { } } + /** + * Puts the conference on hold. + */ public void hold() { try { mConnectionService.hold(mId); @@ -210,6 +399,9 @@ public final class RemoteConference { } } + /** + * Unholds the conference call. + */ public void unhold() { try { mConnectionService.unhold(mId); @@ -217,10 +409,22 @@ public final class RemoteConference { } } + /** + * Returns the {@link DisconnectCause} for the conference if it is in the state + * {@link Connection#STATE_DISCONNECTED}. If the conference is not disconnected, this will + * return null. + * + * @return The disconnect cause. + */ public DisconnectCause getDisconnectCause() { return mDisconnectCause; } + /** + * Requests that the conference start playing the specified DTMF tone. + * + * @param digit The digit for which to play a DTMF tone. + */ public void playDtmfTone(char digit) { try { mConnectionService.playDtmfTone(mId, digit); @@ -228,6 +432,11 @@ public final class RemoteConference { } } + /** + * Stops the most recent request to play a DTMF tone. + * + * @see #playDtmfTone + */ public void stopDtmfTone() { try { mConnectionService.stopDtmfTone(mId); @@ -235,22 +444,79 @@ public final class RemoteConference { } } + /** + * Request to change the conference's audio routing to the specified state. The specified state + * can include audio routing (Bluetooth, Speaker, etc) and muting state. + * + * @see android.telecom.AudioState + * @deprecated Use {@link #setCallAudioState(CallAudioState)} instead. + * @hide + */ + @SystemApi + @Deprecated public void setAudioState(AudioState state) { + setCallAudioState(new CallAudioState(state)); + } + + /** + * Request to change the conference's audio routing to the specified state. The specified state + * can include audio routing (Bluetooth, Speaker, etc) and muting state. + */ + public void setCallAudioState(CallAudioState state) { try { - mConnectionService.onAudioStateChanged(mId, state); + mConnectionService.onCallAudioStateChanged(mId, state); } catch (RemoteException e) { } } + + /** + * Returns a list of independent connections that can me merged with this conference. + * + * @return A list of conferenceable connections. + */ public List<RemoteConnection> getConferenceableConnections() { return mUnmodifiableConferenceableConnections; } + /** + * Register a callback through which to receive state updates for this conference. + * + * @param callback The callback to notify of state changes. + */ public final void registerCallback(Callback callback) { - mCallbacks.add(callback); + registerCallback(callback, new Handler()); + } + + /** + * Registers a callback through which to receive state updates for this conference. + * Callbacks will be notified using the specified handler, if provided. + * + * @param callback The callback to notify of state changes. + * @param handler The handler on which to execute the callbacks. + */ + public final void registerCallback(Callback callback, Handler handler) { + unregisterCallback(callback); + if (callback != null && handler != null) { + mCallbackRecords.add(new CallbackRecord(callback, handler)); + } } + /** + * Unregisters a previously registered callback. + * + * @see #registerCallback + * + * @param callback The callback to unregister. + */ public final void unregisterCallback(Callback callback) { - mCallbacks.remove(callback); + if (callback != null) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + if (record.getCallback() == callback) { + mCallbackRecords.remove(record); + break; + } + } + } } } diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java index 486691f..f960959 100644 --- a/telecomm/java/android/telecom/RemoteConnection.java +++ b/telecomm/java/android/telecom/RemoteConnection.java @@ -20,8 +20,12 @@ import com.android.internal.telecom.IConnectionService; import com.android.internal.telecom.IVideoCallback; import com.android.internal.telecom.IVideoProvider; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.hardware.camera2.CameraManager; import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.view.Surface; @@ -38,11 +42,12 @@ import java.util.concurrent.ConcurrentHashMap; * * @see ConnectionService#createRemoteOutgoingConnection(PhoneAccountHandle, ConnectionRequest) * @see ConnectionService#createRemoteIncomingConnection(PhoneAccountHandle, ConnectionRequest) - * @hide */ -@SystemApi public final class RemoteConnection { + /** + * Callback base class for {@link RemoteConnection}. + */ public static abstract class Callback { /** * Invoked when the state of this {@code RemoteConnection} has changed. See @@ -73,11 +78,6 @@ public final class RemoteConnection { */ public void onRingbackRequested(RemoteConnection connection, boolean ringback) {} - /** @hide */ - @Deprecated public void onCallCapabilitiesChanged( - RemoteConnection connection, - int callCapabilities) {} - /** * Indicates that the call capabilities of this {@code RemoteConnection} have changed. * See {@link #getConnectionCapabilities()}. @@ -156,7 +156,6 @@ public final class RemoteConnection { * * @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) {} @@ -187,7 +186,6 @@ public final class RemoteConnection { * @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) {} @@ -203,46 +201,137 @@ public final class RemoteConnection { public void onConferenceChanged( RemoteConnection connection, RemoteConference conference) {} + + /** + * Handles changes to the {@code RemoteConnection} extras. + * + * @param connection The {@code RemoteConnection} invoking this method. + * @param extras The extras containing other information associated with the connection. + */ + public void onExtrasChanged(RemoteConnection connection, @Nullable Bundle extras) {} } - /** {@hide} */ + /** + * {@link RemoteConnection.VideoProvider} associated with a {@link RemoteConnection}. Used to + * receive video related events and control the video associated with a + * {@link RemoteConnection}. + * + * @see Connection.VideoProvider + */ public static class VideoProvider { - public abstract static class Listener { - public void onReceiveSessionModifyRequest( + /** + * Callback class used by the {@link RemoteConnection.VideoProvider} to relay events from + * the {@link Connection.VideoProvider}. + */ + public abstract static class Callback { + /** + * Reports a session modification request received from the + * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. + * + * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. + * @param videoProfile The requested video call profile. + * @see InCallService.VideoCall.Callback#onSessionModifyRequestReceived(VideoProfile) + * @see Connection.VideoProvider#receiveSessionModifyRequest(VideoProfile) + */ + public void onSessionModifyRequestReceived( VideoProvider videoProvider, VideoProfile videoProfile) {} - public void onReceiveSessionModifyResponse( + /** + * Reports a session modification response received from the + * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. + * + * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. + * @param status Status of the session modify request. + * @param requestedProfile The original request which was sent to the peer device. + * @param responseProfile The actual profile changes made by the peer device. + * @see InCallService.VideoCall.Callback#onSessionModifyResponseReceived(int, + * VideoProfile, VideoProfile) + * @see Connection.VideoProvider#receiveSessionModifyResponse(int, VideoProfile, + * VideoProfile) + */ + public void onSessionModifyResponseReceived( 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) {} - + /** + * Reports a call session event received from the {@link Connection.VideoProvider} + * associated with a {@link RemoteConnection}. + * + * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. + * @param event The event. + * @see InCallService.VideoCall.Callback#onCallSessionEvent(int) + * @see Connection.VideoProvider#handleCallSessionEvent(int) + */ + public void onCallSessionEvent(VideoProvider videoProvider, int event) {} + + /** + * Reports a change in the peer video dimensions received from the + * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. + * + * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. + * @param width The updated peer video width. + * @param height The updated peer video height. + * @see InCallService.VideoCall.Callback#onPeerDimensionsChanged(int, int) + * @see Connection.VideoProvider#changePeerDimensions(int, int) + */ + public void onPeerDimensionsChanged(VideoProvider videoProvider, int width, + int height) {} + + /** + * Reports a change in the data usage (in bytes) received from the + * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. + * + * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. + * @param dataUsage The updated data usage (in bytes). + * @see InCallService.VideoCall.Callback#onCallDataUsageChanged(long) + * @see Connection.VideoProvider#setCallDataUsage(long) + */ + public void onCallDataUsageChanged(VideoProvider videoProvider, long dataUsage) {} + + /** + * Reports a change in the capabilities of the current camera, received from the + * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. + * + * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. + * @param cameraCapabilities The changed camera capabilities. + * @see InCallService.VideoCall.Callback#onCameraCapabilitiesChanged( + * VideoProfile.CameraCapabilities) + * @see Connection.VideoProvider#changeCameraCapabilities( + * VideoProfile.CameraCapabilities) + */ public void onCameraCapabilitiesChanged( VideoProvider videoProvider, - CameraCapabilities cameraCapabilities) {} + VideoProfile.CameraCapabilities cameraCapabilities) {} + + /** + * Reports a change in the video quality received from the + * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. + * + * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. + * @param videoQuality The updated peer video quality. + * @see InCallService.VideoCall.Callback#onVideoQualityChanged(int) + * @see Connection.VideoProvider#changeVideoQuality(int) + */ + public void onVideoQualityChanged(VideoProvider videoProvider, int videoQuality) {} } private final IVideoCallback mVideoCallbackDelegate = new IVideoCallback() { @Override public void receiveSessionModifyRequest(VideoProfile videoProfile) { - for (Listener l : mListeners) { - l.onReceiveSessionModifyRequest(VideoProvider.this, videoProfile); + for (Callback l : mCallbacks) { + l.onSessionModifyRequestReceived(VideoProvider.this, videoProfile); } } @Override public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile, VideoProfile responseProfile) { - for (Listener l : mListeners) { - l.onReceiveSessionModifyResponse( + for (Callback l : mCallbacks) { + l.onSessionModifyResponseReceived( VideoProvider.this, status, requestedProfile, @@ -252,33 +341,41 @@ public final class RemoteConnection { @Override public void handleCallSessionEvent(int event) { - for (Listener l : mListeners) { - l.onHandleCallSessionEvent(VideoProvider.this, event); + for (Callback l : mCallbacks) { + l.onCallSessionEvent(VideoProvider.this, event); } } @Override public void changePeerDimensions(int width, int height) { - for (Listener l : mListeners) { + for (Callback l : mCallbacks) { l.onPeerDimensionsChanged(VideoProvider.this, width, height); } } @Override - public void changeCallDataUsage(int dataUsage) { - for (Listener l : mListeners) { + public void changeCallDataUsage(long dataUsage) { + for (Callback l : mCallbacks) { l.onCallDataUsageChanged(VideoProvider.this, dataUsage); } } @Override - public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) { - for (Listener l : mListeners) { + public void changeCameraCapabilities( + VideoProfile.CameraCapabilities cameraCapabilities) { + for (Callback l : mCallbacks) { l.onCameraCapabilitiesChanged(VideoProvider.this, cameraCapabilities); } } @Override + public void changeVideoQuality(int videoQuality) { + for (Callback l : mCallbacks) { + l.onVideoQualityChanged(VideoProvider.this, videoQuality); + } + } + + @Override public IBinder asBinder() { return null; } @@ -294,25 +391,43 @@ public final class RemoteConnection { * 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 Set<Callback> mCallbacks = Collections.newSetFromMap( + new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1)); - public VideoProvider(IVideoProvider videoProviderBinder) { + VideoProvider(IVideoProvider videoProviderBinder) { mVideoProviderBinder = videoProviderBinder; try { - mVideoProviderBinder.setVideoCallback(mVideoCallbackServant.getStub().asBinder()); + mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder()); } catch (RemoteException e) { } } - public void addListener(Listener l) { - mListeners.add(l); + /** + * Registers a callback to receive commands and state changes for video calls. + * + * @param l The video call callback. + */ + public void registerCallback(Callback l) { + mCallbacks.add(l); } - public void removeListener(Listener l) { - mListeners.remove(l); + /** + * Clears the video call callback set via {@link #registerCallback}. + * + * @param l The video call callback to clear. + */ + public void unregisterCallback(Callback l) { + mCallbacks.remove(l); } + /** + * Sets the camera to be used for the outgoing video for the + * {@link RemoteConnection.VideoProvider}. + * + * @param cameraId The id of the camera (use ids as reported by + * {@link CameraManager#getCameraIdList()}). + * @see Connection.VideoProvider#onSetCamera(String) + */ public void setCamera(String cameraId) { try { mVideoProviderBinder.setCamera(cameraId); @@ -320,6 +435,13 @@ public final class RemoteConnection { } } + /** + * Sets the surface to be used for displaying a preview of what the user's camera is + * currently capturing for the {@link RemoteConnection.VideoProvider}. + * + * @param surface The {@link Surface}. + * @see Connection.VideoProvider#onSetPreviewSurface(Surface) + */ public void setPreviewSurface(Surface surface) { try { mVideoProviderBinder.setPreviewSurface(surface); @@ -327,6 +449,13 @@ public final class RemoteConnection { } } + /** + * Sets the surface to be used for displaying the video received from the remote device for + * the {@link RemoteConnection.VideoProvider}. + * + * @param surface The {@link Surface}. + * @see Connection.VideoProvider#onSetDisplaySurface(Surface) + */ public void setDisplaySurface(Surface surface) { try { mVideoProviderBinder.setDisplaySurface(surface); @@ -334,6 +463,13 @@ public final class RemoteConnection { } } + /** + * Sets the device orientation, in degrees, for the {@link RemoteConnection.VideoProvider}. + * Assumes that a standard portrait orientation of the device is 0 degrees. + * + * @param rotation The device orientation, in degrees. + * @see Connection.VideoProvider#onSetDeviceOrientation(int) + */ public void setDeviceOrientation(int rotation) { try { mVideoProviderBinder.setDeviceOrientation(rotation); @@ -341,6 +477,12 @@ public final class RemoteConnection { } } + /** + * Sets camera zoom ratio for the {@link RemoteConnection.VideoProvider}. + * + * @param value The camera zoom ratio. + * @see Connection.VideoProvider#onSetZoom(float) + */ public void setZoom(float value) { try { mVideoProviderBinder.setZoom(value); @@ -348,13 +490,28 @@ public final class RemoteConnection { } } - public void sendSessionModifyRequest(VideoProfile reqProfile) { + /** + * Issues a request to modify the properties of the current video session for the + * {@link RemoteConnection.VideoProvider}. + * + * @param fromProfile The video profile prior to the request. + * @param toProfile The video profile with the requested changes made. + * @see Connection.VideoProvider#onSendSessionModifyRequest(VideoProfile, VideoProfile) + */ + public void sendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) { try { - mVideoProviderBinder.sendSessionModifyRequest(reqProfile); + mVideoProviderBinder.sendSessionModifyRequest(fromProfile, toProfile); } catch (RemoteException e) { } } + /** + * Provides a response to a request to change the current call video session + * properties for the {@link RemoteConnection.VideoProvider}. + * + * @param responseProfile The response call video properties. + * @see Connection.VideoProvider#onSendSessionModifyResponse(VideoProfile) + */ public void sendSessionModifyResponse(VideoProfile responseProfile) { try { mVideoProviderBinder.sendSessionModifyResponse(responseProfile); @@ -362,6 +519,12 @@ public final class RemoteConnection { } } + /** + * Issues a request to retrieve the capabilities of the current camera for the + * {@link RemoteConnection.VideoProvider}. + * + * @see Connection.VideoProvider#onRequestCameraCapabilities() + */ public void requestCameraCapabilities() { try { mVideoProviderBinder.requestCameraCapabilities(); @@ -369,6 +532,12 @@ public final class RemoteConnection { } } + /** + * Issues a request to retrieve the data usage (in bytes) of the video portion of the + * {@link RemoteConnection} for the {@link RemoteConnection.VideoProvider}. + * + * @see Connection.VideoProvider#onRequestConnectionDataUsage() + */ public void requestCallDataUsage() { try { mVideoProviderBinder.requestCallDataUsage(); @@ -376,7 +545,13 @@ public final class RemoteConnection { } } - public void setPauseImage(String uri) { + /** + * Sets the {@link Uri} of an image to be displayed to the peer device when the video signal + * is paused, for the {@link RemoteConnection.VideoProvider}. + * + * @see Connection.VideoProvider#onSetPauseImage(Uri) + */ + public void setPauseImage(Uri uri) { try { mVideoProviderBinder.setPauseImage(uri); } catch (RemoteException e) { @@ -391,8 +566,8 @@ public final class RemoteConnection { * 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 Set<CallbackRecord> mCallbackRecords = Collections.newSetFromMap( + new ConcurrentHashMap<CallbackRecord, Boolean>(8, 0.9f, 1)); private final List<RemoteConnection> mConferenceableConnections = new ArrayList<>(); private final List<RemoteConnection> mUnmodifiableconferenceableConnections = Collections.unmodifiableList(mConferenceableConnections); @@ -411,6 +586,7 @@ public final class RemoteConnection { private String mCallerDisplayName; private int mCallerDisplayNamePresentation; private RemoteConference mConference; + private Bundle mExtras; /** * @hide @@ -469,7 +645,20 @@ public final class RemoteConnection { * @param callback A {@code Callback}. */ public void registerCallback(Callback callback) { - mCallbacks.add(callback); + registerCallback(callback, new Handler()); + } + + /** + * Adds a callback to this {@code RemoteConnection}. + * + * @param callback A {@code Callback}. + * @param handler A {@code Handler} which command and status changes will be delivered to. + */ + public void registerCallback(Callback callback, Handler handler) { + unregisterCallback(callback); + if (callback != null && handler != null) { + mCallbackRecords.add(new CallbackRecord(callback, handler)); + } } /** @@ -479,7 +668,12 @@ public final class RemoteConnection { */ public void unregisterCallback(Callback callback) { if (callback != null) { - mCallbacks.remove(callback); + for (CallbackRecord record : mCallbackRecords) { + if (record.getCallback() == callback) { + mCallbackRecords.remove(record); + break; + } + } } } @@ -575,8 +769,7 @@ public final class RemoteConnection { /** * Obtains the video state of this {@code RemoteConnection}. * - * @return The video state of the {@code RemoteConnection}. See {@link VideoProfile.VideoState}. - * @hide + * @return The video state of the {@code RemoteConnection}. See {@link VideoProfile}. */ public int getVideoState() { return mVideoState; @@ -584,22 +777,29 @@ public final class RemoteConnection { /** * Obtains the video provider of this {@code RemoteConnection}. - * * @return The video provider associated with this {@code RemoteConnection}. - * @hide */ public final VideoProvider getVideoProvider() { return mVideoProvider; } /** + * Obtain the extras associated with this {@code RemoteConnection}. + * + * @return The extras for this connection. + */ + public final Bundle getExtras() { + return mExtras; + } + + /** * Determines whether this {@code RemoteConnection} is requesting ringback. * * @return Whether the {@code RemoteConnection} is requesting that the framework play a * ringback tone on its behalf. */ public boolean isRingbackRequested() { - return false; + return mRingbackRequested; } /** @@ -756,11 +956,24 @@ public final class RemoteConnection { * Set the audio state of this {@code RemoteConnection}. * * @param state The audio state of this {@code RemoteConnection}. + * @hide + * @deprecated Use {@link #setCallAudioState(CallAudioState) instead. */ + @SystemApi + @Deprecated public void setAudioState(AudioState state) { + setCallAudioState(new CallAudioState(state)); + } + + /** + * Set the audio state of this {@code RemoteConnection}. + * + * @param state The audio state of this {@code RemoteConnection}. + */ + public void setCallAudioState(CallAudioState state) { try { if (mConnected) { - mConnectionService.onAudioStateChanged(mConnectionId, state); + mConnectionService.onCallAudioStateChanged(mConnectionId, state); } } catch (RemoteException ignored) { } @@ -800,11 +1013,18 @@ public final class RemoteConnection { /** * @hide */ - void setState(int state) { + void setState(final int state) { if (mState != state) { mState = state; - for (Callback c: mCallbacks) { - c.onStateChanged(this, state); + for (CallbackRecord record: mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onStateChanged(connection, state); + } + }); } } } @@ -812,13 +1032,20 @@ public final class RemoteConnection { /** * @hide */ - void setDisconnected(DisconnectCause disconnectCause) { + void setDisconnected(final DisconnectCause disconnectCause) { if (mState != Connection.STATE_DISCONNECTED) { mState = Connection.STATE_DISCONNECTED; mDisconnectCause = disconnectCause; - for (Callback c : mCallbacks) { - c.onDisconnected(this, mDisconnectCause); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onDisconnected(connection, disconnectCause); + } + }); } } } @@ -826,11 +1053,18 @@ public final class RemoteConnection { /** * @hide */ - void setRingbackRequested(boolean ringback) { + void setRingbackRequested(final boolean ringback) { if (mRingbackRequested != ringback) { mRingbackRequested = ringback; - for (Callback c : mCallbacks) { - c.onRingbackRequested(this, ringback); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onRingbackRequested(connection, ringback); + } + }); } } } @@ -838,11 +1072,17 @@ public final class RemoteConnection { /** * @hide */ - void setConnectionCapabilities(int connectionCapabilities) { + void setConnectionCapabilities(final int connectionCapabilities) { mConnectionCapabilities = connectionCapabilities; - for (Callback c : mCallbacks) { - c.onConnectionCapabilitiesChanged(this, connectionCapabilities); - c.onCallCapabilitiesChanged(this, connectionCapabilities); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onConnectionCapabilitiesChanged(connection, connectionCapabilities); + } + }); } } @@ -850,17 +1090,24 @@ public final class RemoteConnection { * @hide */ void setDestroyed() { - if (!mCallbacks.isEmpty()) { + if (!mCallbackRecords.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); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onDestroyed(connection); + } + }); } - mCallbacks.clear(); + mCallbackRecords.clear(); mConnected = false; } @@ -869,94 +1116,181 @@ public final class RemoteConnection { /** * @hide */ - void setPostDialWait(String remainingDigits) { - for (Callback c : mCallbacks) { - c.onPostDialWait(this, remainingDigits); + void setPostDialWait(final String remainingDigits) { + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onPostDialWait(connection, remainingDigits); + } + }); } } /** * @hide */ - void onPostDialChar(char nextChar) { - for (Callback c : mCallbacks) { - c.onPostDialChar(this, nextChar); + void onPostDialChar(final char nextChar) { + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onPostDialChar(connection, nextChar); + } + }); } } /** * @hide */ - void setVideoState(int videoState) { + void setVideoState(final int videoState) { mVideoState = videoState; - for (Callback c : mCallbacks) { - c.onVideoStateChanged(this, videoState); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onVideoStateChanged(connection, videoState); + } + }); } } /** * @hide */ - void setVideoProvider(VideoProvider videoProvider) { + void setVideoProvider(final VideoProvider videoProvider) { mVideoProvider = videoProvider; - for (Callback c : mCallbacks) { - c.onVideoProviderChanged(this, videoProvider); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onVideoProviderChanged(connection, videoProvider); + } + }); } } /** @hide */ - void setIsVoipAudioMode(boolean isVoip) { + void setIsVoipAudioMode(final boolean isVoip) { mIsVoipAudioMode = isVoip; - for (Callback c : mCallbacks) { - c.onVoipAudioChanged(this, isVoip); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onVoipAudioChanged(connection, isVoip); + } + }); } } /** @hide */ - void setStatusHints(StatusHints statusHints) { + void setStatusHints(final StatusHints statusHints) { mStatusHints = statusHints; - for (Callback c : mCallbacks) { - c.onStatusHintsChanged(this, statusHints); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onStatusHintsChanged(connection, statusHints); + } + }); } } /** @hide */ - void setAddress(Uri address, int presentation) { + void setAddress(final Uri address, final int presentation) { mAddress = address; mAddressPresentation = presentation; - for (Callback c : mCallbacks) { - c.onAddressChanged(this, address, presentation); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onAddressChanged(connection, address, presentation); + } + }); } } /** @hide */ - void setCallerDisplayName(String callerDisplayName, int presentation) { + void setCallerDisplayName(final String callerDisplayName, final int presentation) { mCallerDisplayName = callerDisplayName; mCallerDisplayNamePresentation = presentation; - for (Callback c : mCallbacks) { - c.onCallerDisplayNameChanged(this, callerDisplayName, presentation); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onCallerDisplayNameChanged( + connection, callerDisplayName, presentation); + } + }); } } /** @hide */ - void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) { + void setConferenceableConnections(final List<RemoteConnection> conferenceableConnections) { mConferenceableConnections.clear(); mConferenceableConnections.addAll(conferenceableConnections); - for (Callback c : mCallbacks) { - c.onConferenceableConnectionsChanged(this, mUnmodifiableconferenceableConnections); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onConferenceableConnectionsChanged( + connection, mUnmodifiableconferenceableConnections); + } + }); } } /** @hide */ - void setConference(RemoteConference conference) { + void setConference(final RemoteConference conference) { if (mConference != conference) { mConference = conference; - for (Callback c : mCallbacks) { - c.onConferenceChanged(this, conference); + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onConferenceChanged(connection, conference); + } + }); } } } + /** @hide */ + void setExtras(final Bundle extras) { + mExtras = extras; + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post(new Runnable() { + @Override + public void run() { + callback.onExtrasChanged(connection, extras); + } + }); + } + } + /** * Create a RemoteConnection represents a failure, and which will be in * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost @@ -969,4 +1303,22 @@ public final class RemoteConnection { public static RemoteConnection failure(DisconnectCause disconnectCause) { return new RemoteConnection(disconnectCause); } + + private static final class CallbackRecord extends Callback { + private final Callback mCallback; + private final Handler mHandler; + + public CallbackRecord(Callback callback, Handler handler) { + mCallback = callback; + mHandler = handler; + } + + public Callback getCallback() { + return mCallback; + } + + public Handler getHandler() { + return mHandler; + } + } } diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index 43a92cb..dc0de0c 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -17,6 +17,7 @@ package android.telecom; import android.net.Uri; +import android.os.Bundle; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.RemoteException; @@ -60,11 +61,16 @@ final class RemoteConnectionService { mPendingConnections.remove(connection); // Unconditionally initialize the connection ... connection.setConnectionCapabilities(parcel.getConnectionCapabilities()); - connection.setAddress( - parcel.getHandle(), parcel.getHandlePresentation()); - connection.setCallerDisplayName( - parcel.getCallerDisplayName(), - parcel.getCallerDisplayNamePresentation()); + if (parcel.getHandle() != null + || parcel.getState() != Connection.STATE_DISCONNECTED) { + connection.setAddress(parcel.getHandle(), parcel.getHandlePresentation()); + } + if (parcel.getCallerDisplayName() != null + || parcel.getState() != Connection.STATE_DISCONNECTED) { + connection.setCallerDisplayName( + parcel.getCallerDisplayName(), + parcel.getCallerDisplayNamePresentation()); + } // Set state after handle so that the client can identify the connection. if (parcel.getState() == Connection.STATE_DISCONNECTED) { connection.setDisconnected(parcel.getDisconnectCause()); @@ -171,6 +177,13 @@ final class RemoteConnectionService { } @Override + public void setConferenceMergeFailed(String callId) { + // Nothing to do here. + // The event has already been handled and there is no state to update + // in the underlying connection or conference objects + } + + @Override public void addConferenceCall( final String callId, ParcelableConference parcel) { @@ -306,6 +319,17 @@ final class RemoteConnectionService { mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnction); } + + @Override + public void setExtras(String callId, Bundle extras) { + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "setExtras") + .setExtras(extras); + } else { + findConferenceForAction(callId, "setExtras") + .setExtras(extras); + } + } }; private final ConnectionServiceAdapterServant mServant = diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java index dd3a639..453f408 100644 --- a/telecomm/java/android/telecom/StatusHints.java +++ b/telecomm/java/android/telecom/StatusHints.java @@ -19,40 +19,48 @@ package android.telecom; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; 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. - * @hide */ -@SystemApi public final class StatusHints implements Parcelable { - private final ComponentName mPackageName; private final CharSequence mLabel; - private final int mIconResId; + private final Icon mIcon; private final Bundle mExtras; + /** + * @hide + */ + @SystemApi @Deprecated public StatusHints(ComponentName packageName, CharSequence label, int iconResId, Bundle extras) { - mPackageName = packageName; + this(label, iconResId == 0 ? null : Icon.createWithResource(packageName.getPackageName(), + iconResId), extras); + } + + public StatusHints(CharSequence label, Icon icon, Bundle extras) { mLabel = label; - mIconResId = iconResId; + mIcon = icon; mExtras = extras; } /** * @return A package used to load the icon. + * + * @hide */ + @SystemApi @Deprecated public ComponentName getPackageName() { - return mPackageName; + // Minimal compatibility shim for legacy apps' tests + return new ComponentName("", ""); } /** @@ -66,16 +74,30 @@ public final class StatusHints implements Parcelable { * The icon resource ID for the icon to show. * * @return A resource ID. + * + * @hide */ + @SystemApi @Deprecated public int getIconResId() { - return mIconResId; + // Minimal compatibility shim for legacy apps' tests + return 0; } /** * @return An icon displayed in the in-call UI. + * + * @hide */ + @SystemApi @Deprecated public Drawable getIcon(Context context) { - return getIcon(context, mIconResId); + return mIcon.loadDrawable(context); + } + + /** + * @return An icon depicting the status. + */ + public Icon getIcon() { + return mIcon; } /** @@ -92,9 +114,8 @@ public final class StatusHints implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mPackageName, flags); out.writeCharSequence(mLabel); - out.writeInt(mIconResId); + out.writeParcelable(mIcon, 0); out.writeParcelable(mExtras, 0); } @@ -110,36 +131,17 @@ public final class StatusHints implements Parcelable { }; private StatusHints(Parcel in) { - mPackageName = in.readParcelable(getClass().getClassLoader()); mLabel = in.readCharSequence(); - mIconResId = in.readInt(); + mIcon = in.readParcelable(getClass().getClassLoader()); 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() && + return Objects.equals(otherHints.getLabel(), getLabel()) && + Objects.equals(otherHints.getIcon(), getIcon()) && Objects.equals(otherHints.getExtras(), getExtras()); } return false; @@ -147,7 +149,6 @@ public final class StatusHints implements Parcelable { @Override public int hashCode() { - return Objects.hashCode(mPackageName) + Objects.hashCode(mLabel) + mIconResId + - Objects.hashCode(mExtras); + return Objects.hashCode(mLabel) + Objects.hashCode(mIcon) + Objects.hashCode(mExtras); } } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 1a6b292..a30e1c0 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -17,6 +17,7 @@ package android.telecom; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; @@ -55,8 +56,6 @@ public class TelecomManager { * 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"; @@ -68,13 +67,25 @@ public class TelecomManager { public static final String ACTION_NEW_UNKNOWN_CALL = "android.telecom.action.NEW_UNKNOWN_CALL"; /** - * The {@link android.content.Intent} action used to configure a - * {@link android.telecom.ConnectionService}. - * @hide + * An {@link android.content.Intent} action sent by the telecom framework to start a + * configuration dialog for a registered {@link PhoneAccount}. There is no default dialog + * and each app that registers a {@link PhoneAccount} should provide one if desired. + * <p> + * A user can access the list of enabled {@link android.telecom.PhoneAccount}s through the Phone + * app's settings menu. For each entry, the settings app will add a click action. When + * triggered, the click-action will start this intent along with the extra + * {@link #EXTRA_PHONE_ACCOUNT_HANDLE} to indicate the {@link PhoneAccount} to configure. If the + * {@link PhoneAccount} package does not register an {@link android.app.Activity} for this + * intent, then it will not be sent. */ - @SystemApi - public static final String ACTION_CONNECTION_SERVICE_CONFIGURE = - "android.telecom.action.CONNECTION_SERVICE_CONFIGURE"; + public static final String ACTION_CONFIGURE_PHONE_ACCOUNT = + "android.telecom.action.CONFIGURE_PHONE_ACCOUNT"; + + /** + * The {@link android.content.Intent} action used to show the call accessibility settings page. + */ + public static final String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = + "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS"; /** * The {@link android.content.Intent} action used to show the call settings page. @@ -83,15 +94,60 @@ public class TelecomManager { "android.telecom.action.SHOW_CALL_SETTINGS"; /** + * The {@link android.content.Intent} action used to show the respond via SMS settings page. + */ + public static final String ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS = + "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS"; + + /** * The {@link android.content.Intent} action used to show the settings page used to configure * {@link PhoneAccount} preferences. - * @hide */ - @SystemApi public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; /** + * The {@link android.content.Intent} action used indicate that a new phone account was + * just registered. + * @hide + */ + @SystemApi + public static final String ACTION_PHONE_ACCOUNT_REGISTERED = + "android.telecom.action.PHONE_ACCOUNT_REGISTERED"; + + /** + * Activity action: Shows a dialog asking the user whether or not they want to replace the + * current default Dialer with the one specified in + * {@link #EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME}. + * + * Usage example: + * <pre> + * Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER); + * intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, + * getActivity().getPackageName()); + * startActivity(intent); + * </pre> + */ + public static final String ACTION_CHANGE_DEFAULT_DIALER = + "android.telecom.action.CHANGE_DEFAULT_DIALER"; + + /** + * Broadcast intent action indicating that the current default dialer has changed. + * The string extra {@link #EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME} will contain the + * name of the package that the default dialer was changed to. + * + * @see #EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME + */ + public static final String ACTION_DEFAULT_DIALER_CHANGED = + "android.telecom.action.DEFAULT_DIALER_CHANGED"; + + /** + * Extra value used to provide the package name for {@link #ACTION_CHANGE_DEFAULT_DIALER}. + */ + public static final String EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = + "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME"; + + /** * 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. */ @@ -102,11 +158,10 @@ public class TelecomManager { * 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 + * {@link VideoProfile#STATE_AUDIO_ONLY}, + * {@link VideoProfile#STATE_BIDIRECTIONAL}, + * {@link VideoProfile#STATE_RX_ENABLED}, + * {@link VideoProfile#STATE_TX_ENABLED}. */ public static final String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE"; @@ -117,20 +172,22 @@ public class TelecomManager { * {@link PhoneAccountHandle} to use when making the call. * <p class="note"> * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}. - * @hide */ - @SystemApi public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE"; /** + * The extra used by a {@link ConnectionService} to provide the handle of the caller that + * has initiated a new incoming call. + */ + public static final String EXTRA_INCOMING_CALL_ADDRESS = + "android.telecom.extra.INCOMING_CALL_ADDRESS"; + + /** * 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 */ - @SystemApi public static final String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS"; @@ -138,11 +195,8 @@ public class TelecomManager { * 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 + * {@code Call.Details} and passed to the {@link ConnectionService} when placing the call. */ - @SystemApi public static final String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS"; @@ -206,12 +260,18 @@ public class TelecomManager { * {@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. - * @hide */ - @SystemApi public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER"; /** + * A boolean meta-data value indicating whether an {@link InCallService} implements an + * in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which + * would also like to replace the in-call interface should set this meta-data to {@code true} in + * the manifest registration of their {@link InCallService}. + */ + public static final String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI"; + + /** * The dual tone multi-frequency signaling character sent to indicate the dialing system should * pause for a predefined period. */ @@ -304,16 +364,24 @@ public class TelecomManager { * displayed to the user. */ - /** Property is displayed normally. */ + /** + * Indicates that the address or number of a call is allowed to be displayed for caller ID. + */ public static final int PRESENTATION_ALLOWED = 1; - /** Property was blocked. */ + /** + * Indicates that the address or number of a call is blocked by the other party. + */ public static final int PRESENTATION_RESTRICTED = 2; - /** Presentation was not specified or is unknown. */ + /** + * Indicates that the address or number of a call is not specified or known by the carrier. + */ public static final int PRESENTATION_UNKNOWN = 3; - /** Property should be displayed as a pay phone. */ + /** + * Indicates that the address or number of a call belongs to a pay phone. + */ public static final int PRESENTATION_PAYPHONE = 4; private static final String TAG = "TelecomManager"; @@ -340,22 +408,29 @@ public class TelecomManager { } /** - * Return the {@link PhoneAccount} which is the user-chosen default for making outgoing phone - * calls with a specified URI scheme. - * <p> - * Apps must be prepared for this method to return {@code null}, indicating that there currently - * exists no user-chosen default {@code PhoneAccount}. + * Return the {@link PhoneAccount} which will be used to place outgoing calls to addresses with + * the specified {@code uriScheme}. This {@link PhoneAccount} will always be a member of the + * list which is returned from invoking {@link #getCallCapablePhoneAccounts()}. The specific + * account returned depends on the following priorities: + * <ul> + * <li> If the user-selected default {@link PhoneAccount} supports the specified scheme, it will + * be returned. + * </li> + * <li> If there exists only one {@link PhoneAccount} that supports the specified scheme, it + * will be returned. + * </li> + * </ul> * <p> + * If no {@link PhoneAccount} fits the criteria above, this method will return {@code null}. + * * @param uriScheme The URI scheme. - * @return The {@link PhoneAccountHandle} corresponding to the user-chosen default for outgoing - * phone calls for a specified URI scheme. - * @hide + * @return The {@link PhoneAccountHandle} corresponding to the account to be used. */ - @SystemApi public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) { try { if (isServiceConnected()) { - return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme); + return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme, + mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e); @@ -367,7 +442,7 @@ public class TelecomManager { * 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()} - * + * <p> * Apps must be prepared for this method to return {@code null}, indicating that there currently * exists no user-chosen default {@code PhoneAccount}. * @@ -386,7 +461,7 @@ public class TelecomManager { } /** - * Sets the default account for making outgoing phone calls. + * Sets the user-chosen default for making outgoing phone calls. * @hide */ public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { @@ -403,8 +478,8 @@ public class TelecomManager { * 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 { @@ -418,37 +493,6 @@ public class TelecomManager { } /** - * 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}. @@ -462,16 +506,6 @@ public class TelecomManager { } /** - * Returns the list of registered SIM call managers. - * @return List of registered SIM call managers. - * @hide - */ - @SystemApi - public List<PhoneAccountHandle> getRegisteredConnectionManagers() { - return getSimCallManagers(); - } - - /** * 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> @@ -488,7 +522,8 @@ public class TelecomManager { public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) { try { if (isServiceConnected()) { - return getTelecomService().getPhoneAccountsSupportingScheme(uriScheme); + return getTelecomService().getPhoneAccountsSupportingScheme(uriScheme, + mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getPhoneAccountsSupportingScheme", e); @@ -498,39 +533,38 @@ public class TelecomManager { /** - * Return a list of {@link PhoneAccountHandle}s which can be used to make and receive phone - * calls. + * Returns a list of {@link PhoneAccountHandle}s which can be used to make and receive phone + * calls. The returned list includes only those accounts which have been explicitly enabled + * by the user. * * @see #EXTRA_PHONE_ACCOUNT_HANDLE * @return A list of {@code PhoneAccountHandle} objects. + */ + public List<PhoneAccountHandle> getCallCapablePhoneAccounts() { + return getCallCapablePhoneAccounts(false); + } + + /** + * Returns a list of {@link PhoneAccountHandle}s including those which have not been enabled + * by the user. * + * @return A list of {@code PhoneAccountHandle} objects. * @hide */ - public List<PhoneAccountHandle> getCallCapablePhoneAccounts() { + public List<PhoneAccountHandle> getCallCapablePhoneAccounts(boolean includeDisabledAccounts) { try { if (isServiceConnected()) { - return getTelecomService().getCallCapablePhoneAccounts(); + return getTelecomService().getCallCapablePhoneAccounts( + includeDisabledAccounts, mContext.getOpPackageName()); } } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts", e); + Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" + + includeDisabledAccounts + ")", 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. - * @hide - */ - @SystemApi - public boolean hasMultipleCallCapableAccounts() { - return getCallCapablePhoneAccounts().size() > 1; - } - - /** * Returns a list of all {@link PhoneAccount}s registered for the calling package. * * @return A list of {@code PhoneAccountHandle} objects. @@ -554,9 +588,7 @@ public class TelecomManager { * * @param account The {@link PhoneAccountHandle}. * @return The {@link PhoneAccount} object. - * @hide */ - @SystemApi public PhoneAccount getPhoneAccount(PhoneAccountHandle account) { try { if (isServiceConnected()) { @@ -635,10 +667,7 @@ public class TelecomManager { * {@link PhoneAccountHandle#getComponentName()} does not match the package name of the app. * * @param account The complete {@link PhoneAccount}. - * - * @hide */ - @SystemApi public void registerPhoneAccount(PhoneAccount account) { try { if (isServiceConnected()) { @@ -653,9 +682,7 @@ public class TelecomManager { * Remove a {@link PhoneAccount} registration from the system. * * @param accountHandle A {@link PhoneAccountHandle} for the {@link PhoneAccount} to unregister. - * @hide */ - @SystemApi public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { @@ -671,6 +698,15 @@ public class TelecomManager { * @hide */ @SystemApi + public void clearPhoneAccounts() { + clearAccounts(); + } + /** + * Remove all Accounts that belong to the calling package from the system. + * @deprecated Use {@link #clearPhoneAccounts()} instead. + * @hide + */ + @SystemApi public void clearAccounts() { try { if (isServiceConnected()) { @@ -695,7 +731,10 @@ public class TelecomManager { } } + /** + * @deprecated - Use {@link TelecomManager#getDefaultDialerPackage} to directly access + * the default dialer's package name instead. * @hide */ @SystemApi @@ -711,19 +750,76 @@ public class TelecomManager { } /** + * Used to determine the currently selected default dialer package. + * + * @return package name for the default dialer package or null if no package has been + * selected as the default dialer. + */ + public String getDefaultDialerPackage() { + try { + if (isServiceConnected()) { + return getTelecomService().getDefaultDialerPackage(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException attempting to get the default dialer package name.", e); + } + return null; + } + + /** + * Used to set the default dialer package. + * + * @param packageName to set the default dialer to.. + * + * @result {@code true} if the default dialer was successfully changed, {@code false} if + * the specified package does not correspond to an installed dialer, or is already + * the default dialer. + * + * Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} + * Requires permission: {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} + * + * @hide + */ + public boolean setDefaultDialer(String packageName) { + try { + if (isServiceConnected()) { + return getTelecomService().setDefaultDialer(packageName); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException attempting to set the default dialer.", e); + } + return false; + } + + /** + * Used to determine the dialer package that is preloaded on the system partition. + * + * @return package name for the system dialer package or null if no system dialer is preloaded. + * @hide + */ + public String getSystemDialerPackage() { + try { + if (isServiceConnected()) { + return getTelecomService().getSystemDialerPackage(); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException attempting to get the system dialer package name.", e); + } + return null; + } + + /** * Return whether a given phone number is the configured voicemail number for a * particular phone account. * * @param accountHandle The handle for the account to check the voicemail number against * @param number The number to look up. - * - * @hide */ - @SystemApi public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) { try { if (isServiceConnected()) { - return getTelecomService().isVoiceMailNumber(accountHandle, number); + return getTelecomService().isVoiceMailNumber(accountHandle, number, + mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#isVoiceMailNumber.", e); @@ -732,23 +828,22 @@ public class TelecomManager { } /** - * Return whether a given phone account has a voicemail number configured. - * - * @param accountHandle The handle for the account to check for a voicemail number. - * @return {@code true} If the given phone account has a voicemail number. + * Return the voicemail number for a given phone account. * - * @hide + * @param accountHandle The handle for the phone account. + * @return The voicemail number for the phone account, and {@code null} if one has not been + * configured. */ - @SystemApi - public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) { + public String getVoiceMailNumber(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { - return getTelecomService().hasVoiceMailNumber(accountHandle); + return getTelecomService().getVoiceMailNumber(accountHandle, + mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#hasVoiceMailNumber.", e); } - return false; + return null; } /** @@ -756,14 +851,12 @@ public class TelecomManager { * * @param accountHandle The handle for the account retrieve a number for. * @return A string representation of the line 1 phone number. - * - * @hide */ - @SystemApi public String getLine1Number(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { - return getTelecomService().getLine1Number(accountHandle); + return getTelecomService().getLine1Number(accountHandle, + mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#getLine1Number.", e); @@ -781,7 +874,7 @@ public class TelecomManager { public boolean isInCall() { try { if (isServiceConnected()) { - return getTelecomService().isInCall(); + return getTelecomService().isInCall(mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling isInCall().", e); @@ -823,7 +916,7 @@ public class TelecomManager { public boolean isRinging() { try { if (isServiceConnected()) { - return getTelecomService().isRinging(); + return getTelecomService().isRinging(mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException attempting to get ringing state of phone app.", e); @@ -869,14 +962,11 @@ public class TelecomManager { /** * Silences the ringer if a ringing call exists. - * - * @hide */ - @SystemApi public void silenceRinger() { try { if (isServiceConnected()) { - getTelecomService().silenceRinger(); + getTelecomService().silenceRinger(mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#silenceRinger", e); @@ -892,7 +982,7 @@ public class TelecomManager { public boolean isTtySupported() { try { if (isServiceConnected()) { - return getTelecomService().isTtySupported(); + return getTelecomService().isTtySupported(mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException attempting to get TTY supported state.", e); @@ -913,7 +1003,7 @@ public class TelecomManager { public int getCurrentTtyMode() { try { if (isServiceConnected()) { - return getTelecomService().getCurrentTtyMode(); + return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e); @@ -933,9 +1023,7 @@ public class TelecomManager { * {@link #registerPhoneAccount}. * @param extras A bundle that will be passed through to * {@link ConnectionService#onCreateIncomingConnection}. - * @hide */ - @SystemApi public void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) { try { if (isServiceConnected()) { @@ -986,7 +1074,7 @@ public class TelecomManager { ITelecomService service = getTelecomService(); if (service != null) { try { - return service.handlePinMmi(dialString); + return service.handlePinMmi(dialString, mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#handlePinMmi", e); } @@ -1005,14 +1093,13 @@ public class TelecomManager { * @param accountHandle The handle for the account the MMI code should apply to. * @param dialString The digits to dial. * @return True if the digits were processed as an MMI code, false otherwise. - * @hide */ - @SystemApi - public boolean handleMmi(PhoneAccountHandle accountHandle, String dialString) { + public boolean handleMmi(String dialString, PhoneAccountHandle accountHandle) { ITelecomService service = getTelecomService(); if (service != null) { try { - return service.handlePinMmiForPhoneAccount(accountHandle, dialString); + return service.handlePinMmiForPhoneAccount(accountHandle, dialString, + mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#handlePinMmi", e); } @@ -1025,14 +1112,12 @@ public class TelecomManager { * {@code null} to return a URI which will use the default account. * @return The URI (with the content:// scheme) specific to the specified {@link PhoneAccount} * for the the content retrieve. - * @hide */ - @SystemApi public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) { ITelecomService service = getTelecomService(); if (service != null && accountHandle != null) { try { - return service.getAdnUriForPhoneAccount(accountHandle); + return service.getAdnUriForPhoneAccount(accountHandle, mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getAdnUriForPhoneAccount", e); } @@ -1050,7 +1135,7 @@ public class TelecomManager { ITelecomService service = getTelecomService(); if (service != null) { try { - service.cancelMissedCallsNotification(); + service.cancelMissedCallsNotification(mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#cancelMissedCallsNotification", e); } @@ -1071,13 +1156,77 @@ public class TelecomManager { ITelecomService service = getTelecomService(); if (service != null) { try { - service.showInCallScreen(showDialpad); + service.showInCallScreen(showDialpad, mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#showCallScreen", e); } } } + /** + * Places a new outgoing call to the provided address using the system telecom service with + * the specified extras. + * + * This method is equivalent to placing an outgoing call using {@link Intent#ACTION_CALL}, + * except that the outgoing call will always be sent via the system telecom service. If + * method-caller is either the user selected default dialer app or preloaded system dialer + * app, then emergency calls will also be allowed. + * + * Requires permission: {@link android.Manifest.permission#CALL_PHONE} + * + * Usage example: + * <pre> + * Uri uri = Uri.fromParts("tel", "12345", null); + * Bundle extras = new Bundle(); + * extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true); + * telecomManager.placeCall(uri, extras); + * </pre> + * + * The following keys are supported in the supplied extras. + * <ul> + * <li>{@link #EXTRA_OUTGOING_CALL_EXTRAS}</li> + * <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li> + * <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li> + * <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li> + * </ul> + * + * @param address The address to make the call to. + * @param extras Bundle of extras to use with the call. + */ + public void placeCall(Uri address, Bundle extras) { + ITelecomService service = getTelecomService(); + if (service != null) { + if (address == null) { + Log.w(TAG, "Cannot place call to empty address."); + } + try { + service.placeCall(address, extras == null ? new Bundle() : extras, + mContext.getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#placeCall", e); + } + } + } + + /** + * Enables and disables specified phone account. + * + * @param handle Handle to the phone account. + * @param isEnabled Enable state of the phone account. + * @hide + */ + @SystemApi + public void enablePhoneAccount(PhoneAccountHandle handle, boolean isEnabled) { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + service.enablePhoneAccount(handle, isEnabled); + } catch (RemoteException e) { + Log.e(TAG, "Error enablePhoneAbbount", e); + } + } + } + private ITelecomService getTelecomService() { return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE)); } diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java index 925058e..c8072d1 100644 --- a/telecomm/java/android/telecom/VideoCallImpl.java +++ b/telecomm/java/android/telecom/VideoCallImpl.java @@ -16,6 +16,7 @@ package android.telecom; +import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -36,16 +37,12 @@ import com.android.internal.telecom.IVideoProvider; * {@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 VideoCall.Callback mCallback; + private int mVideoQuality = VideoProfile.QUALITY_UNKNOWN; + private Call mCall; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override @@ -60,7 +57,7 @@ public class VideoCallImpl extends VideoCall { private final class VideoCallListenerBinder extends IVideoCallback.Stub { @Override public void receiveSessionModifyRequest(VideoProfile videoProfile) { - mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_REQUEST, + mHandler.obtainMessage(MessageHandler.MSG_RECEIVE_SESSION_MODIFY_REQUEST, videoProfile).sendToTarget(); } @@ -71,12 +68,14 @@ public class VideoCallImpl extends VideoCall { args.arg1 = status; args.arg2 = requestProfile; args.arg3 = responseProfile; - mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args).sendToTarget(); + mHandler.obtainMessage(MessageHandler.MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args) + .sendToTarget(); } @Override public void handleCallSessionEvent(int event) { - mHandler.obtainMessage(MSG_HANDLE_CALL_SESSION_EVENT, event).sendToTarget(); + mHandler.obtainMessage(MessageHandler.MSG_HANDLE_CALL_SESSION_EVENT, event) + .sendToTarget(); } @Override @@ -84,33 +83,52 @@ public class VideoCallImpl extends VideoCall { SomeArgs args = SomeArgs.obtain(); args.arg1 = width; args.arg2 = height; - mHandler.obtainMessage(MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget(); + mHandler.obtainMessage(MessageHandler.MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget(); } @Override - public void changeCallDataUsage(int dataUsage) { - mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, dataUsage).sendToTarget(); + public void changeVideoQuality(int videoQuality) { + mHandler.obtainMessage(MessageHandler.MSG_CHANGE_VIDEO_QUALITY, videoQuality, 0) + .sendToTarget(); } @Override - public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) { - mHandler.obtainMessage(MSG_CHANGE_CAMERA_CAPABILITIES, + public void changeCallDataUsage(long dataUsage) { + mHandler.obtainMessage(MessageHandler.MSG_CHANGE_CALL_DATA_USAGE, dataUsage) + .sendToTarget(); + } + + @Override + public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) { + mHandler.obtainMessage(MessageHandler.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()) { + private final class MessageHandler extends Handler { + 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 static final int MSG_CHANGE_VIDEO_QUALITY = 7; + + public MessageHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { - if (mVideoCallListener == null) { + if (mCallback == null) { return; } SomeArgs args; switch (msg.what) { case MSG_RECEIVE_SESSION_MODIFY_REQUEST: - mVideoCallListener.onSessionModifyRequestReceived((VideoProfile) msg.obj); + mCallback.onSessionModifyRequestReceived((VideoProfile) msg.obj); break; case MSG_RECEIVE_SESSION_MODIFY_RESPONSE: args = (SomeArgs) msg.obj; @@ -119,31 +137,35 @@ public class VideoCallImpl extends VideoCall { VideoProfile requestProfile = (VideoProfile) args.arg2; VideoProfile responseProfile = (VideoProfile) args.arg3; - mVideoCallListener.onSessionModifyResponseReceived( + mCallback.onSessionModifyResponseReceived( status, requestProfile, responseProfile); } finally { args.recycle(); } break; case MSG_HANDLE_CALL_SESSION_EVENT: - mVideoCallListener.onCallSessionEvent((int) msg.obj); + mCallback.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); + mCallback.onPeerDimensionsChanged(width, height); } finally { args.recycle(); } break; case MSG_CHANGE_CALL_DATA_USAGE: - mVideoCallListener.onCallDataUsageChanged(msg.arg1); + mCallback.onCallDataUsageChanged((long) msg.obj); break; case MSG_CHANGE_CAMERA_CAPABILITIES: - mVideoCallListener.onCameraCapabilitiesChanged( - (CameraCapabilities) msg.obj); + mCallback.onCameraCapabilitiesChanged( + (VideoProfile.CameraCapabilities) msg.obj); + break; + case MSG_CHANGE_VIDEO_QUALITY: + mVideoQuality = msg.arg1; + mCallback.onVideoQualityChanged(msg.arg1); break; default: break; @@ -151,18 +173,47 @@ public class VideoCallImpl extends VideoCall { } }; - /** {@hide} */ - VideoCallImpl(IVideoProvider videoProvider) throws RemoteException { + private Handler mHandler; + + VideoCallImpl(IVideoProvider videoProvider, Call call) throws RemoteException { mVideoProvider = videoProvider; mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0); mBinder = new VideoCallListenerBinder(); - mVideoProvider.setVideoCallback(mBinder); + mVideoProvider.addVideoCallback(mBinder); + mCall = call; + } + + public void destroy() { + unregisterCallback(mCallback); + } + + /** {@inheritDoc} */ + public void registerCallback(VideoCall.Callback callback) { + registerCallback(callback, null); } /** {@inheritDoc} */ - public void setVideoCallListener(VideoCall.Listener videoCallListener) { - mVideoCallListener = videoCallListener; + public void registerCallback(VideoCall.Callback callback, Handler handler) { + mCallback = callback; + if (handler == null) { + mHandler = new MessageHandler(Looper.getMainLooper()); + } else { + mHandler = new MessageHandler(handler.getLooper()); + } + } + + /** {@inheritDoc} */ + public void unregisterCallback(VideoCall.Callback callback) { + if (callback != mCallback) { + return; + } + + mCallback = null; + try { + mVideoProvider.removeVideoCallback(mBinder); + } catch (RemoteException e) { + } } /** {@inheritDoc} */ @@ -205,10 +256,24 @@ public class VideoCallImpl extends VideoCall { } } - /** {@inheritDoc} */ + /** + * Sends a session modification request to the video provider. + * <p> + * The {@link InCallService} will create the {@code requestProfile} based on the current + * video state (i.e. {@link Call.Details#getVideoState()}). It is, however, possible that the + * video state maintained by the {@link InCallService} could get out of sync with what is known + * by the {@link android.telecom.Connection.VideoProvider}. To remove ambiguity, the + * {@link VideoCallImpl} passes along the pre-modify video profile to the {@code VideoProvider} + * to ensure it has full context of the requested change. + * + * @param requestProfile The requested video profile. + */ public void sendSessionModifyRequest(VideoProfile requestProfile) { try { - mVideoProvider.sendSessionModifyRequest(requestProfile); + VideoProfile originalProfile = new VideoProfile(mCall.getDetails().getVideoState(), + mVideoQuality); + + mVideoProvider.sendSessionModifyRequest(originalProfile, requestProfile); } catch (RemoteException e) { } } @@ -238,10 +303,10 @@ public class VideoCallImpl extends VideoCall { } /** {@inheritDoc} */ - public void setPauseImage(String uri) { + public void setPauseImage(Uri 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 index d0e3f22..1fbad22 100644 --- a/telecomm/java/android/telecom/VideoCallbackServant.java +++ b/telecomm/java/android/telecom/VideoCallbackServant.java @@ -38,6 +38,7 @@ final class VideoCallbackServant { 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 static final int MSG_CHANGE_VIDEO_QUALITY = 6; private final IVideoCallback mDelegate; @@ -90,14 +91,18 @@ final class VideoCallbackServant { case MSG_CHANGE_CALL_DATA_USAGE: { SomeArgs args = (SomeArgs) msg.obj; try { - mDelegate.changeCallDataUsage(args.argi1); + mDelegate.changeCallDataUsage((long) args.arg1); } finally { args.recycle(); } break; } case MSG_CHANGE_CAMERA_CAPABILITIES: { - mDelegate.changeCameraCapabilities((CameraCapabilities) msg.obj); + mDelegate.changeCameraCapabilities((VideoProfile.CameraCapabilities) msg.obj); + break; + } + case MSG_CHANGE_VIDEO_QUALITY: { + mDelegate.changeVideoQuality(msg.arg1); break; } } @@ -136,18 +141,24 @@ final class VideoCallbackServant { } @Override - public void changeCallDataUsage(int dataUsage) throws RemoteException { + public void changeCallDataUsage(long dataUsage) throws RemoteException { SomeArgs args = SomeArgs.obtain(); - args.argi1 = dataUsage; + args.arg1 = dataUsage; mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, args).sendToTarget(); } @Override - public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) + public void changeCameraCapabilities( + VideoProfile.CameraCapabilities cameraCapabilities) throws RemoteException { mHandler.obtainMessage(MSG_CHANGE_CAMERA_CAPABILITIES, cameraCapabilities) .sendToTarget(); } + + @Override + public void changeVideoQuality(int videoQuality) throws RemoteException { + mHandler.obtainMessage(MSG_CHANGE_VIDEO_QUALITY, videoQuality, 0).sendToTarget(); + } }; public VideoCallbackServant(IVideoCallback delegate) { diff --git a/telecomm/java/android/telecom/VideoProfile.aidl b/telecomm/java/android/telecom/VideoProfile.aidl index 091b569..0b32721 100644 --- a/telecomm/java/android/telecom/VideoProfile.aidl +++ b/telecomm/java/android/telecom/VideoProfile.aidl @@ -21,3 +21,4 @@ package android.telecom; * {@hide} */ parcelable VideoProfile; +parcelable VideoProfile.CameraCapabilities; diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java index f5cb054..dabf706 100644 --- a/telecomm/java/android/telecom/VideoProfile.java +++ b/telecomm/java/android/telecom/VideoProfile.java @@ -21,11 +21,14 @@ import android.os.Parcelable; /** * Represents attributes of video calls. - * - * {@hide} */ public class VideoProfile implements Parcelable { /** + * "Unknown" video quality. + * @hide + */ + public static final int QUALITY_UNKNOWN = 0; + /** * "High" video quality. */ public static final int QUALITY_HIGH = 1; @@ -45,6 +48,56 @@ public class VideoProfile implements Parcelable { */ public static final int QUALITY_DEFAULT = 4; + /** + * Used when answering or dialing a call to indicate that the call does not have a video + * component. + * <p> + * Should <b>not</b> be used in comparison checks to determine if a video state represents an + * audio-only call. + * <p> + * The following, for example, is not the correct way to check if a call is audio-only: + * <pre> + * {@code + * // This is the incorrect way to check for an audio-only call. + * if (videoState == VideoProfile.STATE_AUDIO_ONLY) { + * // Handle audio-only call. + * } + * } + * </pre> + * <p> + * Instead, use the {@link VideoProfile#isAudioOnly(int)} helper function to check if a + * video state represents an audio-only call: + * <pre> + * {@code + * // This is the correct way to check for an audio-only call. + * if (VideoProfile.isAudioOnly(videoState)) { + * // Handle audio-only call. + * } + * } + * </pre> + */ + public static final int STATE_AUDIO_ONLY = 0x0; + + /** + * Video transmission is enabled. + */ + public static final int STATE_TX_ENABLED = 0x1; + + /** + * Video reception is enabled. + */ + public static final int STATE_RX_ENABLED = 0x2; + + /** + * Video signal is bi-directional. + */ + public static final int STATE_BIDIRECTIONAL = STATE_TX_ENABLED | STATE_RX_ENABLED; + + /** + * Video is paused. + */ + public static final int STATE_PAUSED = 0x4; + private final int mVideoState; private final int mQuality; @@ -71,11 +124,11 @@ public class VideoProfile implements Parcelable { /** * 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}. + * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, + * {@link VideoProfile#STATE_BIDIRECTIONAL}, + * {@link VideoProfile#STATE_TX_ENABLED}, + * {@link VideoProfile#STATE_RX_ENABLED}, + * {@link VideoProfile#STATE_PAUSED}. */ public int getVideoState() { return mVideoState; @@ -141,91 +194,255 @@ public class VideoProfile implements Parcelable { dest.writeInt(mQuality); } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[VideoProfile videoState = "); + sb.append(videoStateToString(mVideoState)); + sb.append(" videoQuality = "); + sb.append(mQuality); + sb.append("]"); + return sb.toString(); + } + + /** + * Generates a string representation of a video state. + * + * @param videoState The video state. + * @return String representation of the video state. + */ + public static String videoStateToString(int videoState) { + StringBuilder sb = new StringBuilder(); + sb.append("Audio"); + + if (isAudioOnly(videoState)) { + sb.append(" Only"); + } else { + if (isTransmissionEnabled(videoState)) { + sb.append(" Tx"); + } + + if (isReceptionEnabled(videoState)) { + sb.append(" Rx"); + } + + if (isPaused(videoState)) { + sb.append(" Pause"); + } + } + + return sb.toString(); + } + + /** + * Indicates whether the video state is audio only. + * + * @param videoState The video state. + * @return {@code True} if the video state is audio only, {@code false} otherwise. + */ + public static boolean isAudioOnly(int videoState) { + return !hasState(videoState, VideoProfile.STATE_TX_ENABLED) + && !hasState(videoState, VideoProfile.STATE_RX_ENABLED); + } + + /** + * Indicates whether video transmission or reception is enabled for a video state. + * + * @param videoState The video state. + * @return {@code True} if video transmission or reception is enabled, {@code false} otherwise. + */ + public static boolean isVideo(int videoState) { + return hasState(videoState, VideoProfile.STATE_TX_ENABLED) + || hasState(videoState, VideoProfile.STATE_RX_ENABLED) + || hasState(videoState, VideoProfile.STATE_BIDIRECTIONAL); + } + + /** + * Indicates whether the video state has video transmission enabled. + * + * @param videoState The video state. + * @return {@code True} if video transmission is enabled, {@code false} otherwise. + */ + public static boolean isTransmissionEnabled(int videoState) { + return hasState(videoState, VideoProfile.STATE_TX_ENABLED); + } + + /** + * Indicates whether the video state has video reception enabled. + * + * @param videoState The video state. + * @return {@code True} if video reception is enabled, {@code false} otherwise. + */ + public static boolean isReceptionEnabled(int videoState) { + return hasState(videoState, VideoProfile.STATE_RX_ENABLED); + } + + /** + * Indicates whether the video state is bi-directional. + * + * @param videoState The video state. + * @return {@code True} if the video is bi-directional, {@code false} otherwise. + */ + public static boolean isBidirectional(int videoState) { + return hasState(videoState, VideoProfile.STATE_BIDIRECTIONAL); + } + + /** + * Indicates whether the video state is paused. + * + * @param videoState The video state. + * @return {@code True} if the video is paused, {@code false} otherwise. + */ + public static boolean isPaused(int videoState) { + return hasState(videoState, VideoProfile.STATE_PAUSED); + } + /** - * 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 { + * Indicates 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. + */ + private static boolean hasState(int videoState, int state) { + return (videoState & state) == state; + } + + /** + * Represents the camera capabilities important to a Video Telephony provider. + */ + public static final class CameraCapabilities implements Parcelable { + /** - * Call is currently in an audio-only mode with no video transmission or receipt. + * The width of the camera video in pixels. */ - public static final int AUDIO_ONLY = 0x0; + private final int mWidth; /** - * Video transmission is enabled. + * The height of the camera video in pixels. */ - public static final int TX_ENABLED = 0x1; + private final int mHeight; /** - * Video reception is enabled. + * Whether the camera supports zoom. */ - public static final int RX_ENABLED = 0x2; + private final boolean mZoomSupported; /** - * Video signal is bi-directional. + * The maximum zoom supported by the camera. + */ + private final float mMaxZoom; + + /** + * Create a call camera capabilities instance. + * + * @param width The width of the camera video (in pixels). + * @param height The height of the camera video (in pixels). */ - public static final int BIDIRECTIONAL = TX_ENABLED | RX_ENABLED; + public CameraCapabilities(int width, int height) { + this(width, height, false, 1.0f); + } /** - * Video is paused. + * Create a call camera capabilities instance that optionally + * supports zoom. + * + * @param width The width of the camera video (in pixels). + * @param height The height of the camera video (in pixels). + * @param zoomSupported True when camera supports zoom. + * @param maxZoom Maximum zoom supported by camera. + * @hide */ - public static final int PAUSED = 0x4; + public CameraCapabilities(int width, int height, boolean zoomSupported, float maxZoom) { + mWidth = width; + mHeight = height; + mZoomSupported = zoomSupported; + mMaxZoom = maxZoom; + } /** - * Whether the video state is audio only. - * @param videoState The video state. - * @return Returns true if the video state is audio only. + * 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) { + int width = source.readInt(); + int height = source.readInt(); + boolean supportsZoom = source.readByte() != 0; + float maxZoom = source.readFloat(); + + return new CameraCapabilities(width, height, supportsZoom, maxZoom); + } + + @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. */ - public static boolean isAudioOnly(int videoState) { - return !hasState(videoState, TX_ENABLED) && !hasState(videoState, RX_ENABLED); + @Override + public int describeContents() { + return 0; } /** - * Whether the video transmission is enabled. - * @param videoState The video state. - * @return Returns true if the video transmission is enabled. + * 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}. */ - public static boolean isTransmissionEnabled(int videoState) { - return hasState(videoState, TX_ENABLED); + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(getWidth()); + dest.writeInt(getHeight()); + dest.writeByte((byte) (isZoomSupported() ? 1 : 0)); + dest.writeFloat(getMaxZoom()); } /** - * Whether the video reception is enabled. - * @param videoState The video state. - * @return Returns true if the video transmission is enabled. + * The width of the camera video in pixels. */ - public static boolean isReceptionEnabled(int videoState) { - return hasState(videoState, RX_ENABLED); + public int getWidth() { + return mWidth; } /** - * Whether the video signal is bi-directional. - * @param videoState - * @return Returns true if the video signal is bi-directional. + * The height of the camera video in pixels. */ - public static boolean isBidirectional(int videoState) { - return hasState(videoState, BIDIRECTIONAL); + public int getHeight() { + return mHeight; } /** - * Whether the video is paused. - * @param videoState The video state. - * @return Returns true if the video is paused. + * Whether the camera supports zoom. + * @hide */ - public static boolean isPaused(int videoState) { - return hasState(videoState, PAUSED); + public boolean isZoomSupported() { + return mZoomSupported; } /** - * 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} + * The maximum zoom supported by the camera. + * @hide */ - private static boolean hasState(int videoState, int state) { - return (videoState & state) == state; + public float getMaxZoom() { + return mMaxZoom; } } + } diff --git a/telecomm/java/android/telecom/Voicemail.java b/telecomm/java/android/telecom/Voicemail.java new file mode 100644 index 0000000..151917e --- /dev/null +++ b/telecomm/java/android/telecom/Voicemail.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2015 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; + +/** + * Represents a single voicemail stored in the voicemail content provider. + * + * @hide + */ +public class Voicemail implements Parcelable { + private final Long mTimestamp; + private final String mNumber; + private final PhoneAccountHandle mPhoneAccount; + private final Long mId; + private final Long mDuration; + private final String mSource; + private final String mProviderData; + private final Uri mUri; + private final Boolean mIsRead; + private final Boolean mHasContent; + + private Voicemail(Long timestamp, String number, PhoneAccountHandle phoneAccountHandle, Long id, + Long duration, String source, String providerData, Uri uri, Boolean isRead, + Boolean hasContent) { + mTimestamp = timestamp; + mNumber = number; + mPhoneAccount = phoneAccountHandle; + mId = id; + mDuration = duration; + mSource = source; + mProviderData = providerData; + mUri = uri; + mIsRead = isRead; + mHasContent = hasContent; + } + + /** + * Create a {@link Builder} for a new {@link Voicemail} to be inserted. + * <p> + * The number and the timestamp are mandatory for insertion. + */ + public static Builder createForInsertion(long timestamp, String number) { + return new Builder().setNumber(number).setTimestamp(timestamp); + } + + /** + * Create a {@link Builder} for a {@link Voicemail} to be updated (or deleted). + * <p> + * The id and source data fields are mandatory for update - id is necessary for updating the + * database and source data is necessary for updating the server. + */ + public static Builder createForUpdate(long id, String sourceData) { + return new Builder().setId(id).setSourceData(sourceData); + } + + /** + * Builder pattern for creating a {@link Voicemail}. The builder must be created with the + * {@link #createForInsertion(long, String)} method. + * <p> + * This class is <b>not thread safe</b> + */ + public static class Builder { + private Long mBuilderTimestamp; + private String mBuilderNumber; + private PhoneAccountHandle mBuilderPhoneAccount; + private Long mBuilderId; + private Long mBuilderDuration; + private String mBuilderSourcePackage; + private String mBuilderSourceData; + private Uri mBuilderUri; + private Boolean mBuilderIsRead; + private boolean mBuilderHasContent; + + /** You should use the correct factory method to construct a builder. */ + private Builder() { + } + + public Builder setNumber(String number) { + mBuilderNumber = number; + return this; + } + + public Builder setTimestamp(long timestamp) { + mBuilderTimestamp = timestamp; + return this; + } + + public Builder setPhoneAccount(PhoneAccountHandle phoneAccount) { + mBuilderPhoneAccount = phoneAccount; + return this; + } + + public Builder setId(long id) { + mBuilderId = id; + return this; + } + + public Builder setDuration(long duration) { + mBuilderDuration = duration; + return this; + } + + public Builder setSourcePackage(String sourcePackage) { + mBuilderSourcePackage = sourcePackage; + return this; + } + + public Builder setSourceData(String sourceData) { + mBuilderSourceData = sourceData; + return this; + } + + public Builder setUri(Uri uri) { + mBuilderUri = uri; + return this; + } + + public Builder setIsRead(boolean isRead) { + mBuilderIsRead = isRead; + return this; + } + + public Builder setHasContent(boolean hasContent) { + mBuilderHasContent = hasContent; + return this; + } + + public Voicemail build() { + mBuilderId = mBuilderId == null ? -1 : mBuilderId; + mBuilderTimestamp = mBuilderTimestamp == null ? 0 : mBuilderTimestamp; + mBuilderDuration = mBuilderDuration == null ? 0: mBuilderDuration; + mBuilderIsRead = mBuilderIsRead == null ? false : mBuilderIsRead; + return new Voicemail(mBuilderTimestamp, mBuilderNumber, mBuilderPhoneAccount, + mBuilderId, mBuilderDuration, mBuilderSourcePackage, mBuilderSourceData, + mBuilderUri, mBuilderIsRead, mBuilderHasContent); + } + } + + /** + * The identifier of the voicemail in the content provider. + * <p> + * This may be missing in the case of a new {@link Voicemail} that we plan to insert into the + * content provider, since until it has been inserted we don't know what id it should have. If + * none is specified, we return -1. + */ + public long getId() { + return mId; + } + + /** The number of the person leaving the voicemail, empty string if unknown, null if not set. */ + public String getNumber() { + return mNumber; + } + + /** The phone account associated with the voicemail, null if not set. */ + public PhoneAccountHandle getPhoneAccount() { + return mPhoneAccount; + } + + /** The timestamp the voicemail was received, in millis since the epoch, zero if not set. */ + public long getTimestampMillis() { + return mTimestamp; + } + + /** Gets the duration of the voicemail in millis, or zero if the field is not set. */ + public long getDuration() { + return mDuration; + } + + /** + * Returns the package name of the source that added this voicemail, or null if this field is + * not set. + */ + public String getSourcePackage() { + return mSource; + } + + /** + * Returns the application-specific data type stored with the voicemail, or null if this field + * is not set. + * <p> + * Source data is typically used as an identifier to uniquely identify the voicemail against + * the voicemail server. This is likely to be something like the IMAP UID, or some other + * server-generated identifying string. + */ + public String getSourceData() { + return mProviderData; + } + + /** + * Gets the Uri that can be used to refer to this voicemail, and to make it play. + * <p> + * Returns null if we don't know the Uri. + */ + public Uri getUri() { + return mUri; + } + + /** + * Tells us if the voicemail message has been marked as read. + * <p> + * Always returns false if this field has not been set, i.e. if hasRead() returns false. + */ + public boolean isRead() { + return mIsRead; + } + + /** + * Tells us if there is content stored at the Uri. + */ + public boolean hasContent() { + return mHasContent; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mTimestamp); + dest.writeCharSequence(mNumber); + if (mPhoneAccount == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + mPhoneAccount.writeToParcel(dest, flags); + } + dest.writeLong(mId); + dest.writeLong(mDuration); + dest.writeCharSequence(mSource); + dest.writeCharSequence(mProviderData); + if (mUri == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + mUri.writeToParcel(dest, flags); + } + if (mIsRead) { + dest.writeInt(1); + } else { + dest.writeInt(0); + } + if (mHasContent) { + dest.writeInt(1); + } else { + dest.writeInt(0); + } + } + + public static final Creator<Voicemail> CREATOR + = new Creator<Voicemail>() { + @Override + public Voicemail createFromParcel(Parcel in) { + return new Voicemail(in); + } + + @Override + public Voicemail[] newArray(int size) { + return new Voicemail[size]; + } + }; + + private Voicemail(Parcel in) { + mTimestamp = in.readLong(); + mNumber = (String) in.readCharSequence(); + if (in.readInt() > 0) { + mPhoneAccount = PhoneAccountHandle.CREATOR.createFromParcel(in); + } else { + mPhoneAccount = null; + } + mId = in.readLong(); + mDuration = in.readLong(); + mSource = (String) in.readCharSequence(); + mProviderData = (String) in.readCharSequence(); + if (in.readInt() > 0) { + mUri = Uri.CREATOR.createFromParcel(in); + } else { + mUri = null; + } + mIsRead = in.readInt() > 0 ? true : false; + mHasContent = in.readInt() > 0 ? true : false; + } +} |
