Merge "IMS-VT: Upgrade/Downgrade change -Add isVideo API to VideoProfile.VideoState" into m-wireless-dev
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 436f20a..2d2c246 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -208,10 +208,6 @@
          */
         public static final int CAPABILITY_WIFI = 0x00010000;
 
-        //******************************************************************************************
-        // Next CAPABILITY value: 0x00020000
-        //******************************************************************************************
-
         /**
          * Indicates that the current device callback number should be shown.
          *
@@ -225,8 +221,14 @@
          */
         public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00040000;
 
+         /**
+         * Call type can be modified for IMS call
+         * @hide
+         */
+        public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 0x00080000;
+
         //******************************************************************************************
-        // Next CAPABILITY value: 0x00080000
+        // Next CAPABILITY value: 0x00100000
         //******************************************************************************************
 
         private final Uri mHandle;
@@ -242,6 +244,7 @@
         private final int mVideoState;
         private final StatusHints mStatusHints;
         private final Bundle mExtras;
+        private final int mCallSubstate;
 
         /**
          * Whether the supplied capabilities  supports the specified capability.
@@ -329,6 +332,9 @@
             if (can(capabilities, CAPABILITY_SPEED_UP_MT_AUDIO)) {
                 builder.append(" CAPABILITY_SPEED_UP_MT_AUDIO");
             }
+            if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
+                builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO");
+            }
             builder.append("]");
             return builder.toString();
         }
@@ -434,6 +440,14 @@
             return mExtras;
         }
 
+        /**
+         * @return The substate of the {@code Call}.
+         * @hide
+         */
+        public int getCallSubstate() {
+            return mCallSubstate;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (o instanceof Details) {
@@ -452,7 +466,8 @@
                         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(mCallSubstate, d.mCallSubstate);
             }
             return false;
         }
@@ -472,7 +487,8 @@
                     Objects.hashCode(mGatewayInfo) +
                     Objects.hashCode(mVideoState) +
                     Objects.hashCode(mStatusHints) +
-                    Objects.hashCode(mExtras);
+                    Objects.hashCode(mExtras) +
+                    Objects.hashCode(mCallSubstate);
         }
 
         /** {@hide} */
@@ -489,7 +505,8 @@
                 GatewayInfo gatewayInfo,
                 int videoState,
                 StatusHints statusHints,
-                Bundle extras) {
+                Bundle extras,
+                int callSubstate) {
             mHandle = handle;
             mHandlePresentation = handlePresentation;
             mCallerDisplayName = callerDisplayName;
@@ -503,6 +520,7 @@
             mVideoState = videoState;
             mStatusHints = statusHints;
             mExtras = extras;
+            mCallSubstate = callSubstate;
         }
     }
 
@@ -883,7 +901,8 @@
                 parcelableCall.getGatewayInfo(),
                 parcelableCall.getVideoState(),
                 parcelableCall.getStatusHints(),
-                parcelableCall.getExtras());
+                parcelableCall.getExtras(),
+                parcelableCall.getCallSubstate());
         boolean detailsChanged = !Objects.equals(mDetails, details);
         if (detailsChanged) {
             mDetails = details;
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 33bbb29..ddaedcd 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -17,10 +17,12 @@
 package android.telecom;
 
 import android.annotation.SystemApi;
+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;
@@ -49,6 +51,8 @@
         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) {}
     }
 
     private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
@@ -180,6 +184,22 @@
     }
 
     /**
+     * Returns VideoProvider of the primary call. This can be null.
+     *  @hide
+     */
+    public VideoProvider getVideoProvider() {
+        return null;
+    }
+
+    /**
+     * Returns video state of the primary call.
+     *  @hide
+     */
+    public int getVideoState() {
+        return VideoProfile.VideoState.AUDIO_ONLY;
+    }
+
+    /**
      * Invoked when the Conference and all it's {@link Connection}s should be disconnected.
      */
     public void onDisconnect() {}
@@ -309,6 +329,7 @@
      * @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 +376,38 @@
         fireOnConferenceableConnectionsChanged();
     }
 
+    /**
+     * Set the video state for the conference.
+     * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY},
+     * {@link VideoProfile.VideoState#BIDIRECTIONAL},
+     * {@link VideoProfile.VideoState#TX_ENABLED},
+     * {@link VideoProfile.VideoState#RX_ENABLED}.
+     *
+     * @param videoState The new video state.
+     * @hide
+     */
+    public final void setVideoState(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.
+     * @hide
+     */
+    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());
@@ -484,4 +537,15 @@
         }
         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());
