From b6591b8e5399099dc6b7693e0fc719b613aba89c Mon Sep 17 00:00:00 2001 From: Jungshik Jang Date: Wed, 23 Jul 2014 16:10:23 +0900 Subject: [PATCH] Implement OneTouchRecordAction and stop one touch record. In addition to it, add api for clearTimerRecording as well. Separately, I will replace logic for notifying message with callback interface. Bug: 16160962 Change-Id: I2368f7c697eb44ed4542c0ec4412c63a6ae41a5c --- .../hardware/hdmi/HdmiControlManager.java | 70 +++++++++++ .../hardware/hdmi/HdmiRecordSources.java | 29 ++++- .../android/hardware/hdmi/HdmiTvClient.java | 60 +++++++++- .../hardware/hdmi/IHdmiControlService.aidl | 4 +- .../server/hdmi/HdmiCecLocalDevice.java | 9 ++ .../server/hdmi/HdmiCecLocalDeviceTv.java | 92 +++++++++++++++ .../server/hdmi/HdmiCecMessageBuilder.java | 23 ++++ .../server/hdmi/HdmiControlService.java | 101 +++++++++++++++- .../server/hdmi/OneTouchRecordAction.java | 109 ++++++++++++++---- 9 files changed, 463 insertions(+), 34 deletions(-) diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 56fc1d6dcb93f..521a439c2c57d 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -69,6 +69,76 @@ public final class HdmiControlManager { public static final int RESULT_INCORRECT_MODE = 6; public static final int RESULT_COMMUNICATION_FAILED = 7; + // -- Message ids for display osd. + + /** Place holder for recording status message. Indicates the status of a recording. */ + public static final int MESSAGE_RECORDING_STATUS_MESSAGE_START = 0x100; + /** Recording currently selected source. Indicates the status of a recording. */ + public static final int MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x101; + /** Recording Digital Service. Indicates the status of a recording. */ + public static final int MESSAGE_RECORDING_DIGITAL_SERVICE = 0x102; + /** Recording Analogue Service. Indicates the status of a recording. */ + public static final int MESSAGE_RECORDING_ANALOGUE_SERVICE = 0x103; + /** Recording External input. Indicates the status of a recording. */ + public static final int MESSAGE_RECORDING_EXTERNAL_INPUT = 0x104; + /** No recording – unable to record Digital Service. No suitable tuner. */ + public static final int MESSAGE_NO_RECORDNIG_UNABLE_DIGITAL_SERVICE = 0x105; + /** No recording – unable to record Analogue Service. No suitable tuner. */ + public static final int MESSAGE_NO_RECORDNIG_UNABLE_ANALOGUE_SERVICE = 0x106; + /** + * No recording – unable to select required service. as suitable tuner, but the requested + * parameters are invalid or out of range for that tuner. + */ + public static final int MESSAGE_NO_RECORDNIG_UNABLE_SELECTED_SERVICE = 0x107; + /** No recording – invalid External plug number */ + public static final int MESSAGE_NO_RECORDNIG_INVALID_EXTERNAL_PLUG_NUMBER = 0x109; + /** No recording – invalid External Physical Address */ + public static final int MESSAGE_NO_RECORDNIG_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x10A; + /** No recording – CA system not supported */ + public static final int MESSAGE_NO_RECORDNIG_UNSUPPORTED_CA = 0x10B; + /** No Recording – No or Insufficient CA Entitlements” */ + public static final int MESSAGE_NO_RECORDNIG_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x10C; + /** No recording – Not allowed to copy source. Source is “copy never”. */ + public static final int MESSAGE_NO_RECORDNIG_DISALLOW_TO_COPY = 0x10D; + /** No recording – No further copies allowed */ + public static final int MESSAGE_NO_RECORDNIG_DISALLOW_TO_FUTHER_COPIES = 0x10E; + /** No recording – No media */ + public static final int MESSAGE_NO_RECORDNIG_NO_MEDIA = 0x110; + /** No recording – playing */ + public static final int MESSAGE_NO_RECORDNIG_PLAYING = 0x111; + /** No recording – already recording */ + public static final int MESSAGE_NO_RECORDNIG_ALREADY_RECORDING = 0x112; + /** No recording – media protected */ + public static final int MESSAGE_NO_RECORDNIG_MEDIA_PROTECTED = 0x113; + /** No recording – no source signal */ + public static final int MESSAGE_NO_RECORDNIG_NO_SOURCE_SIGNAL = 0x114; + /** No recording – media problem */ + public static final int MESSAGE_NO_RECORDNIG_MEDIA_PROBLEM = 0x115; + /** No recording – not enough space available */ + public static final int MESSAGE_NO_RECORDNIG_NOT_ENOUGH_SPACE = 0x116; + /** No recording – Parental Lock On */ + public static final int MESSAGE_NO_RECORDNIG_PARENT_LOCK_ON = 0x117; + /** Recording terminated normally */ + public static final int MESSAGE_RECORDING_TERMINATED_NORMALLY = 0x11A; + /** Recording has already terminated */ + public static final int MESSAGE_RECORDING_ALREADY_TERMINATED = 0x11B; + /** No recording – other reason */ + public static final int MESSAGE_NO_RECORDNIG_OTHER_REASON = 0x11F; + // From here extra message for recording that is not mentioned in CEC spec + /** No recording. Previous recording request in progress. */ + public static final int MESSAGE_NO_RECORDING_PREVIOUS_RECORDING_IN_PROGRESS = 0x130; + /** No recording. Please check recorder and connection. */ + public static final int MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION = 0x131; + /** Cannot record currently displayed source. */ + public static final int MESSAGE_NO_RECORDING_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x132; + + /** Timer recording type for digital service source. */ + public static final int TIMER_RECORDING_TYPE_DIGITAL = 1; + /** Timer recording type for analogue service source. */ + public static final int TIMER_RECORDING_TYPE_ANALOGUE = 2; + /** Timer recording type for external source. */ + public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3; + // True if we have a logical device of type playback hosted in the system. private final boolean mHasPlaybackDevice; // True if we have a logical device of type TV hosted in the system. diff --git a/core/java/android/hardware/hdmi/HdmiRecordSources.java b/core/java/android/hardware/hdmi/HdmiRecordSources.java index 685eb170fddb6..296cae6253d91 100644 --- a/core/java/android/hardware/hdmi/HdmiRecordSources.java +++ b/core/java/android/hardware/hdmi/HdmiRecordSources.java @@ -103,8 +103,10 @@ public final class HdmiRecordSources { */ @SystemApi public static final class OwnSource extends RecordSource { - protected OwnSource() { - super(RECORD_SOURCE_TYPE_OWN_SOURCE, 0); + private static final int EXTRA_DATA_SIZE = 0; + + private OwnSource() { + super(RECORD_SOURCE_TYPE_OWN_SOURCE, EXTRA_DATA_SIZE); } @Override @@ -744,4 +746,27 @@ public final class HdmiRecordSources { byteArray[index + 1] = (byte) (value & 0xFF); return 2; } + + /** + * Check the byte array of record source. + * @hide + */ + public static boolean checkRecordSource(byte[] recordSource) { + int recordSourceType = recordSource[0]; + int extraDataSize = recordSource.length - 1; + switch (recordSourceType) { + case RECORD_SOURCE_TYPE_OWN_SOURCE: + return extraDataSize == OwnSource.EXTRA_DATA_SIZE; + case RECORD_SOURCE_TYPE_DIGITAL_SERVICE: + return extraDataSize == DigitalServiceSource.EXTRA_DATA_SIZE; + case RECORD_SOURCE_TYPE_ANALOGUE_SERVICE: + return extraDataSize == AnalogueServiceSource.EXTRA_DATA_SIZE; + case RECORD_SOURCE_TYPE_EXTERNAL_PLUG: + return extraDataSize == ExternalPlugData.EXTRA_DATA_SIZE; + case RECORD_SOURCE_TYPE_EXTERNAL_PHYSICAL_ADDRESS: + return extraDataSize == ExternalPhysicalAddress.EXTRA_DATA_SIZE; + default: + return false; + } + } } diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java index c02ff8ae653f1..6080914e59363 100644 --- a/core/java/android/hardware/hdmi/HdmiTvClient.java +++ b/core/java/android/hardware/hdmi/HdmiTvClient.java @@ -15,6 +15,9 @@ */ package android.hardware.hdmi; +import static android.hardware.hdmi.HdmiRecordSources.RecordSource; +import static android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource; + import android.annotation.SystemApi; import android.os.RemoteException; import android.util.Log; @@ -162,7 +165,7 @@ public final class HdmiTvClient extends HdmiClient { * * @return {@link HdmiRecordSources} to be used to set recording info */ - HdmiRecordSources.RecordSource onRecordRequestReceived(int recorderAddress); + RecordSource onRecordRequestReceived(int recorderAddress); } /** @@ -187,7 +190,7 @@ public final class HdmiTvClient extends HdmiClient { * tvClient.startOneTouchRecord(recorderAddress, ownSource); * */ - public void startOneTouchRecord(int recorderAddress, HdmiRecordSources.RecordSource source) { + public void startOneTouchRecord(int recorderAddress, RecordSource source) { try { byte[] data = new byte[source.getDataSize(true)]; source.toByteArray(true, data, 0); @@ -197,6 +200,19 @@ public final class HdmiTvClient extends HdmiClient { } } + /** + * Stop one touch record. + * + * @param recorderAddress recorder address where recoding will be stopped + */ + public void stopOneTouchRecord(int recorderAddress) { + try { + mService.stopOneTouchRecord(recorderAddress); + } catch (RemoteException e) { + Log.e(TAG, "failed to stop record: ", e); + } + } + /** * Start timer recording with the given recoder address and recorder source. *

@@ -211,13 +227,47 @@ public final class HdmiTvClient extends HdmiClient { * TimerRecordSource source = HdmiTimerRecourdSources.ofDigitalSource(timerInfo, recordSource); * tvClient.startTimerRecording(recorderAddress, source); * + * + * @param recorderAddress target recorder address + * @param sourceType type of record source. It should be one of + * {@link HdmiControlManager#TIMER_RECORDING_TYPE_DIGITAL}, + * {@link HdmiControlManager#TIMER_RECORDING_TYPE_ANALOGUE}, + * {@link HdmiControlManager#TIMER_RECORDING_TYPE_EXTERNAL}. + * @param source record source to be used */ - public void startTimerRecording(int recorderAddress, - HdmiTimerRecordSources.TimerRecordSource source) { + public void startTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) { + checkTimerRecordingSourceType(sourceType); + try { byte[] data = new byte[source.getDataSize()]; source.toByteArray(data, 0); - mService.startTimerRecording(recorderAddress, data); + mService.startTimerRecording(recorderAddress, sourceType, data); + } catch (RemoteException e) { + Log.e(TAG, "failed to start record: ", e); + } + } + + private void checkTimerRecordingSourceType(int sourceType) { + switch (sourceType) { + case HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL: + case HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE: + case HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL: + break; + default: + throw new IllegalArgumentException("Invalid source type:" + sourceType); + } + } + + /** + * Clear timer recording with the given recorder address and recording source. + * For more details, please refer {@link #startTimerRecording(int, int, TimerRecordSource)}. + */ + public void clearTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) { + checkTimerRecordingSourceType(sourceType); + try { + byte[] data = new byte[source.getDataSize()]; + source.toByteArray(data, 0); + mService.clearTimerRecording(recorderAddress, sourceType, data); } catch (RemoteException e) { Log.e(TAG, "failed to start record: ", e); } diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index 53b8b3f153eb6..95e0ee0a1307f 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -64,5 +64,7 @@ interface IHdmiControlService { void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType); void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener); void startOneTouchRecord(int recorderAddress, in byte[] recordSource); - void startTimerRecording(int recorderAddress, in byte[] recordSource); + void stopOneTouchRecord(int recorderAddress); + void startTimerRecording(int recorderAddress, int sourceType, in byte[] recordSource); + void clearTimerRecording(int recorderAddress, int sourceType, in byte[] recordSource); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index f1e7ff2c17650..dbcdfc01e6e47 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -200,6 +200,8 @@ abstract class HdmiCecLocalDevice { return handleVendorCommandWithId(message); case Constants.MESSAGE_SET_OSD_NAME: return handleSetOsdName(message); + case Constants.MESSAGE_RECORD_TV_SCREEN: + return handleRecordTvScreen(message); default: return false; } @@ -403,6 +405,13 @@ abstract class HdmiCecLocalDevice { return true; } + protected boolean handleRecordTvScreen(HdmiCecMessage message) { + // The default behavior of is replying with "Refused". + mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, + message.getSource(), message.getOpcode(), Constants.ABORT_REFUSED)); + return true; + } + @ServiceThreadOnly final void handleAddressAllocated(int logicalAddress, boolean fromBootup) { assertRunOnServiceThread(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index e985e354f5663..bbecafa8f7797 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -19,6 +19,7 @@ package com.android.server.hdmi; import android.content.Intent; import android.hardware.hdmi.HdmiCecDeviceInfo; import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiRecordSources; import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioManager; import android.media.AudioSystem; @@ -35,6 +36,7 @@ import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -808,6 +810,26 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return true; } + // Seq #53 + @Override + @ServiceThreadOnly + protected boolean handleRecordTvScreen(HdmiCecMessage message) { + List actions = getActions(OneTouchRecordAction.class); + if (!actions.isEmpty()) { + // Assumes only one OneTouchRecordAction. + OneTouchRecordAction action = actions.get(0); + if (action.getRecorderAddress() != message.getSource()) { + displayOsd(HdmiControlManager.MESSAGE_NO_RECORDING_PREVIOUS_RECORDING_IN_PROGRESS); + } + return super.handleRecordTvScreen(message); + } + + int recorderAddress = message.getSource(); + byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); + startOneTouchRecord(recorderAddress, recordSource); + return true; + } + private boolean isMessageForSystemAudio(HdmiCecMessage message) { if (message.getSource() != Constants.ADDR_AUDIO_SYSTEM || message.getDestination() != Constants.ADDR_TV @@ -1129,6 +1151,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). removeAction(DeviceDiscoveryAction.class); removeAction(HotplugDetectionAction.class); + // Remove one touch record action. + removeAction(OneTouchRecordAction.class); disableSystemAudioIfExist(); disableArcIfExist(); @@ -1210,4 +1234,72 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL, HdmiControlService.PERMISSION); } + + // Seq #54 and #55 + @ServiceThreadOnly + void startOneTouchRecord(int recorderAddress, byte[] recordSource) { + assertRunOnServiceThread(); + if (!mService.isControlEnabled()) { + Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); + return; + } + + if (!checkRecorder(recorderAddress)) { + Slog.w(TAG, "Invalid recorder address:" + recorderAddress); + return; + } + + if (!checkRecordSource(recordSource)) { + Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); + return; + } + + addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); + Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" + + Arrays.toString(recordSource)); + } + + @ServiceThreadOnly + void stopOneTouchRecord(int recorderAddress) { + assertRunOnServiceThread(); + if (!mService.isControlEnabled()) { + Slog.w(TAG, "Can not stop one touch record. CEC control is disabled."); + return; + } + + if (!checkRecorder(recorderAddress)) { + Slog.w(TAG, "Invalid recorder address:" + recorderAddress); + return; + } + + // Remove one touch record action so that other one touch record can be started. + removeAction(OneTouchRecordAction.class); + mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress)); + Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress); + } + + private boolean checkRecorder(int recorderAddress) { + HdmiCecDeviceInfo device = getDeviceInfo(recorderAddress); + return (device != null) + && (HdmiUtils.getTypeFromAddress(recorderAddress) + == HdmiCecDeviceInfo.DEVICE_RECORDER); + } + + private boolean checkRecordSource(byte[] recordSource) { + return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource); + } + + @ServiceThreadOnly + void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { + assertRunOnServiceThread(); + + // TODO: implement this. + } + + @ServiceThreadOnly + void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { + assertRunOnServiceThread(); + + // TODO: implement this. + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 0b6c3c5f105bc..79f1964812669 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -455,6 +455,29 @@ public class HdmiCecMessageBuilder { return buildCommand(src, dest, Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, params); } + /** + * Build <Record On> command. + * + * @param src source address of command + * @param dest destination address of command + * @param params parameter of command + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildRecordOn(int src, int dest, byte[] params) { + return buildCommand(src, dest, Constants.MESSAGE_RECORD_ON, params); + } + + /** + * Build <Record Off> command. + * + * @param src source address of command + * @param dest destination address of command + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildRecordOff(int src, int dest) { + return buildCommand(src, dest, Constants.MESSAGE_RECORD_OFF); + } + /***** Please ADD new buildXXX() methods above. ******/ /** diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 95cd7ef6038e3..76722325a1822 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -55,6 +55,8 @@ import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback; +import libcore.util.EmptyArray; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -157,6 +159,12 @@ public final class HdmiControlService extends SystemService { @GuardedBy("mLock") private InputChangeListenerRecord mInputChangeListenerRecord; + @GuardedBy("mLock") + private IHdmiRecordRequestListener mRecordRequestListener; + + @GuardedBy("mLock") + private HdmiRecordRequestListenerRecord mRecordRequestListenerRecord; + // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol // handling will be disabled and no request will be handled. @GuardedBy("mLock") @@ -684,6 +692,15 @@ public final class HdmiControlService extends SystemService { } } + private class HdmiRecordRequestListenerRecord implements IBinder.DeathRecipient { + @Override + public void binderDied() { + synchronized (mLock) { + mRecordRequestListener = null; + } + } + } + private void enforceAccessPermission() { getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); } @@ -1014,17 +1031,65 @@ public final class HdmiControlService extends SystemService { @Override public void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) { - // TODO: implement this. + HdmiControlService.this.setOneTouchRecordRequestListener(listener); } @Override - public void startOneTouchRecord(int recorderAddress, byte[] recordSource) { - // TODO: implement this. + public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { + runOnServiceThread(new Runnable() { + @Override + public void run() { + if (!isTvDevice()) { + Slog.w(TAG, "No TV is available."); + return; + } + tv().startOneTouchRecord(recorderAddress, recordSource); + } + }); } @Override - public void startTimerRecording(int recorderAddress, byte[] recordSource) { - // TODO: implement this. + public void stopOneTouchRecord(final int recorderAddress) { + runOnServiceThread(new Runnable() { + @Override + public void run() { + if (!isTvDevice()) { + Slog.w(TAG, "No TV is available."); + return; + } + tv().stopOneTouchRecord(recorderAddress); + } + }); + } + + @Override + public void startTimerRecording(final int recorderAddress, final int sourceType, + final byte[] recordSource) { + runOnServiceThread(new Runnable() { + @Override + public void run() { + if (!isTvDevice()) { + Slog.w(TAG, "No TV is available."); + return; + } + tv().startTimerRecording(recorderAddress, sourceType, recordSource); + } + }); + } + + @Override + public void clearTimerRecording(final int recorderAddress, final int sourceType, + final byte[] recordSource) { + runOnServiceThread(new Runnable() { + @Override + public void run() { + if (!isTvDevice()) { + Slog.w(TAG, "No TV is available."); + return; + } + tv().clearTimerRecording(recorderAddress, sourceType, recordSource); + } + }); } } @@ -1169,6 +1234,32 @@ public final class HdmiControlService extends SystemService { } } + private void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) { + synchronized (mLock) { + mRecordRequestListenerRecord = new HdmiRecordRequestListenerRecord(); + try { + listener.asBinder().linkToDeath(mRecordRequestListenerRecord, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Listener already died", e); + return; + } + mRecordRequestListener = listener; + } + } + + byte[] invokeRecordRequestListener(int recorderAddress) { + synchronized (mLock) { + try { + if (mRecordRequestListener != null) { + return mRecordRequestListener.onRecordRequestReceived(recorderAddress); + } + } catch (RemoteException e) { + Slog.w(TAG, "Failed to start record.", e); + } + return EmptyArray.BYTE; + } + } + private void invokeCallback(IHdmiControlCallback callback, int result) { try { callback.onComplete(result); diff --git a/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java b/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java index 51aa984369d57..9ecbc5ebb338a 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java @@ -16,51 +16,118 @@ package com.android.server.hdmi; -import static com.android.server.hdmi.Constants.RECORDING_TYPE_ANALOGUE_RF; -import static com.android.server.hdmi.Constants.RECORDING_TYPE_DIGITAL_RF; -import static com.android.server.hdmi.Constants.RECORDING_TYPE_EXTERNAL_PHYSICAL_ADDRESS; -import static com.android.server.hdmi.Constants.RECORDING_TYPE_OWN_SOURCE; +import static android.hardware.hdmi.HdmiControlManager.MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION; +import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_ANALOGUE_SERVICE; +import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE; +import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_DIGITAL_SERVICE; +import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_EXTERNAL_INPUT; +import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_STATUS_MESSAGE_START; + +import android.util.Slog; + +import com.android.server.hdmi.HdmiControlService.SendMessageCallback; /** * Feature action that performs one touch record. This class only provides a skeleton of one touch * play and has no detail implementation. */ public class OneTouchRecordAction extends FeatureAction { - private final int mRecorderAddress; - private final int mRecordingType; + private static final String TAG = "OneTouchRecordAction"; - OneTouchRecordAction(HdmiCecLocalDevice source, int recorderAddress, int recordingType) { + // Timer out for waiting + private static final int RECORD_STATUS_TIMEOUT = 120000; + + // State that waits for once sending + private static final int STATE_WAITING_FOR_RECORD_STATUS = 1; + // State that describes recording in progress. + private static final int STATE_RECORDING_IN_PROGRESS = 2; + + private final int mRecorderAddress; + private final byte[] mRecordSource; + + OneTouchRecordAction(HdmiCecLocalDevice source, int recorderAddress, byte[] recordSource) { super(source); mRecorderAddress = recorderAddress; - mRecordingType = recordingType; + mRecordSource = recordSource; } @Override boolean start() { - return false; + sendRecordOn(); + return true; } - private void sendRecordOn(int recordingType) { - switch (recordingType) { - case RECORDING_TYPE_DIGITAL_RF: - break; - case RECORDING_TYPE_ANALOGUE_RF: - break; - case RECORDING_TYPE_EXTERNAL_PHYSICAL_ADDRESS: - break; - case RECORDING_TYPE_OWN_SOURCE: - break; - // TODO: implement this. - } + private void sendRecordOn() { + sendCommand(HdmiCecMessageBuilder.buildRecordOn(getSourceAddress(), mRecorderAddress, + mRecordSource), + new SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + // if failed to send , display error message and finish action. + if (error != Constants.SEND_RESULT_SUCCESS) { + tv().displayOsd(MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION); + finish(); + return; + } + + mState = STATE_WAITING_FOR_RECORD_STATUS; + addTimer(mState, RECORD_STATUS_TIMEOUT); + } + }); } @Override boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WAITING_FOR_RECORD_STATUS) { + return false; + } + + switch (cmd.getOpcode()) { + case Constants.MESSAGE_RECORD_STATUS: + return handleRecordStatus(cmd); + + } return false; } + private boolean handleRecordStatus(HdmiCecMessage cmd) { + // Only handle message coming from original recorder. + if (cmd.getSource() != mRecorderAddress) { + return false; + } + + int recordStatus = cmd.getParams()[0]; + Slog.i(TAG, "Got record status:" + recordStatus + " from " + cmd.getSource()); + + int recordStatusMessageCode = recordStatus + MESSAGE_RECORDING_STATUS_MESSAGE_START; + tv().displayOsd(recordStatusMessageCode); + + // If recording started successfully, change state and keep this action until + // received. Otherwise, finish action. + switch (recordStatusMessageCode) { + case MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE: + case MESSAGE_RECORDING_DIGITAL_SERVICE: + case MESSAGE_RECORDING_ANALOGUE_SERVICE: + case MESSAGE_RECORDING_EXTERNAL_INPUT: + mState = STATE_RECORDING_IN_PROGRESS; + mActionTimer.clearTimerMessage(); + break; + default: + finish(); + break; + } + return true; + } + @Override void handleTimerEvent(int state) { + if (mState != state) { + Slog.w(TAG, "Timeout in invalid state:[Expected:" + mState + ", Actual:" + state + "]"); + return; + } + + tv().displayOsd(MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION); + finish(); } int getRecorderAddress() {