Merge "SurfaceTexture: Allow creation in detached mode"
diff --git a/api/current.txt b/api/current.txt
index fbbae6c..1f1fd65 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12517,6 +12517,7 @@
     field public static final int CONTROL_SCENE_MODE_DISABLED = 0; // 0x0
     field public static final int CONTROL_SCENE_MODE_FACE_PRIORITY = 1; // 0x1
     field public static final int CONTROL_SCENE_MODE_FIREWORKS = 12; // 0xc
+    field public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17; // 0x11
     field public static final int CONTROL_SCENE_MODE_LANDSCAPE = 4; // 0x4
     field public static final int CONTROL_SCENE_MODE_NIGHT = 5; // 0x5
     field public static final int CONTROL_SCENE_MODE_NIGHT_PORTRAIT = 6; // 0x6
@@ -27662,14 +27663,14 @@
 
   public abstract class Connection {
     ctor protected Connection();
-    method public final void conference();
     method public final android.telecomm.CallAudioState getCallAudioState();
-    method public java.util.List<android.telecomm.Connection> getChildConnections();
+    method public final java.util.List<android.telecomm.Connection> getChildConnections();
     method public final android.net.Uri getHandle();
-    method public android.telecomm.Connection getParentConnection();
-    method public boolean isConferenceCapable();
-    method public boolean isConferenceConnection();
-    method public boolean isRequestingRingback();
+    method public final android.telecomm.Connection getParentConnection();
+    method public final int getState();
+    method public final boolean isConferenceCapable();
+    method public final boolean isConferenceConnection();
+    method public final boolean isRequestingRingback();
     method protected void onAbort();
     method protected void onAnswer();
     method protected void onChildrenChanged(java.util.List<android.telecomm.Connection>);
@@ -27681,21 +27682,20 @@
     method protected void onReject();
     method protected void onSeparate();
     method protected void onSetAudioState(android.telecomm.CallAudioState);
-    method protected void onSetSignal(android.os.Bundle);
     method protected void onSetState(int);
     method protected void onStopDtmfTone();
     method protected void onUnhold();
-    method protected void setActive();
-    method public void setAudioState(android.telecomm.CallAudioState);
-    method protected void setDestroyed();
-    method protected void setDialing();
-    method protected void setDisconnected(int, java.lang.String);
-    method protected void setHandle(android.net.Uri);
-    method protected void setIsConferenceCapable(boolean);
-    method protected void setOnHold();
-    method public void setParentConnection(android.telecomm.Connection);
-    method protected void setRequestingRingback(boolean);
-    method protected void setRinging();
+    method public final void setActive();
+    method public final void setDestroyed();
+    method public final void setDialing();
+    method public final void setDisconnected(int, java.lang.String);
+    method public final void setHandle(android.net.Uri);
+    method public final void setIsConferenceCapable(boolean);
+    method public final void setOnHold();
+    method public final void setParentConnection(android.telecomm.Connection);
+    method public final void setRequestingRingback(boolean);
+    method public final void setRinging();
+    method public final void setSignal(android.os.Bundle);
     method public static java.lang.String stateToString(int);
   }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 88746bf4..87140a3 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -538,6 +538,30 @@
         }
 
         /**
+         * Sets the label for this task description.
+         * @hide
+         */
+        public void setLabel(String label) {
+            mLabel = label;
+        }
+
+        /**
+         * Sets the primary color for this task description.
+         * @hide
+         */
+        public void setPrimaryColor(int primaryColor) {
+            mColorPrimary = primaryColor;
+        }
+
+        /**
+         * Sets the icon for this task description.
+         * @hide
+         */
+        public void setIcon(Bitmap icon) {
+            mIcon = icon;
+        }
+
+        /**
          * @return The label and description of the current state of this task.
          */
         public String getLabel() {
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index ef8c67b..14e6f92 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -466,6 +466,34 @@
             new Key<Integer>("android.control.maxRegionsAf", int.class);
 
     /**
+     * <p>List of available high speed video size and fps range configurations
+     * supported by the camera device, in the format of (width, height, fps_min, fps_max).</p>
+     * <p>When HIGH_SPEED_VIDEO is supported in {@link CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES android.control.availableSceneModes},
+     * this metadata will list the supported high speed video size and fps range
+     * configurations. All the sizes listed in this configuration will be a subset
+     * of the sizes reported by StreamConfigurationMap#getOutputSizes for processed
+     * non-stalling formats.</p>
+     * <p>For the high speed video use case, where the application will set
+     * {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} to HIGH_SPEED_VIDEO in capture requests, the application must
+     * select the video size and fps range from this metadata to configure the recording and
+     * preview streams and setup the recording requests. For example, if the application intends
+     * to do high speed recording, it can select the maximum size reported by this metadata to
+     * configure output streams. Once the size is selected, application can filter this metadata
+     * by selected size and get the supported fps ranges, and use these fps ranges to setup the
+     * recording requests.</p>
+     * <p>For normal video recording use case, where some application will NOT set
+     * {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} to HIGH_SPEED_VIDEO in capture requests, the fps ranges
+     * reported in this metadata must not be used to setup capture requests, or it will cause
+     * request error.</p>
+     *
+     * @see CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES
+     * @see CaptureRequest#CONTROL_SCENE_MODE
+     * @hide
+     */
+    public static final Key<int[]> CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS =
+            new Key<int[]>("android.control.availableHighSpeedVideoConfigurations", int[].class);
+
+    /**
      * <p>The set of edge enhancement modes supported by this camera device.</p>
      * <p>This tag lists the valid modes for {@link CaptureRequest#EDGE_MODE android.edge.mode}.</p>
      * <p>Full-capability camera devices must always support OFF and FAST.</p>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 889b127..e464f2a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1393,6 +1393,84 @@
      */
     public static final int CONTROL_SCENE_MODE_BARCODE = 16;
 
+    /**
+     * <p>Optimized for high speed video recording (frame rate &gt;=60fps) use case.</p>
+     * <p>The supported high speed video sizes and fps ranges are specified in
+     * android.control.availableHighSpeedVideoConfigurations. To get desired
+     * output frame rates, the application is only allowed to select video size
+     * and fps range combinations listed in this static metadata. The fps range
+     * can be control via {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}.</p>
+     * <p>In this mode, the camera device will override aeMode, awbMode, and afMode to
+     * ON, ON, and CONTINUOUS_VIDEO, respectively. All post-processing block mode
+     * controls will be overridden to be FAST. Therefore, no manual control of capture
+     * and post-processing parameters is possible. All other controls operate the
+     * same as when {@link CaptureRequest#CONTROL_MODE android.control.mode} == AUTO. This means that all other
+     * android.control.* fields continue to work, such as</p>
+     * <ul>
+     * <li>{@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}</li>
+     * <li>{@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION android.control.aeExposureCompensation}</li>
+     * <li>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock}</li>
+     * <li>{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock}</li>
+     * <li>{@link CaptureRequest#CONTROL_EFFECT_MODE android.control.effectMode}</li>
+     * <li>{@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}</li>
+     * <li>{@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}</li>
+     * <li>{@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}</li>
+     * <li>{@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger}</li>
+     * <li>{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}</li>
+     * </ul>
+     * <p>Outside of android.control.*, the following controls will work:</p>
+     * <ul>
+     * <li>{@link CaptureRequest#FLASH_MODE android.flash.mode} (automatic flash for still capture will not work since aeMode is ON)</li>
+     * <li>{@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode} (if it is supported)</li>
+     * <li>{@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}</li>
+     * <li>{@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode}</li>
+     * </ul>
+     * <p>For high speed recording use case, the actual maximum supported frame rate may
+     * be lower than what camera can output, depending on the destination Surfaces for
+     * the image data. For example, if the destination surface is from video encoder,
+     * the application need check if the video encoder is capable of supporting the
+     * high frame rate for a given video size, or it will end up with lower recording
+     * frame rate. If the destination surface is from preview window, the preview frame
+     * rate will be bounded by the screen refresh rate.</p>
+     * <p>The camera device will only support up to 2 output high speed streams
+     * (processed non-stalling format defined in android.request.maxNumOutputStreams)
+     * in this mode. This control will be effective only if all of below conditions are true:</p>
+     * <ul>
+     * <li>The application created no more than maxNumHighSpeedStreams processed non-stalling
+     * format output streams, where maxNumHighSpeedStreams is calculated as
+     * min(2, android.request.maxNumOutputStreams[Processed (but not-stalling)]).</li>
+     * <li>The stream sizes are selected from the sizes reported by
+     * android.control.availableHighSpeedVideoConfigurations.</li>
+     * <li>No processed non-stalling or raw streams are configured.</li>
+     * </ul>
+     * <p>When above conditions are NOT satistied, the controls of this mode and
+     * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} will be ignored by the camera device,
+     * the camera device will fall back to {@link CaptureRequest#CONTROL_MODE android.control.mode} <code>==</code> AUTO,
+     * and the returned capture result metadata will give the fps range choosen
+     * by the camera device.</p>
+     * <p>Switching into or out of this mode may trigger some camera ISP/sensor
+     * reconfigurations, which may introduce extra latency. It is recommended that
+     * the application avoids unnecessary scene mode switch as much as possible.</p>
+     *
+     * @see CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION
+     * @see CaptureRequest#CONTROL_AE_LOCK
+     * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+     * @see CaptureRequest#CONTROL_AE_REGIONS
+     * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
+     * @see CaptureRequest#CONTROL_AF_REGIONS
+     * @see CaptureRequest#CONTROL_AF_TRIGGER
+     * @see CaptureRequest#CONTROL_AWB_LOCK
+     * @see CaptureRequest#CONTROL_AWB_REGIONS
+     * @see CaptureRequest#CONTROL_EFFECT_MODE
+     * @see CaptureRequest#CONTROL_MODE
+     * @see CaptureRequest#FLASH_MODE
+     * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
+     * @see CaptureRequest#SCALER_CROP_REGION
+     * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
+     * @see CaptureRequest#CONTROL_SCENE_MODE
+     */
+    public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17;
+
     //
     // Enumeration values for CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
     //
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5bc59dc..91ff7fa 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1011,6 +1011,7 @@
      * @see #CONTROL_SCENE_MODE_PARTY
      * @see #CONTROL_SCENE_MODE_CANDLELIGHT
      * @see #CONTROL_SCENE_MODE_BARCODE
+     * @see #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO
      */
     public static final Key<Integer> CONTROL_SCENE_MODE =
             new Key<Integer>("android.control.sceneMode", int.class);
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 9097220..be2d960 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -1581,6 +1581,7 @@
      * @see #CONTROL_SCENE_MODE_PARTY
      * @see #CONTROL_SCENE_MODE_CANDLELIGHT
      * @see #CONTROL_SCENE_MODE_BARCODE
+     * @see #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO
      */
     public static final Key<Integer> CONTROL_SCENE_MODE =
             new Key<Integer>("android.control.sceneMode", int.class);
diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java
index d86dd5e..c87b674 100644
--- a/core/java/android/hardware/hdmi/HdmiCec.java
+++ b/core/java/android/hardware/hdmi/HdmiCec.java
@@ -101,6 +101,9 @@
     /** Logical address used to indicate it is not initialized or invalid. */
     public static final int ADDR_INVALID = -1;
 
+    /** Logical address used to indicate the source comes from internal device. */
+    public static final int ADDR_INTERNAL = 0xFFFF;
+
     // TODO: Complete the list of CEC messages definition.
     public static final int MESSAGE_FEATURE_ABORT = 0x00;
     public static final int MESSAGE_IMAGE_VIEW_ON = 0x04;
diff --git a/docs/html/preview/index.html b/docs/html/preview/index.html
index 4f3f150..51597fe 100644
--- a/docs/html/preview/index.html
+++ b/docs/html/preview/index.html
@@ -257,7 +257,7 @@
                     Join the community of Android developers testing out the L Developer Preview and
                     share your thoughts and experiences.
                   </p><p class="landing-small">
-                    <a href="https://plus.google.com/communities/113159138894928487684">
+                    <a href="https://plus.google.com/communities/101985907812750684586">
                     Discuss on Google+</a>
                     </p>
                 </div>
diff --git a/docs/html/preview/support.jd b/docs/html/preview/support.jd
index 8efc4bc..9d7844b 100644
--- a/docs/html/preview/support.jd
+++ b/docs/html/preview/support.jd
@@ -7,7 +7,7 @@
 our issue tracker.</p>
 
 <p>For more support,
-<a href="https://plus.google.com/communities/113159138894928487684">join
+<a href="https://plus.google.com/communities/101985907812750684586">join
 the L Developer Preview Google+ community</a> to discuss your development experiences.
 
 
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 0825f2e..f4e9876 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -79,8 +79,6 @@
     private static final String ATTR_LAUNCHEDFROMPACKAGE = "launched_from_package";
     private static final String ATTR_RESOLVEDTYPE = "resolved_type";
     private static final String ATTR_COMPONENTSPECIFIED = "component_specified";
-    private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
-    private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
     private static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_";
 
     final ActivityManagerService service; // owner
@@ -1064,20 +1062,10 @@
         }
         out.attribute(null, ATTR_COMPONENTSPECIFIED, String.valueOf(componentSpecified));
         out.attribute(null, ATTR_USERID, String.valueOf(userId));