+    }
 }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index bab064e..c34b080 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -186,10 +186,58 @@
     */
     public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00040000;
 
+    /**
+     * Call type can be modified for IMS call
+     * @hide
+     */
+    public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 0x00080000;
+
     //**********************************************************************************************
-    // Next CAPABILITY value: 0x00080000
+    // Next CAPABILITY value: 0x00100000
     //**********************************************************************************************
 
+    /**
+     * Call substate bitmask values
+     */
+
+    /* Default case */
+    /**
+     * @hide
+     */
+    public static final int SUBSTATE_NONE = 0;
+
+    /* Indicates that the call is connected but audio attribute is suspended */
+    /**
+     * @hide
+     */
+    public static final int SUBSTATE_AUDIO_CONNECTED_SUSPENDED = 0x1;
+
+    /* Indicates that the call is connected but video attribute is suspended */
+    /**
+     * @hide
+     */
+    public static final int SUBSTATE_VIDEO_CONNECTED_SUSPENDED = 0x2;
+
+    /* Indicates that the call is established but media retry is needed */
+    /**
+     * @hide
+     */
+    public static final int SUBSTATE_AVP_RETRY = 0x4;
+
+    /* Indicates that the call is multitasking */
+    /**
+     * @hide
+     */
+    public static final int SUBSTATE_MEDIA_PAUSED = 0x8;
+
+    /* Mask containing all the call substate bits set */
+    /**
+     * @hide
+     */
+    public static final int SUBSTATE_ALL = SUBSTATE_AUDIO_CONNECTED_SUSPENDED |
+        SUBSTATE_VIDEO_CONNECTED_SUSPENDED | SUBSTATE_AVP_RETRY |
+        SUBSTATE_MEDIA_PAUSED;
+
     // Flag controlling whether PII is emitted into the logs
     private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
 
@@ -294,6 +342,9 @@
         if (can(capabilities, CAPABILITY_SPEED_UP_MT_AUDIO)) {
             builder.append(" CAPABILITY_SPEED_UP_MT_AUDIO");
         }
+        if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
+            builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO");
+        }
         builder.append("]");
         return builder.toString();
     }
@@ -322,6 +373,7 @@
         public void onConferenceParticipantsChanged(Connection c,
                 List<ConferenceParticipant> participants) {}
         public void onConferenceStarted() {}
+        public void onCallSubstateChanged(Connection c, int substate) {}
     }
 
     /** @hide */
@@ -377,6 +429,16 @@
          */
         public static final int SESSION_MODIFY_REQUEST_INVALID = 3;
 
+        /**
+         * Session modify request timed out.
+         */
+        public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4;
+
+        /**
+         * Session modify request rejected by remote UE.
+         */
+        public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5;
+
         private static final int MSG_SET_VIDEO_CALLBACK = 1;
         private static final int MSG_SET_CAMERA = 2;
         private static final int MSG_SET_PREVIEW_SURFACE = 3;
@@ -462,7 +524,8 @@
             }
 
             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) {
@@ -656,7 +719,7 @@
          *
          * @param dataUsage The updated data usage.
          */
-        public void changeCallDataUsage(int dataUsage) {
+        public void changeCallDataUsage(long dataUsage) {
             if (mVideoCallback != null) {
                 try {
                     mVideoCallback.changeCallDataUsage(dataUsage);
@@ -678,6 +741,20 @@
                 }
             }
         }
+
+        /**
+         * Invokes callback method defined in In-Call UI.
+         *
+         * @param videoQuality The updated video quality.
+         */
+        public void changeVideoQuality(int videoQuality) {
+            if (mVideoCallback != null) {
+                try {
+                    mVideoCallback.changeVideoQuality(videoQuality);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
     }
 
     private final Listener mConnectionDeathListener = new Listener() {
@@ -724,6 +801,7 @@
     private DisconnectCause mDisconnectCause;
     private Conference mConference;
     private ConnectionService mConnectionService;
+    private int mCallSubstate;
 
     /**
      * Create a new Connection.
@@ -782,6 +860,21 @@
     }
 
     /**
+     * Returns the call substate of the call.
+     * Valid values: {@link Connection#SUBSTATE_NONE},
+     * {@link Connection#SUBSTATE_AUDIO_CONNECTED_SUSPENDED},
+     * {@link Connection#SUBSTATE_VIDEO_CONNECTED_SUSPENDED},
+     * {@link Connection#SUBSTATE_AVP_RETRY},
+     * {@link Connection#SUBSTATE_MEDIA_PAUSED}.
+     *
+     * @param callSubstate The new call substate.
+     * @hide
+     */
+    public final int getCallSubstate() {
+        return mCallSubstate;
+    }
+
+    /**
      * @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.
@@ -960,6 +1053,25 @@
     }
 
     /**
+     * Set the call substate for the connection.
+     * Valid values: {@link Connection#SUBSTATE_NONE},
+     * {@link Connection#SUBSTATE_AUDIO_CONNECTED_SUSPENDED},
+     * {@link Connection#SUBSTATE_VIDEO_CONNECTED_SUSPENDED},
+     * {@link Connection#SUBSTATE_AVP_RETRY},
+     * {@link Connection#SUBSTATE_MEDIA_PAUSED}.
+     *
+     * @param callSubstate The new call substate.
+     * @hide
+     */
+    public final void setCallSubstate(int callSubstate) {
+        Log.d(this, "setCallSubstate %d", callSubstate);
+        mCallSubstate = callSubstate;
+        for (Listener l : mListeners) {
+            l.onCallSubstateChanged(this, mCallSubstate);
+        }
+    }
+
+    /**
      * Sets state to active (e.g., an ongoing connection where two or more parties can actively
      * communicate).
      */
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index dfdc3e1..0c719cd 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -415,6 +415,21 @@
                     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);
+        }
     };
 
     private final Connection.Listener mConnectionListener = new Connection.Listener() {
@@ -508,6 +523,8 @@
         @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);
         }
 
@@ -542,6 +559,13 @@
                 mAdapter.setIsConferenced(id, conferenceId);
             }
         }
