Merge "Use CharSequence instead of String in CursorAnchorInfo"
diff --git a/docs/html/preview/index.html b/docs/html/preview/index.html
index 51597fe..4c42d99 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/101985907812750684586">
+                    <a href="http://g.co/androidldevpreview">
                     Discuss on Google+</a>
                     </p>
                 </div>
diff --git a/docs/html/preview/material/images/list_mail.png b/docs/html/preview/material/images/list_mail.png
index bd107ff..e70291c 100644
--- a/docs/html/preview/material/images/list_mail.png
+++ b/docs/html/preview/material/images/list_mail.png
Binary files differ
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
index fd3341a..b97350d 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -164,8 +164,10 @@
     }
 
     private void turnOnDevice() {
-        sendRemoteKeyCommand(HdmiConstants.UI_COMMAND_POWER);
-        sendRemoteKeyCommand(HdmiConstants.UI_COMMAND_POWER_ON_FUNCTION);
+        sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
+                HdmiConstants.UI_COMMAND_POWER);
+        sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
+                HdmiConstants.UI_COMMAND_POWER_ON_FUNCTION);
         mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
         addTimer(mState, TIMEOUT_POWER_ON_MS);
     }
@@ -177,13 +179,6 @@
         addTimer(mState, TIMEOUT_ACTIVE_SOURCE_MS);
     }
 
-    private void sendRemoteKeyCommand(int keyCode) {
-        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(),
-                mTarget.getLogicalAddress(), keyCode));
-        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
-                mTarget.getLogicalAddress()));
-    }
-
     @Override
     public void handleTimerEvent(int timeoutState) {
         if (mState != timeoutState) {
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 0ec17f6..cf28f05 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -248,4 +248,11 @@
     protected final int getSourcePath() {
         return mSource.getDeviceInfo().getPhysicalAddress();
     }
+
+    protected void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
+                getSourceAddress(), targetAddress, uiCommand));
+        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
+                getSourceAddress(), targetAddress));
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index f72d3f0..bf7e57b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -151,6 +151,8 @@
                 return handleSetSystemAudioMode(message);
             case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
                 return handleSystemAudioModeStatus(message);
+            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
+                return handleReportAudioStatus(message);
             default:
                 return false;
         }
@@ -263,6 +265,10 @@
         return false;
     }
 
+    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+        return false;
+    }
+
     @ServiceThreadOnly
     final void handleAddressAllocated(int logicalAddress) {
         assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 2431ec4..718072a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -56,6 +56,12 @@
     @GuardedBy("mLock")
     private int mPrevPortId;
 
+    @GuardedBy("mLock")
+    private int mSystemAudioVolume = HdmiConstants.UNKNOWN_VOLUME;
+
+    @GuardedBy("mLock")
+    private boolean mSystemAudioMute = false;
+
     // Copy of mDeviceInfos to guarantee thread-safety.
     @GuardedBy("mLock")
     private List<HdmiCecDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
@@ -353,6 +359,22 @@
         return true;
     }
 
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+
+        byte params[] = message.getParams();
+        if (params.length < 1) {
+            Slog.w(TAG, "Invalide <Report Audio Status> message:" + message);
+            return true;
+        }
+        int mute = params[0] & 0x80;
+        int volume = params[0] & 0x7F;
+        setAudioStatus(mute == 0x80, volume);
+        return true;
+    }
+
     @ServiceThreadOnly
     private void launchDeviceDiscovery() {
         assertRunOnServiceThread();
@@ -458,9 +480,64 @@
         }
     }
 