+
         if (taskDescription != null) {
-            final String label = taskDescription.getLabel();
-            if (label != null) {
-                out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
-            }
-            final int colorPrimary = taskDescription.getPrimaryColor();
-            if (colorPrimary != 0) {
-                out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
-            }
-            final Bitmap icon = taskDescription.getIcon();
-            if (icon != null) {
-                TaskPersister.saveImage(icon, String.valueOf(task.taskId) + ACTIVITY_ICON_SUFFIX +
-                        createTime);
-            }
+            TaskPersister.saveTaskDescription(taskDescription, String.valueOf(task.taskId) +
+                    ACTIVITY_ICON_SUFFIX + createTime, out);
         }
 
         out.startTag(null, TAG_INTENT);
@@ -1100,10 +1088,9 @@
         String resolvedType = null;
         boolean componentSpecified = false;
         int userId = 0;
-        String activityLabel = null;
-        int activityColor = 0;
         long createTime = -1;
         final int outerDepth = in.getDepth();
+        TaskDescription taskDescription = new TaskDescription();
 
         for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
             final String attrName = in.getAttributeName(attrNdx);
@@ -1122,10 +1109,9 @@
                 componentSpecified = Boolean.valueOf(attrValue);
             } else if (ATTR_USERID.equals(attrName)) {
                 userId = Integer.valueOf(attrValue);
-            } else if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
-                activityLabel = attrValue;
-            } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
-                activityColor = (int) Long.parseLong(attrValue, 16);
+            } else if (TaskPersister.readTaskDescriptionAttribute(taskDescription, attrName,
+                    attrValue)) {
+                // Completed in TaskPersister.readTaskDescriptionAttribute()
             } else {
                 Log.d(TAG, "Unknown ActivityRecord attribute=" + attrName);
             }