+
+        @Override
+        public void onCallSubstateChanged(Connection c, int callSubstate) {
+            String id = mIdByConnection.get(c);
+            Log.d(this, "Adapter set call substate %d", callSubstate);
+            mAdapter.setCallSubstate(id, callSubstate);
+        }
     };
 
     /** {@inheritDoc} */
@@ -611,7 +635,8 @@
                         connection.getAudioModeIsVoip(),
                         connection.getStatusHints(),
                         connection.getDisconnectCause(),
-                        createIdList(connection.getConferenceables())));
+                        createIdList(connection.getConferenceables()),
+                        connection.getCallSubstate()));
     }
 
     private void abort(String callId) {
@@ -871,6 +896,8 @@
      * @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 +911,14 @@
                     conference.getState(),
                     conference.getConnectionCapabilities(),
                     connectionIds,
-                    conference.getConnectTimeMillis());
+                    conference.getVideoProvider() == null ?
+                            null : conference.getVideoProvider().getInterface(),
+                    conference.getVideoState(),
+                    conference.getConnectTimeMillis()
+                    );
             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 +959,7 @@
                     connection.getAudioModeIsVoip(),
                     connection.getStatusHints(),
                     connection.getDisconnectCause(),
-                    emptyList);
+                    emptyList, connection.getCallSubstate());
             mAdapter.addExistingConnection(id, parcelableConnection);
         }
     }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index d026a28..a410976 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -369,4 +369,26 @@
             }
         }
     }
+
+    /**
+     * Set the call substate for the connection.
+     * Valid values: {@link Connection#CALL_SUBSTATE_NONE},
+     * {@link Connection#CALL_SUBSTATE_AUDIO_CONNECTED_SUSPENDED},
+     * {@link Connection#CALL_SUBSTATE_VIDEO_CONNECTED_SUSPENDED},
+     * {@link Connection#CALL_SUBSTATE_AVP_RETRY},
+     * {@link Connection#CALL_SUBSTATE_MEDIA_PAUSED}.
+     *
+     * @param callId The unique ID of the call to set the substate for.
+     * @param callSubstate The new call substate.
+     * @hide
+     */
+    public final void setCallSubstate(String callId, int callSubstate) {
+        Log.v(this, "setCallSubstate: %d", callSubstate);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setCallSubstate(callId, callSubstate);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 429f296..5f93789 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -59,6 +59,7 @@
     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_CALL_SUBSTATE = 23;
 
     private final IConnectionServiceAdapter mDelegate;
 
@@ -220,6 +221,10 @@
                     }
                     break;
                 }
+                case MSG_SET_CALL_SUBSTATE: {
+                    mDelegate.setCallSubstate((String) msg.obj, msg.arg1);
+                    break;
+                }
             }
         }
     };
@@ -384,6 +389,12 @@
             args.arg2 = connection;
             mHandler.obtainMessage(MSG_ADD_EXISTING_CONNECTION, args).sendToTarget();
         }