-    @ServiceThreadOnly
     void setAudioStatus(boolean mute, int volume) {
-        mService.setAudioStatus(mute, volume);
+        synchronized (mLock) {
+            mSystemAudioMute = mute;
+            mSystemAudioVolume = volume;
+            // TODO: pass volume to service (audio service) after scale it to local volume level.
+            mService.setAudioStatus(mute, volume);
+        }
+    }
+
+    @ServiceThreadOnly
+    void changeVolume(int curVolume, int delta, int maxVolume) {
+        assertRunOnServiceThread();
+        if (delta == 0 || !isSystemAudioOn()) {
+            return;
+        }
+
+        int targetVolume = curVolume + delta;
+        int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
+        synchronized (mLock) {
+            // If new volume is the same as current system audio volume, just ignore it.
+            // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
+            if (cecVolume == mSystemAudioVolume) {
+                // Update tv volume with system volume value.
+                mService.setAudioStatus(false,
+                        VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
+                return;
+            }
+        }
+
+        // Remove existing volume action.
+        removeAction(VolumeControlAction.class);
+
+        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
+        addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(),
+                cecVolume, delta > 0));
+    }
+
+    @ServiceThreadOnly
+    void changeMute(boolean mute) {
+        assertRunOnServiceThread();
+        if (!isSystemAudioOn()) {
+            return;
+        }
+
+        // Remove existing volume action.
+        removeAction(VolumeControlAction.class);
+        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
+        addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute));
+    }
+
+    private boolean isSystemAudioOn() {
+        if (getAvrDeviceInfo() == null) {
+            return false;
+        }
+
+        synchronized (mLock) {
+            return mSystemAudioMode;
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiConstants.java b/services/core/java/com/android/server/hdmi/HdmiConstants.java
index 5294506..ab5b8d8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiConstants.java
+++ b/services/core/java/com/android/server/hdmi/HdmiConstants.java
@@ -97,5 +97,12 @@
 
     static final int UNKNOWN_VOLUME = -1;
 
+    // IRT(Initiator Repetition Time) in millisecond as recommended in the standard.
+    // Outgoing UCP commands, when in 'Press and Hold' mode, should be this much apart
+    // from the adjacent one so as not to place unnecessarily heavy load on the CEC line.
+    // TODO: This value might need tweaking per product basis. Consider putting it
+    //       in config.xml to allow customization.
+    static final int IRT_MS = 300;
+
     private HdmiConstants() { /* cannot be instantiated */ }
 }
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index c3078a2..5d81251 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.HdmiConstants.IRT_MS;
+
 import android.hardware.hdmi.HdmiCecMessage;
 import android.util.Slog;
 import android.view.KeyEvent;
@@ -38,13 +40,6 @@
     // persists throughout the process till it is set back to {@code STATE_NONE} at the end.
     private static final int STATE_PROCESSING_KEYCODE = 1;
 
-    // IRT(Initiator Repetition Time) in millisecond as recommended in the standard.
-    // Outgoing UCP commands, when in 'Press and Hold' mode, should be this much apart
-    // from the adjacent one so as not to place unnecessarily heavy load on the CEC line.
-    // TODO: This value might need tweaking per product basis. Consider putting it
-    //       in config.xml to allow customization.
-    private static final int IRT_MS = 450;
-
     // Logical address of the device to which the UCP/UCP commands are sent.
     private final int mTargetAddress;
 
@@ -77,7 +72,6 @@
      *
      * @param keyCode key code of {@link KeyEvent} object
      * @param isPressed true if the key event is of {@link KeyEvent#ACTION_DOWN}
-     * @param param additional parameter that comes with the key event
      */
     void processKeyEvent(int keyCode, boolean isPressed) {
         if (mState != STATE_PROCESSING_KEYCODE) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
index 89206a7..ecb158b 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -72,19 +72,12 @@
         int uiCommand = tv().getSystemAudioMode()
                 ? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION  // SystemAudioMode: ON
                 : HdmiConstants.UI_COMMAND_MUTE_FUNCTION;           // SystemAudioMode: OFF
-        sendUserControlPressedAndReleased(uiCommand);
+        sendUserControlPressedAndReleased(mAvrAddress, uiCommand);
 
         // Still return SUCCESS to callback.
         finishWithCallback(HdmiCec.RESULT_SUCCESS);
     }
 
-    private void sendUserControlPressedAndReleased(int uiCommand) {
-        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
-                getSourceAddress(), mAvrAddress, uiCommand));
-        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
-                getSourceAddress(), mAvrAddress));
-    }
-
     @Override
     boolean processCommand(HdmiCecMessage cmd) {
         if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS) {
@@ -109,7 +102,7 @@
 
             if ((tv().getSystemAudioMode() && mute) || (!tv().getSystemAudioMode() && !mute)) {
                 // Toggle AVR's mute status to match with the system audio status.
-                sendUserControlPressedAndReleased(HdmiConstants.UI_COMMAND_MUTE);
+                sendUserControlPressedAndReleased(mAvrAddress, HdmiConstants.UI_COMMAND_MUTE);
             }
             finishWithCallback(HdmiCec.RESULT_SUCCESS);
         } else {
diff --git a/services/core/java/com/android/server/hdmi/VolumeControlAction.java b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
new file mode 100644
index 0000000..07c72f7
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.HdmiConstants.IRT_MS;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Feature action that transmits volume change to Audio Receiver.
+ * <p>
+ * This action is created when a user pressed volume up/down. However, Since Android only provides a
+ * listener for delta of some volume change, we will set a target volume, and check reported volume
+ * from Audio Receiver(AVR). If TV receives no &lt;Report Audio Status&gt; from AVR, this action
+ * will be finished in {@link #IRT_MS} * {@link #VOLUME_CHANGE_TIMEOUT_MAX_COUNT} (ms).
+ */
+final class VolumeControlAction extends FeatureAction {
+    private static final String TAG = "VolumeControlAction";
+
+    private static final int VOLUME_MUTE = 101;
+    private static final int VOLUME_RESTORE = 102;
+    private static final int MAX_VOLUME = 100;
+    private static final int MIN_VOLUME = 0;
+
+    // State where to wait for <Report Audio Status>
+    private static final int STATE_WAIT_FOR_REPORT_VOLUME_STATUS = 1;
+
+    // Maximum count of time out used to finish volume action.
+    private static final int VOLUME_CHANGE_TIMEOUT_MAX_COUNT = 2;
+
+    private final int mAvrAddress;
+    private final int mTargetVolume;
+    private final boolean mIsVolumeUp;
+    private int mTimeoutCount;
+
+    /**
+     * Create a {@link VolumeControlAction} for mute/restore change
+     *
+     * @param source source device sending volume change
+     * @param avrAddress address of audio receiver
+     * @param mute whether to mute sound or not. {@code true} for mute on; {@code false} for mute
+     *            off, i.e restore volume
+     * @return newly created {@link VolumeControlAction}
+     */
+    public static VolumeControlAction ofMute(HdmiCecLocalDevice source, int avrAddress,
+            boolean mute) {
+        return new VolumeControlAction(source, avrAddress, mute ? VOLUME_MUTE : VOLUME_RESTORE,
+                false);
+    }
+
+    /**
+     * Create a {@link VolumeControlAction} for volume up/down change
+     *
+     * @param source source device sending volume change
+     * @param avrAddress address of audio receiver
+     * @param targetVolume target volume to be set to AVR. It should be in range of [0-100]
+     * @param isVolumeUp whether to volume up or not. {@code true} for volume up; {@code false} for
+     *            volume down
+     * @return newly created {@link VolumeControlAction}
+     */
+    public static VolumeControlAction ofVolumeChange(HdmiCecLocalDevice source, int avrAddress,
+            int targetVolume, boolean isVolumeUp) {
+        Preconditions.checkArgumentInRange(targetVolume, MIN_VOLUME, MAX_VOLUME, "volume");
+        return new VolumeControlAction(source, avrAddress, targetVolume, isVolumeUp);
+    }
+
+    /**
+     * Scale a custom volume value to cec volume scale.
+     *
+     * @param volume volume value in custom scale
+     * @param scale scale of volume (max volume)
+     * @return a volume scaled to cec volume range
+     */
+    public static int scaleToCecVolume(int volume, int scale) {
+        return (volume * MAX_VOLUME) / scale;
+    }
+
+    /**
+     * Scale a cec volume which is in range of 0 to 100 to custom volume level.
+     *
+     * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100]
+     * @param scale scale of custom volume (max volume)
+     * @return a volume value scaled to custom volume range
+     */
+    public static int scaleToCustomVolume(int cecVolume, int scale) {
+        return (cecVolume * scale) / MAX_VOLUME;
+    }
+
+    private VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, int targetVolume,
+            boolean isVolumeUp) {
+        super(source);
+
+        mAvrAddress = avrAddress;
+        mTargetVolume = targetVolume;
+        mIsVolumeUp = isVolumeUp;
+    }
+
+    @Override
+    boolean start() {
+        if (isForMute()) {
+            sendMuteChange(mTargetVolume == VOLUME_MUTE);
+            finish();
+            return true;
+        }
+
+        startVolumeChange();
+        return true;
+    }
+
+
+    private boolean isForMute() {
+        return mTargetVolume == VOLUME_MUTE || mTargetVolume == VOLUME_RESTORE;
+    }
+
+
+    private void startVolumeChange() {
+        mTimeoutCount = 0;
+        sendVolumeChange(mIsVolumeUp);
+        mState = STATE_WAIT_FOR_REPORT_VOLUME_STATUS;
+        addTimer(mState, IRT_MS);
+    }
+
+    private void sendVolumeChange(boolean up) {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress,
+                up ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
+                        : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
+    }
+
+    private void sendMuteChange(boolean mute) {
+        sendUserControlPressedAndReleased(mAvrAddress,
+                mute ? HdmiConstants.UI_COMMAND_MUTE_FUNCTION :
+                        HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION);
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
+            return false;
+        }
+
+        switch (cmd.getOpcode()) {
+            case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
+                handleReportAudioStatus(cmd);
+                return true;
+            case HdmiCec.MESSAGE_FEATURE_ABORT:
+                // TODO: handle feature abort.
+                finish();
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void handleReportAudioStatus(HdmiCecMessage cmd) {
+        byte[] params = cmd.getParams();
+        if (params.length != 1) {
+            Slog.e(TAG, "Invalid <Report Audio Status> message:" + cmd);
+            return;
+        }
+
+        int volume = params[0] & 0x7F;
+        // Update volume with new value.
+        // Note that it will affect system volume change.
+        tv().setAudioStatus(false, volume);
+        if (mIsVolumeUp) {
+            if (mTargetVolume <= volume) {
+                finishWithVolumeChangeRelease();
+                return;
+            }
+        } else {
+            if (mTargetVolume >= volume) {
+                finishWithVolumeChangeRelease();
+                return;
+            }
+        }
+
+        // Clear action status and send another volume change command.
+        clear();
+        startVolumeChange();
+    }
+
+    private void finishWithVolumeChangeRelease() {
+        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
+                getSourceAddress(), mAvrAddress));
+        finish();
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
+            return;
+        }
+
+        // If no report volume action after IRT * VOLUME_CHANGE_TIMEOUT_MAX_COUNT just stop volume
+        // action.
+        if (++mTimeoutCount == VOLUME_CHANGE_TIMEOUT_MAX_COUNT) {
+            finishWithVolumeChangeRelease();
+            return;
+        }
+
+        sendVolumeChange(mIsVolumeUp);
+        addTimer(mState, IRT_MS);
+    }
+}