@@ -1169,12 +1155,11 @@
 
         r.persistentState = persistentState;
 
-        Bitmap icon = null;
         if (createTime >= 0) {
-            icon = TaskPersister.restoreImage(String.valueOf(taskId) + ACTIVITY_ICON_SUFFIX +
-                    createTime);
+            taskDescription.setIcon(TaskPersister.restoreImage(String.valueOf(taskId) +
+                    ACTIVITY_ICON_SUFFIX + createTime));
         }
-        r.taskDescription = new TaskDescription(activityLabel, icon, activityColor);
+        r.taskDescription = taskDescription;
         r.createTime = createTime;
 
         return r;
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index c79b33d..132b244 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import android.app.ActivityManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Debug;
@@ -56,6 +57,9 @@
 
     private static final String TAG_TASK = "task";
 
+    private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
+    private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
+
     private static File sImagesDir;
     private static File sTasksDir;
 
@@ -143,6 +147,36 @@
         }
     }
 
+    static void saveTaskDescription(ActivityManager.TaskDescription taskDescription,
+            String iconFilename, XmlSerializer out) throws IOException {
+        if (taskDescription != null) {
+            final String label = taskDescription.getLabel();
+            if (label != null) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
+            }
+            final int colorPrimary = taskDescription.getPrimaryColor();
+            if (colorPrimary != 0) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
+            }
+            final Bitmap icon = taskDescription.getIcon();
+            if (icon != null) {
+                saveImage(icon, iconFilename);
+            }
+        }
+    }
+
+    static boolean readTaskDescriptionAttribute(ActivityManager.TaskDescription taskDescription,
+        String attrName, String attrValue) {
+        if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
+            taskDescription.setLabel(attrValue);
+            return true;
+        } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
+            taskDescription.setPrimaryColor((int) Long.parseLong(attrValue, 16));
+            return true;
+        }
+        return false;
+    }
+
     ArrayList<TaskRecord> restoreTasksLocked() {
         final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
         ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 57dee2e..0bd7ef9 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -60,6 +60,7 @@
     private static final String ATTR_LASTDESCRIPTION = "last_description";
     private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
     private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
+    private static final String LAST_ACTIVITY_ICON_SUFFIX = "_last_activity_icon_";
 
     private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
 
@@ -138,7 +139,8 @@
             String _affinity, ComponentName _realActivity, ComponentName _origActivity,
             boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId,
             String _lastDescription, ArrayList<ActivityRecord> activities, long _lastActiveTime,
-            long lastTimeMoved, boolean neverRelinquishIdentity) {
+            long lastTimeMoved, boolean neverRelinquishIdentity,
+            ActivityManager.TaskDescription _lastTaskDescription) {
         mService = service;
         taskId = _taskId;
         intent = _intent;
@@ -158,8 +160,7 @@
         mActivities = activities;
         mLastTimeMoved = lastTimeMoved;
         mNeverRelinquishIdentity = neverRelinquishIdentity;
-        // Recompute the task description for this task
-        updateTaskDescription();
+        lastTaskDescription = _lastTaskDescription;
     }
 
     void touchActiveTime() {
@@ -714,6 +715,11 @@
             out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
         }
 