+
+        @Override
+        public void setCallSubstate(String connectionId, int callSubstate) {
+            mHandler.obtainMessage(MSG_SET_CALL_SUBSTATE, callSubstate, 0,
+                connectionId).sendToTarget();
+        }
     };
 
     public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index a85e84d..c0c59fa 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -361,11 +361,18 @@
             public abstract void onPeerDimensionsChanged(int width, int height);
 
             /**
+             * Handles a change to the video quality.
+             *
+             * @param videoQuality  The updated peer video quality.
+             */
+            public abstract void onVideoQualityChanged(int videoQuality);
+
+            /**
              * Handles an update to the total data used for the current session.
              *
              * @param dataUsage The updated data usage.
              */
-            public abstract void onCallDataUsageChanged(int dataUsage);
+            public abstract void onCallDataUsageChanged(long dataUsage);
 
             /**
              * Handles a change in camera capabilities.
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index c5c3d11..adc648f 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -54,6 +54,7 @@
     private final int mVideoState;
     private final List<String> mConferenceableCallIds;
     private final Bundle mExtras;
+    private int mCallSubstate;
 
     public ParcelableCall(
             String id,
@@ -75,7 +76,8 @@
             StatusHints statusHints,
             int videoState,
             List<String> conferenceableCallIds,
-            Bundle extras) {
+            Bundle extras,
+            int callSubstate) {
         mId = id;
         mState = state;
         mDisconnectCause = disconnectCause;
@@ -96,6 +98,7 @@
         mVideoState = videoState;
         mConferenceableCallIds = Collections.unmodifiableList(conferenceableCallIds);
         mExtras = extras;
+        mCallSubstate = callSubstate;
     }
 
     /** The unique ID of the call. */
@@ -232,6 +235,14 @@
         return mExtras;
     }
 
+    /**
+     * The call substate.
+     * @return The substate of the call.
+     */
+    public int getCallSubstate() {
+        return mCallSubstate;
+    }
+
     /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
     public static final Parcelable.Creator<ParcelableCall> CREATOR =
             new Parcelable.Creator<ParcelableCall> () {
@@ -262,6 +273,7 @@
             List<String> conferenceableCallIds = new ArrayList<>();
             source.readList(conferenceableCallIds, classLoader);
             Bundle extras = source.readParcelable(classLoader);
+            int callSubstate = source.readInt();
             return new ParcelableCall(
                     id,
                     state,
@@ -282,7 +294,8 @@
                     statusHints,
                     videoState,
                     conferenceableCallIds,
-                    extras);
+                    extras,
+                    callSubstate);
         }
 
         @Override
@@ -321,6 +334,7 @@
         destination.writeInt(mVideoState);
         destination.writeList(mConferenceableCallIds);
         destination.writeParcelable(mExtras, 0);
