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 <Report Audio Status> 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);
+ }
+}