+        if (lastTaskDescription != null) {
+            TaskPersister.saveTaskDescription(lastTaskDescription, String.valueOf(taskId) +
+                    LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime, out);
+        }
+
         if (affinityIntent != null) {
             out.startTag(null, TAG_AFFINITYINTENT);
             affinityIntent.saveToXml(out);
@@ -758,11 +764,12 @@
         int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
         int userId = 0;
         String lastDescription = null;
-        long lastActiveTime = 0;
+        long lastActiveTime = -1;
         long lastTimeOnTop = 0;
         boolean neverRelinquishIdentity = true;
         int taskId = -1;
         final int outerDepth = in.getDepth();
+        ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription();
 
         for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
             final String attrName = in.getAttributeName(attrNdx);
@@ -793,6 +800,9 @@
                 lastTimeOnTop = Long.valueOf(attrValue);
             } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
                 neverRelinquishIdentity = Boolean.valueOf(attrValue);
+            } else if (TaskPersister.readTaskDescriptionAttribute(taskDescription, attrName,
+                    attrValue)) {
+                // Completed in TaskPersister.readTaskDescriptionAttribute()
             } else {
                 Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
             }
@@ -824,10 +834,15 @@
             }
         }
 
+        if (lastActiveTime >= 0) {
+            taskDescription.setIcon(TaskPersister.restoreImage(String.valueOf(taskId) +
+                    LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime));
+        }
+
         final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
                 affinityIntent, affinity, realActivity, origActivity, rootHasReset,
                 askedCompatMode, taskType, userId, lastDescription, activities, lastActiveTime,