+        destination.writeInt(mCallSubstate);
     }
 
     @Override
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index dcc2713..ab82549 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -22,6 +22,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import com.android.internal.telecom.IVideoProvider;
+
 /**
  * A parcelable representation of a conference connection.
  * @hide
@@ -33,17 +35,23 @@
     private int mConnectionCapabilities;
     private List<String> mConnectionIds;
     private long mConnectTimeMillis;
+    private final IVideoProvider mVideoProvider;
+    private final int mVideoState;
 
     public ParcelableConference(
             PhoneAccountHandle phoneAccount,
             int state,
             int connectionCapabilities,
-            List<String> connectionIds) {
+            List<String> connectionIds,
+            IVideoProvider videoProvider,
+            int videoState) {
         mPhoneAccount = phoneAccount;
         mState = state;
         mConnectionCapabilities = connectionCapabilities;
         mConnectionIds = connectionIds;
         mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
+        mVideoProvider = videoProvider;
+        mVideoState = videoState;
     }
 
     public ParcelableConference(
@@ -51,8 +59,10 @@
             int state,
             int connectionCapabilities,
             List<String> connectionIds,
+            IVideoProvider videoProvider,
+            int videoState,
             long connectTimeMillis) {
-        this(phoneAccount, state, connectionCapabilities, connectionIds);
+        this(phoneAccount, state, connectionCapabilities, connectionIds, videoProvider, videoState);
         mConnectTimeMillis = connectTimeMillis;
     }
 
@@ -69,6 +79,10 @@
                 .append(mConnectTimeMillis)
                 .append(", children: ")
                 .append(mConnectionIds)
+                .append(", VideoState: ")
+                .append(mVideoState)
+                .append(", VideoProvider: ")
+                .append(mVideoProvider)
                 .toString();
     }
 
@@ -91,6 +105,13 @@
     public long getConnectTimeMillis() {
         return mConnectTimeMillis;
     }
+    public IVideoProvider getVideoProvider() {
+        return mVideoProvider;
+    }
+
+    public int getVideoState() {
+        return mVideoState;
+    }
 
     public static final Parcelable.Creator<ParcelableConference> CREATOR =
             new Parcelable.Creator<ParcelableConference> () {
@@ -104,8 +125,12 @@
             source.readList(connectionIds, classLoader);
             long connectTimeMillis = source.readLong();
 
+            IVideoProvider videoCallProvider =
+                    IVideoProvider.Stub.asInterface(source.readStrongBinder());
+            int videoState = source.readInt();
+
             return new ParcelableConference(phoneAccount, state, capabilities, connectionIds,
-                    connectTimeMillis);
+                    videoCallProvider, videoState);
         }
 
         @Override
@@ -128,5 +153,8 @@
         destination.writeInt(mConnectionCapabilities);
         destination.writeList(mConnectionIds);
         destination.writeLong(mConnectTimeMillis);
+        destination.writeStrongBinder(
+                mVideoProvider != null ? mVideoProvider.asBinder() : null);
+        destination.writeInt(mVideoState);
     }
 }
diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java
index 552e250..b60b99d 100644
--- a/telecomm/java/android/telecom/ParcelableConnection.java
+++ b/telecomm/java/android/telecom/ParcelableConnection.java
@@ -46,6 +46,7 @@
     private final StatusHints mStatusHints;
     private final DisconnectCause mDisconnectCause;
     private final List<String> mConferenceableConnectionIds;
+    private final int mCallSubstate;
 
     /** @hide */
     public ParcelableConnection(
@@ -62,7 +63,8 @@
             boolean isVoipAudioMode,
             StatusHints statusHints,
             DisconnectCause disconnectCause,
-            List<String> conferenceableConnectionIds) {
+            List<String> conferenceableConnectionIds,
+            int callSubstate) {
         mPhoneAccount = phoneAccount;
         mState = state;
         mConnectionCapabilities = capabilities;
@@ -77,6 +79,7 @@
         mStatusHints = statusHints;
         mDisconnectCause = disconnectCause;
         this.mConferenceableConnectionIds = conferenceableConnectionIds;
+        mCallSubstate = callSubstate;
     }
 
     public PhoneAccountHandle getPhoneAccount() {
@@ -136,6 +139,10 @@
         return mConferenceableConnectionIds;
     }
 
+    public int getCallSubstate() {
+        return mCallSubstate;
+    }
+
     @Override
     public String toString() {
         return new StringBuilder()
@@ -170,6 +177,7 @@
             DisconnectCause disconnectCause = source.readParcelable(classLoader);
             List<String> conferenceableConnectionIds = new ArrayList<>();
             source.readStringList(conferenceableConnectionIds);
+            int callSubstate = source.readInt();
 
             return new ParcelableConnection(
                     phoneAccount,
@@ -185,7 +193,8 @@
                     audioModeIsVoip,
                     statusHints,
                     disconnectCause,
-                    conferenceableConnectionIds);
+                    conferenceableConnectionIds,
+                    callSubstate);
         }
 
         @Override
@@ -218,5 +227,6 @@
         destination.writeParcelable(mStatusHints, 0);
         destination.writeParcelable(mDisconnectCause, 0);
         destination.writeStringList(mConferenceableConnectionIds);