-                lastTimeOnTop, neverRelinquishIdentity);
+                lastTimeOnTop, neverRelinquishIdentity, taskDescription);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
             final ActivityRecord r = activities.get(activityNdx);
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
index 74eaf2a..8de6763 100644
--- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -27,18 +27,18 @@
 /**
  * Handles CEC command &lt;Active Source&gt;.
  * <p>
- * Used by feature actions that need to handle the command in their flow.
+ * Used by feature actions that need to handle the command in their flow. Only for TV
+ * local device.
  */
 final class ActiveSourceHandler {
     private static final String TAG = "ActiveSourceHandler";
 
-    private final HdmiCecLocalDevice mSource;
+    private final HdmiCecLocalDeviceTv mSource;
     private final HdmiControlService mService;
     @Nullable
     private final IHdmiControlCallback mCallback;
 
-    static ActiveSourceHandler create(HdmiCecLocalDevice source,
-            IHdmiControlCallback callback) {
+    static ActiveSourceHandler create(HdmiCecLocalDeviceTv source, IHdmiControlCallback callback) {
         if (source == null) {
             Slog.e(TAG, "Wrong arguments");
             return null;
@@ -46,7 +46,7 @@
         return new ActiveSourceHandler(source, callback);
     }
 
-    private ActiveSourceHandler(HdmiCecLocalDevice source, IHdmiControlCallback callback) {
+    private ActiveSourceHandler(HdmiCecLocalDeviceTv source, IHdmiControlCallback callback) {
         mSource = source;
         mService = mSource.getService();
         mCallback = callback;
@@ -55,48 +55,46 @@
     /**
      * Handles the incoming active source command.
      *
-     * @param deviceLogicalAddress logical address of the device to be the active source
-     * @param routingPath routing path of the device to be the active source
+     * @param activeAddress logical address of the device to be the active source
+     * @param activePath routing path of the device to be the active source
      */
-    void process(int deviceLogicalAddress, int routingPath) {
-        if (getSourcePath() == routingPath && mSource.getActiveSource() == getSourceAddress()) {
+    void process(int activeAddress, int activePath) {
+        // Seq #17
+        HdmiCecLocalDeviceTv tv = mSource;
+        if (getSourcePath() == activePath && tv.getActiveSource() == getSourceAddress()) {
             invokeCallback(HdmiCec.RESULT_SUCCESS);
             return;
         }
-        HdmiCecDeviceInfo device = mService.getDeviceInfo(deviceLogicalAddress);
+        HdmiCecDeviceInfo device = mService.getDeviceInfo(activeAddress);
         if (device == null) {
             // "New device action" initiated by <Active Source> does not require
             // "Routing change action".
-            mSource.addAndStartAction(new NewDeviceAction(mSource, deviceLogicalAddress,
-                    routingPath, false));
+            tv.addAndStartAction(new NewDeviceAction(tv, activeAddress, activePath, false));
         }
 
-        if (!mSource.isInPresetInstallationMode()) {
-            int prevActiveInput = mSource.getActivePortId();
-            mSource.updateActiveDevice(deviceLogicalAddress, routingPath);
-            if (prevActiveInput != mSource.getActivePortId()) {
-                // TODO: change port input here.
+        int currentActive = tv.getActiveSource();
+        int currentPath = tv.getActivePath();
+        if (!tv.isInPresetInstallationMode()) {
+            tv.updateActiveSource(activeAddress, activePath);
+            if (currentActive != activeAddress && currentPath != activePath) {
+                tv.updateActivePortId(mService.pathToPortId(activePath));
             }
             invokeCallback(HdmiCec.RESULT_SUCCESS);
         } else {
             // TV is in a mode that should keep its current source/input from
             // being changed for its operation. Reclaim the active source
             // or switch the port back to the one used for the current mode.
-            if (mSource.getActiveSource() == getSourceAddress()) {
+            if (currentActive == getSourceAddress()) {
                 HdmiCecMessage activeSource =
-                        HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(),
-                                getSourcePath());
+                        HdmiCecMessageBuilder.buildActiveSource(currentActive, currentPath);
                 mService.sendCecCommand(activeSource);
-                mSource.updateActiveDevice(deviceLogicalAddress, routingPath);
+                tv.updateActiveSource(currentActive, currentPath);
                 invokeCallback(HdmiCec.RESULT_SUCCESS);
             } else {
-                int activePath = mSource.getActivePath();
-                mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(getSourceAddress(),
-                        routingPath, activePath));
-                // TODO: Start port select action here
-                // PortSelectAction action = new PortSelectAction(mService, getSourceAddress(),
-                // activePath, mCallback);
-                // mService.addActionAndStart(action);
+                HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange(
+                        getSourceAddress(), activePath, currentPath);
+                mService.sendCecCommand(routingChange);
+                tv.addAndStartAction(new RoutingControlAction(tv, currentPath, mCallback));
             }
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
index dbe3b80..fd3341a 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -77,7 +77,7 @@
      * @param target target logical device that will be a new active source
      * @param callback callback object
      */
-    public DeviceSelectAction(HdmiCecLocalDevice source,
+    public DeviceSelectAction(HdmiCecLocalDeviceTv source,
             HdmiCecDeviceInfo target, IHdmiControlCallback callback) {
         super(source);
         mCallback = callback;
@@ -116,7 +116,7 @@
                 if (opcode == HdmiCec.MESSAGE_ACTIVE_SOURCE && params.length == 2) {
                     int activePath = HdmiUtils.twoBytesToInt(params);
                     ActiveSourceHandler
-                            .create(localDevice(), mCallback)
+                            .create((HdmiCecLocalDeviceTv) localDevice(), mCallback)
                             .process(cmd.getSource(), activePath);
                     finish();
                     return true;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 6f7f5c2..f72d3f0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -125,6 +125,12 @@
             return true;
         }
         switch (message.getOpcode()) {
+            case HdmiCec.MESSAGE_ACTIVE_SOURCE:
+                return handleActiveSource(message);
+            case HdmiCec.MESSAGE_INACTIVE_SOURCE:
+                return handleInactiveSource(message);
+            case HdmiCec.MESSAGE_REQUEST_ACTIVE_SOURCE:
+                return handleRequestActiveSource(message);
             case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
                 return handleGetMenuLanguage(message);
             case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
@@ -193,6 +199,21 @@
     }
 
     @ServiceThreadOnly
+    protected boolean handleActiveSource(HdmiCecMessage message) {
+        return false;
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleInactiveSource(HdmiCecMessage message) {
+        return false;
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+        return false;
+    }
+
+    @ServiceThreadOnly
     protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
@@ -383,15 +404,24 @@
         }
     }
 
-    /**
-     * Returns the active routing path.
-     */
+    void setActiveSource(int source) {
+        synchronized (mLock) {
+            mActiveSource = source;
+        }
+    }
+
     int getActivePath() {
         synchronized (mLock) {
             return mActiveRoutingPath;
         }
     }
 
+    void setActivePath(int path) {
+        synchronized (mLock) {
+            mActiveRoutingPath = path;
+        }
+    }
+
     /**
      * Returns the ID of the active HDMI port. The active port is the one that has the active
      * routing path connected to it directly or indirectly under the device hierarchy.
@@ -429,6 +459,13 @@
     }
 
     boolean isInPresetInstallationMode() {
+        // TODO: Change this to check the right flag.
+        synchronized (mLock) {
+            return !mInputChangeEnabled;
+        }
+    }
+
+    boolean isHdmiControlEnabled() {
         synchronized (mLock) {
             return !mInputChangeEnabled;
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 0333dbf..2431ec4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -48,6 +48,14 @@
     @GuardedBy("mLock")
     private boolean mSystemAudioMode;
 
+    // The previous port id (input) before switching to the new one. This is remembered in order to
+    // be able to switch to it upon receiving <Inactive Source> from currently active source.
+    // This remains valid only when the active source was switched via one touch play operation
+    // (either by TV or source device). Manual port switching invalidates this value to
+    // HdmiConstants.PORT_INVALID, for which case <Inactive Source> does not do anything.
+    @GuardedBy("mLock")
+    private int mPrevPortId;
+
     // Copy of mDeviceInfos to guarantee thread-safety.
     @GuardedBy("mLock")
     private List<HdmiCecDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
@@ -62,7 +70,7 @@
 
     HdmiCecLocalDeviceTv(HdmiControlService service) {
         super(service, HdmiCec.DEVICE_TV);
-
+        mPrevPortId = HdmiConstants.INVALID_PORT_ID;
         // TODO: load system audio mode and set it to mSystemAudioMode.
     }
 
@@ -90,6 +98,10 @@
     @ServiceThreadOnly
     void deviceSelect(int targetAddress, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
+        if (targetAddress == HdmiCec.ADDR_INTERNAL) {
+            handleSelectInternalSource(callback);
+            return;
+        }
         HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
         if (targetDevice == null) {
             invokeCallback(callback, HdmiCec.RESULT_TARGET_NOT_AVAILABLE);
@@ -99,30 +111,85 @@
         addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
     }
 
-    /**
-     * Performs the action routing control.
-     *
-     * @param portId new HDMI port to route to
-     * @param callback callback object to report the result with
-     */
     @ServiceThreadOnly
-    void portSelect(int portId, IHdmiControlCallback callback) {
+    private void handleSelectInternalSource(IHdmiControlCallback callback) {
         assertRunOnServiceThread();
-        if (isInPresetInstallationMode()) {
+        // Seq #18
+        if (isHdmiControlEnabled() && getActiveSource() != mAddress) {
+            updateActiveSource(mAddress, mService.getPhysicalAddress());
+            // TODO: Check if this comes from <Text/Image View On> - if true, do nothing.
+            HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+                    mAddress, mService.getPhysicalAddress());
+            mService.sendCecCommand(activeSource);
+        }
+    }
+
+    @ServiceThreadOnly
+    void updateActiveSource(int activeSource, int activePath) {
+        assertRunOnServiceThread();
+        // Seq #14
+        if (activeSource == getActiveSource() && activePath == getActivePath()) {
+            return;
+        }
+        setActiveSource(activeSource);
+        setActivePath(activePath);
+        if (getDeviceInfo(activeSource) != null && activeSource != mAddress) {
+            if (mService.pathToPortId(activePath) == getActivePortId()) {
+                setPrevPortId(getActivePortId());
+            }
+            // TODO: Show the OSD banner related to the new active source device.
+        } else {
+            // TODO: If displayed, remove the OSD banner related to the previous
+            //       active source device.
+        }
+    }
+
+    /**
+     * Returns the previous port id kept to handle input switching on <Inactive Source>.
+     */
+    int getPrevPortId() {
+        synchronized (mLock) {
+            return mPrevPortId;
+        }
+    }
+
+    /**
+     * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
+     * taken for <Inactive Source>.
+     */
+    void setPrevPortId(int portId) {
+        synchronized (mLock) {
+            mPrevPortId = portId;
+        }
+    }
+
+    @ServiceThreadOnly
+    void updateActivePortId(int portId) {
+        assertRunOnServiceThread();
+        // Seq #15
+        if (portId == getActivePortId()) {
+            return;
+        }
+        setPrevPortId(portId);
+        // TODO: Actually switch the physical port here. Handle PAP/PIP as well.
+        //       Show OSD port change banner
+    }
+
+    @ServiceThreadOnly
+    void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        // Seq #20
+        if (!isHdmiControlEnabled() || portId == getActivePortId()) {
             invokeCallback(callback, HdmiCec.RESULT_INCORRECT_MODE);
             return;
         }
-        // Make sure this call does not stem from <Active Source> message reception, in
-        // which case the two ports will be the same.
-        if (portId == getActivePortId()) {
-            invokeCallback(callback, HdmiCec.RESULT_SUCCESS);
-            return;
-        }
-        setActivePortId(portId);
+        // TODO: Make sure this call does not stem from <Active Source> message reception.
 
+        setActivePortId(portId);
         // TODO: Return immediately if the operation is triggered by <Text/Image View On>
+        //       and this is the first notification about the active input after power-on.
         // TODO: Handle invalid port id / active input which should be treated as an
-        //        internal tuner.
+        //       internal tuner.
 
         removeAction(RoutingControlAction.class);
 
@@ -168,6 +235,61 @@
 
     @Override
     @ServiceThreadOnly
+    protected boolean handleActiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int activePath = HdmiUtils.twoBytesToInt(message.getParams());
+        ActiveSourceHandler.create(this, null).process(message.getSource(), activePath);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleInactiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // Seq #10
+
+        // Ignore <Inactive Source> from non-active source device.
+        if (getActiveSource() != message.getSource()) {
+            return true;
+        }
+        if (isInPresetInstallationMode()) {
+            return true;
+        }
+        int portId = getPrevPortId();
+        if (portId != HdmiConstants.INVALID_PORT_ID) {
+            // TODO: Do this only if TV is not showing multiview like PIP/PAP.
+
+            HdmiCecDeviceInfo inactiveSource = getDeviceInfo(message.getSource());
+            if (inactiveSource == null) {
+                return true;
+            }
+            if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
+                return true;
+            }
+            // TODO: Switch the TV freeze mode off
+
+            setActivePortId(portId);
+            doManualPortSwitching(portId, null);
+            setPrevPortId(HdmiConstants.INVALID_PORT_ID);
+        }
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        // Seq #19
+        int address = getDeviceInfo().getLogicalAddress();
+        if (address == getActiveSource()) {
+            mService.sendCecCommand(
+                    HdmiCecMessageBuilder.buildActiveSource(address, getActivePath()));
+        }
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
     protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
         HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 53cb81d..ad95181 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -582,7 +582,7 @@
                         invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
                         return;
                     }
-                    tv.portSelect(portId, callback);
+                    tv.doManualPortSwitching(portId, callback);
                 }
             });
         }
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 1783327..e4992d0 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -123,8 +123,6 @@
 
     /**
      * @return The state of this Connection.
-     *
-     * @hide
      */
     public final int getState() {
         return mState;
@@ -275,6 +273,8 @@
 
     /**
      * TODO(santoscordon): Needs updated documentation.
+     *
+     * @hide
      */
     public final void conference() {
         Log.d(this, "conference");
@@ -285,9 +285,14 @@
      * Inform this Connection that the state of its audio output has been changed externally.
      *
      * @param state The new audio state.
+     * @hide
      */
-    public void setAudioState(CallAudioState state) {
+    public final void setAudioState(CallAudioState state) {
         Log.d(this, "setAudioState %s", state);
+        mCallAudioState = state;
+        for (Listener l : mListeners) {
+            l.onAudioStateChanged(this, state);
+        }
         onSetAudioState(state);
     }
 
@@ -319,18 +324,21 @@
      * Returns whether this connection is requesting that the system play a ringback tone
      * on its behalf.
      */
-    public boolean isRequestingRingback() {
+    public final boolean isRequestingRingback() {
         return mRequestingRingback;
     }
 
     /**
      * Returns whether this connection is a conference connection (has child connections).
      */
-    public boolean isConferenceConnection() {
+    public final boolean isConferenceConnection() {
         return !mChildConnections.isEmpty();
     }
 
-    public void setParentConnection(Connection parentConnection) {
+    /**
+     * TODO(santoscordon): Needs documentation.
+     */
+    public final void setParentConnection(Connection parentConnection) {
         Log.d(this, "parenting %s to %s", this, parentConnection);
         if (mParentConnection != parentConnection) {
             if (mParentConnection != null) {
@@ -347,18 +355,18 @@
         }
     }
 
-    public Connection getParentConnection() {
+    public final Connection getParentConnection() {
         return mParentConnection;
     }
 
-    public List<Connection> getChildConnections() {
+    public final List<Connection> getChildConnections() {
         return mChildConnections;
     }
 
     /**
      * Returns whether this connection is capable of being conferenced.
      */
-    public boolean isConferenceCapable() {
+    public final boolean isConferenceCapable() {
         return mIsConferenceCapable;
     }
 
@@ -367,7 +375,7 @@
      *
      * @param handle The new handle.
      */
-    protected void setHandle(Uri handle) {
+    public final void setHandle(Uri handle) {
         Log.d(this, "setHandle %s", handle);
         // TODO: Enforce super called
         mHandle = handle;
@@ -380,7 +388,7 @@
      * Sets state to active (e.g., an ongoing call where two or more parties can actively
      * communicate).
      */
-    protected void setActive() {
+    public final void setActive() {
         setRequestingRingback(false);
         setState(State.ACTIVE);
     }
@@ -388,21 +396,21 @@
     /**
      * Sets state to ringing (e.g., an inbound ringing call).
      */
-    protected void setRinging() {
+    public final void setRinging() {
         setState(State.RINGING);
     }
 
     /**
      * Sets state to dialing (e.g., dialing an outbound call).
      */
-    protected void setDialing() {
+    public final void setDialing() {
         setState(State.DIALING);
     }
 
     /**
      * Sets state to be on hold.
      */
-    protected void setOnHold() {
+    public final void setOnHold() {
         setState(State.HOLDING);
     }
 
@@ -416,7 +424,7 @@
      *         {@link android.telephony.DisconnectCause}.
      * @param message Optional call-service-provided message about the disconnect.
      */
-    protected void setDisconnected(int cause, String message) {
+    public final void setDisconnected(int cause, String message) {
         setState(State.DISCONNECTED);
         Log.d(this, "Disconnected with cause %d message %s", cause, message);
         for (Listener l : mListeners) {
@@ -430,7 +438,7 @@
      *
      * @param ringback Whether the ringback tone is to be played.
      */
-    protected void setRequestingRingback(boolean ringback) {
+    public final void setRequestingRingback(boolean ringback) {
         if (mRequestingRingback != ringback) {
             mRequestingRingback = ringback;
             for (Listener l : mListeners) {
@@ -442,7 +450,7 @@
     /**
      * TODO(santoscordon): Needs documentation.
      */
-    protected void setIsConferenceCapable(boolean isConferenceCapable) {
+    public final void setIsConferenceCapable(boolean isConferenceCapable) {
         if (mIsConferenceCapable != isConferenceCapable) {
             mIsConferenceCapable = isConferenceCapable;
             for (Listener l : mListeners) {
@@ -454,7 +462,7 @@
     /**
      * TODO(santoscordon): Needs documentation.
      */
-    protected void setDestroyed() {
+    public final void setDestroyed() {
         // It is possible that onDestroy() will trigger the listener to remove itself which will
         // result in a concurrent modification exception. To counteract this we make a copy of the
         // listeners and iterate on that.
@@ -466,46 +474,32 @@
     }
 
     /**
-     * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
-     * has a new value.
-     *
-     * @param state The new call audio state.
-     */
-    protected void onSetAudioState(CallAudioState state) {
-        // TODO: Enforce super called
-        mCallAudioState = state;
-        for (Listener l : mListeners) {
-            l.onAudioStateChanged(this, state);
-        }
-    }
-
-    /**
      * Notifies this Connection and listeners of a change in the current signal levels
      * for the underlying data transport.
      *
      * @param details A {@link android.os.Bundle} containing details of the current level.
      */
-    protected void onSetSignal(Bundle details) {
-        // TODO: Enforce super called
+    public final void setSignal(Bundle details) {
         for (Listener l : mListeners) {
             l.onSignalChanged(this, details);
         }
     }
 
     /**
+     * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
+     * has a new value.
+     *
+     * @param state The new call audio state.
+     */
+    protected void onSetAudioState(CallAudioState state) {}
+
+    /**
      * Notifies this Connection of an internal state change. This method is called before the
-     * state is actually changed. Overriding implementations must call
-     * {@code super.onSetState(state)}.
+     * state is actually changed.
      *
      * @param state The new state, a {@link Connection.State} member.
      */
-    protected void onSetState(int state) {
-        // TODO: Enforce super called
-        this.mState = state;
-        for (Listener l : mListeners) {
-            l.onStateChanged(this, state);
-        }
-    }
+    protected void onSetState(int state) {}
 
     /**
      * Notifies this Connection of a request to play a DTMF tone.
@@ -585,6 +579,10 @@
 
     private void setState(int state) {
         Log.d(this, "setState: %s", stateToString(state));
+        this.mState = state;
+        for (Listener l : mListeners) {
+            l.onStateChanged(this, state);
+        }
         onSetState(state);
     }
 }