+        destination.writeInt(mCallSubstate);
     }
 }
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 486691f..be7a0a0 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -161,6 +161,16 @@
         public void onVideoStateChanged(RemoteConnection connection, int videoState) {}
 
         /**
+         * Indicates that the call substate of this {@code RemoteConnection} has changed.
+         * See {@link #getCallSubstate()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param callSubstate The new call substate of the {@code RemoteConnection}.
+         * @hide
+         */
+        public void onCallSubstateChanged(RemoteConnection connection, int callSubstate) {}
+
+        /**
          * Indicates that this {@code RemoteConnection} has been destroyed. No further requests
          * should be made to the {@code RemoteConnection}, and references to it should be cleared.
          *
@@ -223,11 +233,13 @@
 
             public void onPeerDimensionsChanged(VideoProvider videoProvider, int width, int height) {}
 
-            public void onCallDataUsageChanged(VideoProvider videoProvider, int dataUsage) {}
+            public void onCallDataUsageChanged(VideoProvider videoProvider, long dataUsage) {}
 
             public void onCameraCapabilitiesChanged(
                     VideoProvider videoProvider,
                     CameraCapabilities cameraCapabilities) {}
+
+            public void onVideoQualityChanged(VideoProvider videoProvider, int videoQuality) {}
         }
 
         private final IVideoCallback mVideoCallbackDelegate = new IVideoCallback() {
@@ -265,7 +277,7 @@
             }
 
             @Override
-            public void changeCallDataUsage(int dataUsage) {
+            public void changeCallDataUsage(long dataUsage) {
                 for (Listener l : mListeners) {
                     l.onCallDataUsageChanged(VideoProvider.this, dataUsage);
                 }
@@ -279,6 +291,13 @@
             }
 
             @Override
+            public void changeVideoQuality(int videoQuality) {
+                for (Listener l : mListeners) {
+                    l.onVideoQualityChanged(VideoProvider.this, videoQuality);
+                }
+            }
+
+            @Override
             public IBinder asBinder() {
                 return null;
             }
@@ -403,6 +422,7 @@
     private boolean mConnected;
     private int mConnectionCapabilities;
     private int mVideoState;
+    private int mCallSubstate;
     private VideoProvider mVideoProvider;
     private boolean mIsVoipAudioMode;
     private StatusHints mStatusHints;
@@ -583,8 +603,16 @@
     }
 
     /**
-     * Obtains the video provider of this {@code RemoteConnection}.
      *
+     * @return The call substate of the {@code RemoteConnection}. See
+     * @hide
+     */
+    public int getCallSubstate() {
+        return mCallSubstate;
+    }
+
+    /**
+     * Obtains the video provider of this {@code RemoteConnection}.
      * @return The video provider associated with this {@code RemoteConnection}.
      * @hide
      */
@@ -897,6 +925,16 @@
     /**
      * @hide
      */
+    void setCallSubstate(int callSubstate) {
+        mCallSubstate = callSubstate;
+        for (Callback c : mCallbacks) {
+            c.onCallSubstateChanged(this, callSubstate);
+        }
+    }
+
+    /**
+     * @hide
+     */
     void setVideoProvider(VideoProvider videoProvider) {
         mVideoProvider = videoProvider;
         for (Callback c : mCallbacks) {
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 43a92cb..179db55 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -79,6 +79,7 @@
                 }
                 connection.setConferenceableConnections(conferenceable);
                 connection.setVideoState(parcel.getVideoState());
+                connection.setCallSubstate(parcel.getCallSubstate());
                 if (connection.getState() == Connection.STATE_DISCONNECTED) {
                     // ... then, if it was created in a disconnected state, that indicates
                     // failure on the providing end, so immediately mark it destroyed
@@ -306,6 +307,12 @@
 
             mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnction);
         }
+
+        @Override
+        public void setCallSubstate(String callId, int callSubstate) {
+            findConnectionForAction(callId, "callSubstate")
+                    .setCallSubstate(callSubstate);
+        }
     };
 
     private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index 925058e..0445448 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -42,6 +42,7 @@
     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;
 
     private final IVideoProvider mVideoProvider;
     private final VideoCallListenerBinder mBinder;
@@ -88,7 +89,12 @@
         }
 
         @Override
-        public void changeCallDataUsage(int dataUsage) {
+        public void changeVideoQuality(int videoQuality) {
+            mHandler.obtainMessage(MSG_CHANGE_VIDEO_QUALITY, videoQuality, 0).sendToTarget();
+        }
+
+        @Override
+        public void changeCallDataUsage(long dataUsage) {
             mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, dataUsage).sendToTarget();
         }
 
@@ -139,12 +145,15 @@
                     }
                     break;
                 case MSG_CHANGE_CALL_DATA_USAGE:
-                    mVideoCallListener.onCallDataUsageChanged(msg.arg1);
+                    mVideoCallListener.onCallDataUsageChanged((long) msg.obj);
                     break;
                 case MSG_CHANGE_CAMERA_CAPABILITIES:
                     mVideoCallListener.onCameraCapabilitiesChanged(
                             (CameraCapabilities) msg.obj);
                     break;
+                case MSG_CHANGE_VIDEO_QUALITY:
+                    mVideoCallListener.onVideoQualityChanged(msg.arg1);
+                    break;
                 default:
                     break;
             }
@@ -244,4 +253,4 @@
         } 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..1123621 100644
--- a/telecomm/java/android/telecom/VideoCallbackServant.java
+++ b/telecomm/java/android/telecom/VideoCallbackServant.java
@@ -38,6 +38,7 @@
     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,7 +91,7 @@
                 case MSG_CHANGE_CALL_DATA_USAGE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        mDelegate.changeCallDataUsage(args.argi1);
+                        mDelegate.changeCallDataUsage((long) args.arg1);
                     } finally {
                         args.recycle();
                     }
@@ -100,6 +101,10 @@
                     mDelegate.changeCameraCapabilities((CameraCapabilities) msg.obj);
                     break;
                 }
+                case MSG_CHANGE_VIDEO_QUALITY: {
+                    mDelegate.changeVideoQuality(msg.arg1);
+                    break;
+                }
             }
         }
     };
@@ -136,9 +141,9 @@
         }
 
         @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();
         }
 
@@ -148,6 +153,11 @@
             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.java b/telecomm/java/android/telecom/VideoProfile.java
index e62e994..2fd438a 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -24,6 +24,11 @@
  */
 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;
@@ -179,6 +184,17 @@
         }
 
         /**
+         * Whether the video state is any of the video type
+         * @param videoState The video state.
+         * @hide
+         * @return Returns true if the video state TX or RX or Bidirectional
+         */
+        public static boolean isVideo(int videoState) {
+            return hasState(videoState, TX_ENABLED) || hasState(videoState, RX_ENABLED)
+                    || hasState(videoState, BIDIRECTIONAL);
+        }
+
+        /**
          * Whether the video transmission is enabled.
          * @param videoState The video state.
          * @return Returns true if the video transmission is enabled.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 7e7e9cc..e6c28f3 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -81,4 +81,6 @@
     void setConferenceableConnections(String callId, in List<String> conferenceableCallIds);
 
     void addExistingConnection(String callId, in ParcelableConnection connection);
+
+    void setCallSubstate(String callId, int callSubstate);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IVideoCallback.aidl b/telecomm/java/com/android/internal/telecom/IVideoCallback.aidl
index f758b60..59f8f0c 100644
--- a/telecomm/java/com/android/internal/telecom/IVideoCallback.aidl
+++ b/telecomm/java/com/android/internal/telecom/IVideoCallback.aidl
@@ -39,7 +39,9 @@
 
     void changePeerDimensions(int width, int height);
 
-    void changeCallDataUsage(int dataUsage);
+    void changeCallDataUsage(long dataUsage);
 
     void changeCameraCapabilities(in CameraCapabilities cameraCapabilities);
+
+    void changeVideoQuality(int videoQuality);
 }
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 239c16a..604d32d 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -270,7 +270,6 @@
         return "{ serviceType=" + mServiceType +
                 ", callType=" + mCallType +
                 ", restrictCause=" + mRestrictCause +
-                //", callExtras=" + mCallExtras.toString() +
                 ", mediaProfile=" + mMediaProfile.toString() + " }";
     }
 
@@ -313,22 +312,31 @@
      * @param callType The call type.
      * @return The video state.
      */
-    public static int getVideoStateFromCallType(int callType) {
-        switch (callType) {
-            case CALL_TYPE_VT_NODIR:
-                return VideoProfile.VideoState.PAUSED |
-                        VideoProfile.VideoState.BIDIRECTIONAL;
+    public static int getVideoStateFromImsCallProfile(ImsCallProfile callProfile) {
+        int videostate = VideoProfile.VideoState.AUDIO_ONLY;
+        switch (callProfile.mCallType) {
             case CALL_TYPE_VT_TX:
-                return VideoProfile.VideoState.TX_ENABLED;
+                videostate = VideoProfile.VideoState.TX_ENABLED;
+                break;
             case CALL_TYPE_VT_RX:
-                return VideoProfile.VideoState.RX_ENABLED;
+                videostate = VideoProfile.VideoState.RX_ENABLED;
+                break;
             case CALL_TYPE_VT:
-                return VideoProfile.VideoState.BIDIRECTIONAL;
+                videostate = VideoProfile.VideoState.BIDIRECTIONAL;
+                break;
             case CALL_TYPE_VOICE:
-                return VideoProfile.VideoState.AUDIO_ONLY;
+                videostate = VideoProfile.VideoState.AUDIO_ONLY;
+                break;
             default:
-                return VideoProfile.VideoState.AUDIO_ONLY;
+                videostate = VideoProfile.VideoState.AUDIO_ONLY;
+                break;
         }
+        if (callProfile.isVideoPaused() && videostate != VideoProfile.VideoState.AUDIO_ONLY) {
+            videostate |= VideoProfile.VideoState.PAUSED;
+        } else {
+            videostate &= ~VideoProfile.VideoState.PAUSED;
+        }
+        return videostate;
     }
 
     /**
@@ -387,6 +395,14 @@
     }
 
     /**
+     * Checks if video call is paused
+     * @return true if call is video paused
+     */
+    public boolean isVideoPaused() {
+        return mMediaProfile.mVideoDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE;
+    }
+
+    /**
      * Determines if a video state is set in a video state bit-mask.
      *
      * @param videoState The video state bit mask.
diff --git a/telephony/java/com/android/ims/ImsConfigListener.aidl b/telephony/java/com/android/ims/ImsConfigListener.aidl
index e827774..64a5015 100644
--- a/telephony/java/com/android/ims/ImsConfigListener.aidl
+++ b/telephony/java/com/android/ims/ImsConfigListener.aidl
@@ -48,4 +48,26 @@
      * @return void.
      */
     void onSetFeatureResponse(int feature, int network, int value, int status);
-}
\ No newline at end of file
+
+    /**
+     * Notifies client the value of the get operation result on the video quality item.
+     *
+     * @param status. as defined in com.android.ims.ImsConfig#OperationStatusConstants.
+     * @param quality. as defined in com.android.ims.ImsConfig#OperationValuesConstants.
+     * @return void
+     *
+     * @throws ImsException if calling the IMS service results in an error.
+     */
+     void onGetVideoQuality(int status, int quality);
+
+    /**
+     * Notifies client the set value operation result for video quality item.
+     * Used by clients that need to be notified the set operation result.
+     *
+     * @param status. as defined in com.android.ims.ImsConfig#OperationStatusConstants.
+     * @return void
+     *
+     * @throws ImsException if calling the IMS service results in an error.
+     */
+     void onSetVideoQuality(int status);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
index b1f2d32..9b435dc 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -255,4 +255,11 @@
      * @return {@code True} if the session is multiparty.
      */
     boolean isMultiparty();
+
+    /**
+     * Gets the call substate for this session.
+     *
+     * @return the call substate for this session.
+     */
+    int getCallSubstate();
 }
diff --git a/telephony/java/com/android/ims/internal/IImsConfig.aidl b/telephony/java/com/android/ims/internal/IImsConfig.aidl
index 441e03e..7324814 100644
--- a/telephony/java/com/android/ims/internal/IImsConfig.aidl
+++ b/telephony/java/com/android/ims/internal/IImsConfig.aidl
@@ -100,4 +100,24 @@
      * @return void
      */
     boolean getVolteProvisioned();
+
+    /**
+     *
+     * Gets the value for ims fature item video quality.
+     *
+     * @param listener. Video quality value returned asynchronously through listener.
+     * @return void
+     */
+    oneway void getVideoQuality(ImsConfigListener listener);
+
+    /**
+     * Sets the value for IMS feature item video quality.
+     *
+     * @param quality, defines the value of video quality.
+     * @param listener, provided if caller needs to be notified for set result.
+     * @return void
+     *
+     * @throws ImsException if calling the IMS service results in an error.
+     */
+     oneway void setVideoQuality(int quality, ImsConfigListener listener);
 }
diff --git a/telephony/java/com/android/ims/internal/IImsVideoCallCallback.aidl b/telephony/java/com/android/ims/internal/IImsVideoCallCallback.aidl
index f867fcb..be8751b 100644
--- a/telephony/java/com/android/ims/internal/IImsVideoCallCallback.aidl
+++ b/telephony/java/com/android/ims/internal/IImsVideoCallCallback.aidl
@@ -41,7 +41,9 @@
 
     void changePeerDimensions(int width, int height);
 
-    void changeCallDataUsage(int dataUsage);
+    void changeCallDataUsage(long dataUsage);
 
     void changeCameraCapabilities(in CameraCapabilities cameraCapabilities);
+
+    void changeVideoQuality(int videoQuality);
 }
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index b8e8064..0ebd719 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -186,4 +186,11 @@
 
     //FIXME maybe this shouldn't be here - sprout only
     public static final int CAPABILITY_3G   = 1;
+
+    /**
+     * Values for the adb property "persist.radio.videocall.audio.output"
+     */
+    public static final int AUDIO_OUTPUT_ENABLE_SPEAKER = 0;
+    public static final int AUDIO_OUTPUT_DISABLE_SPEAKER = 1;
+    public static final int AUDIO_OUTPUT_DEFAULT = AUDIO_OUTPUT_ENABLE_SPEAKER;
 }
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index c89208d..645c3a1 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -209,4 +209,12 @@
      * Set to the sim count.
      */
     static final String PROPERTY_SIM_COUNT = "ro.telephony.sim.count";
+
+    /**
+     * Controls audio route for video calls.
+     * 0 - Use the default audio routing strategy.
+     * 1 - Disable the speaker. Route the audio to Headset or Bluetooth
+     *     or Earpiece, based on the default audio routing strategy.
+     */
+    static final String PROPERTY_VIDEOCALL_AUDIO_OUTPUT = "persist.radio.call.audio.output";
 }