diff --git a/Android.mk b/Android.mk index 278e67f9649ad..0d71150e8bfe0 100644 --- a/Android.mk +++ b/Android.mk @@ -155,7 +155,7 @@ LOCAL_SRC_FILES += \ core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl \ core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl \ core/java/android/hardware/hdmi/IHdmiInputChangeListener.aidl \ - core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl \ + core/java/android/hardware/hdmi/IHdmiRecordListener.aidl \ core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl \ core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl \ core/java/android/hardware/input/IInputManager.aidl \ diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 521a439c2c57d..55db1df5438d7 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -69,69 +69,71 @@ 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; + // --- One Touch Recording success result /** Recording currently selected source. Indicates the status of a recording. */ - public static final int MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x101; + public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x01; /** Recording Digital Service. Indicates the status of a recording. */ - public static final int MESSAGE_RECORDING_DIGITAL_SERVICE = 0x102; + public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 0x02; /** Recording Analogue Service. Indicates the status of a recording. */ - public static final int MESSAGE_RECORDING_ANALOGUE_SERVICE = 0x103; + public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 0x03; /** Recording External input. Indicates the status of a recording. */ - public static final int MESSAGE_RECORDING_EXTERNAL_INPUT = 0x104; + public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 0x04; + + // --- One Touch Record failure result /** No recording – unable to record Digital Service. No suitable tuner. */ - public static final int MESSAGE_NO_RECORDNIG_UNABLE_DIGITAL_SERVICE = 0x105; + public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 0x05; /** No recording – unable to record Analogue Service. No suitable tuner. */ - public static final int MESSAGE_NO_RECORDNIG_UNABLE_ANALOGUE_SERVICE = 0x106; + public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 0x06; /** * 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; + public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 0x07; /** No recording – invalid External plug number */ - public static final int MESSAGE_NO_RECORDNIG_INVALID_EXTERNAL_PLUG_NUMBER = 0x109; + public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 0x09; /** No recording – invalid External Physical Address */ - public static final int MESSAGE_NO_RECORDNIG_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x10A; + public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x0A; /** No recording – CA system not supported */ - public static final int MESSAGE_NO_RECORDNIG_UNSUPPORTED_CA = 0x10B; + public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 0x0B; /** No Recording – No or Insufficient CA Entitlements” */ - public static final int MESSAGE_NO_RECORDNIG_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x10C; + public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x0C; /** No recording – Not allowed to copy source. Source is “copy never”. */ - public static final int MESSAGE_NO_RECORDNIG_DISALLOW_TO_COPY = 0x10D; + public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 0x0D; /** No recording – No further copies allowed */ - public static final int MESSAGE_NO_RECORDNIG_DISALLOW_TO_FUTHER_COPIES = 0x10E; + public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 0x0E; /** No recording – No media */ - public static final int MESSAGE_NO_RECORDNIG_NO_MEDIA = 0x110; + public static final int ONE_TOUCH_RECORD_NO_MEDIA = 0x10; /** No recording – playing */ - public static final int MESSAGE_NO_RECORDNIG_PLAYING = 0x111; + public static final int ONE_TOUCH_RECORD_PLAYING = 0x11; /** No recording – already recording */ - public static final int MESSAGE_NO_RECORDNIG_ALREADY_RECORDING = 0x112; + public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 0x12; /** No recording – media protected */ - public static final int MESSAGE_NO_RECORDNIG_MEDIA_PROTECTED = 0x113; + public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 0x13; /** No recording – no source signal */ - public static final int MESSAGE_NO_RECORDNIG_NO_SOURCE_SIGNAL = 0x114; + public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 0x14; /** No recording – media problem */ - public static final int MESSAGE_NO_RECORDNIG_MEDIA_PROBLEM = 0x115; + public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 0x15; /** No recording – not enough space available */ - public static final int MESSAGE_NO_RECORDNIG_NOT_ENOUGH_SPACE = 0x116; + public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 0x16; /** No recording – Parental Lock On */ - public static final int MESSAGE_NO_RECORDNIG_PARENT_LOCK_ON = 0x117; + public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 0x17; /** Recording terminated normally */ - public static final int MESSAGE_RECORDING_TERMINATED_NORMALLY = 0x11A; + public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 0x1A; /** Recording has already terminated */ - public static final int MESSAGE_RECORDING_ALREADY_TERMINATED = 0x11B; + public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 0x1B; /** No recording – other reason */ - public static final int MESSAGE_NO_RECORDNIG_OTHER_REASON = 0x11F; + public static final int ONE_TOUCH_RECORD_OTHER_REASON = 0x1F; // 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; + public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 0x30; /** No recording. Please check recorder and connection. */ - public static final int MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION = 0x131; + public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 0x31; /** Cannot record currently displayed source. */ - public static final int MESSAGE_NO_RECORDING_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x132; + public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x32; + /** CEC is disabled. */ + public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 0x33; + // --- Types for timer recording /** Timer recording type for digital service source. */ public static final int TIMER_RECORDING_TYPE_DIGITAL = 1; /** Timer recording type for analogue service source. */ @@ -139,6 +141,14 @@ public final class HdmiControlManager { /** Timer recording type for external source. */ public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3; + // --- Extra result value for timer recording. + /** No timer recording - check recorder and connection. */ + public static final int TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 0x01; + /** No timer recording - cannot record selected source. */ + public static final int TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 0x02; + /** CEC is disabled. */ + public static final int TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED = 0x33; + // 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/HdmiRecordListener.java b/core/java/android/hardware/hdmi/HdmiRecordListener.java new file mode 100644 index 0000000000000..0b1166b7c0d7c --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiRecordListener.java @@ -0,0 +1,67 @@ +/* + * 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 android.hardware.hdmi; + +import android.annotation.SystemApi; +import android.hardware.hdmi.HdmiRecordSources.RecordSource; + +/** + * Listener for hdmi record feature including one touch record and timer recording. + * @hide + */ +@SystemApi +public abstract class HdmiRecordListener { + protected HdmiRecordListener() {} + + /** + * Called when TV received one touch record request from record device. The client of this + * should use {@link HdmiRecordSources} to return it. + * + * @param recorderAddress + * @return record source to be used for recording. Null if no device is available. + */ + public abstract RecordSource getOneTouchRecordSource(int recorderAddress); + + /** + * Called when one touch record is started or failed during initialization. + * + * @param result result code. For more details, please look at all constants starting with + * "ONE_TOUCH_RECORD_". Only + * {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE}, + * {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE}, + * {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE}, and + * {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT} mean normal + * start of recording; otherwise, describes failure. + */ + public void onOneTouchRecordResult(int result) { + } + + /** + * Called when timer recording is started or failed during initialization. + * + * @param result The most significant three bytes may contain result of <Timer Status> + * while the least significant byte may have error message like + * {@link HdmiControlManager#TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION} + * or + * {@link HdmiControlManager #TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE} + * . If the least significant byte has non zero value the most significant three bytes + * may have 0 value. + */ + // TODO: implement result parser. + public void onTimerRecordingResult(int result) { + } +} diff --git a/core/java/android/hardware/hdmi/HdmiRecordSources.java b/core/java/android/hardware/hdmi/HdmiRecordSources.java index 296cae6253d91..917d1d9dd08f0 100644 --- a/core/java/android/hardware/hdmi/HdmiRecordSources.java +++ b/core/java/android/hardware/hdmi/HdmiRecordSources.java @@ -492,7 +492,7 @@ public final class HdmiRecordSources { /** Indicates that a service is identified by a logical or virtual channel number. */ private static final int DIGITAL_SERVICE_IDENTIFIED_BY_CHANNEL = 1; - private static final int EXTRA_DATA_SIZE = 7; + static final int EXTRA_DATA_SIZE = 7; /** * Type of identification. It should be one of DIGITAL_SERVICE_IDENTIFIED_BY_DIGITAL_ID and @@ -609,7 +609,7 @@ public final class HdmiRecordSources { */ @SystemApi public static final class AnalogueServiceSource extends RecordSource { - private static final int EXTRA_DATA_SIZE = 4; + static final int EXTRA_DATA_SIZE = 4; /** Indicates the Analogue broadcast type. */ private final int mBroadcastType; @@ -668,7 +668,7 @@ public final class HdmiRecordSources { */ @SystemApi public static final class ExternalPlugData extends RecordSource { - private static final int EXTRA_DATA_SIZE = 1; + static final int EXTRA_DATA_SIZE = 1; /** External Plug number on the Recording Device. */ private final int mPlugNumber; @@ -713,7 +713,7 @@ public final class HdmiRecordSources { */ @SystemApi public static final class ExternalPhysicalAddress extends RecordSource { - private static final int EXTRA_DATA_SIZE = 2; + static final int EXTRA_DATA_SIZE = 2; private final int mPhysicalAddress; @@ -751,6 +751,7 @@ public final class HdmiRecordSources { * Check the byte array of record source. * @hide */ + @SystemApi public static boolean checkRecordSource(byte[] recordSource) { int recordSourceType = recordSource[0]; int extraDataSize = recordSource.length - 1; diff --git a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java index 3e5e49bbd97fb..01b4dd3b269e8 100644 --- a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java +++ b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java @@ -16,6 +16,10 @@ package android.hardware.hdmi; +import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE; +import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; +import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; + import android.annotation.SystemApi; import android.hardware.hdmi.HdmiRecordSources.AnalogueServiceSource; import android.hardware.hdmi.HdmiRecordSources.DigitalServiceSource; @@ -420,4 +424,35 @@ public class HdmiTimerRecordSources { return getDataSize(false); } } + + /** + * Check the byte array of timer record source. + * @param sourcetype + * @param recordSource + * @hide + */ + @SystemApi + public static boolean checkTimerRecordSource(int sourcetype, byte[] recordSource) { + int recordSourceSize = recordSource.length - TimerInfo.BASIC_INFO_SIZE; + switch (sourcetype) { + case TIMER_RECORDING_TYPE_DIGITAL: + return DigitalServiceSource.EXTRA_DATA_SIZE == recordSourceSize; + case TIMER_RECORDING_TYPE_ANALOGUE: + return AnalogueServiceSource.EXTRA_DATA_SIZE == recordSourceSize; + case TIMER_RECORDING_TYPE_EXTERNAL: + int specifier = recordSource[TimerInfo.BASIC_INFO_SIZE]; + if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PLUG) { + // One byte for specifier. + return ExternalPlugData.EXTRA_DATA_SIZE + 1 == recordSourceSize; + } else if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PHYSICAL_ADDRESS) { + // One byte for specifier. + return ExternalPhysicalAddress.EXTRA_DATA_SIZE + 1 == recordSourceSize; + } else { + // Invalid specifier. + return false; + } + default: + return false; + } + } } diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java index 6080914e59363..34362871e230c 100644 --- a/core/java/android/hardware/hdmi/HdmiTvClient.java +++ b/core/java/android/hardware/hdmi/HdmiTvClient.java @@ -15,10 +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.hardware.hdmi.HdmiRecordSources.RecordSource; +import android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource; import android.os.RemoteException; import android.util.Log; @@ -155,27 +154,15 @@ public final class HdmiTvClient extends HdmiClient { } /** - * Callback interface to used to get notified when a record request from recorder device. + * Set record listener + * + * @param listener */ - public interface RecordRequestListener { - /** - * Called when tv receives request request from recorder device. When it's called, - * it should return record source in byte array so that hdmi control service - * can start recording with the given source info. - * - * @return {@link HdmiRecordSources} to be used to set recording info - */ - RecordSource onRecordRequestReceived(int recorderAddress); - } - - /** - * Set {@link RecordRequestListener} to hdmi control service. - */ - public void setOneTouchRecordRequestListener(RecordRequestListener listener) { + public void setRecordListener(HdmiRecordListener listener) { try { - mService.setOneTouchRecordRequestListener(getCallbackWrapper(listener)); + mService.setHdmiRecordListener(getListenerWrapper(listener)); } catch (RemoteException e) { - Log.e(TAG, "failed to set record request listener: ", e); + Log.e(TAG, "failed to set record listener.", e); } } @@ -282,13 +269,12 @@ public final class HdmiTvClient extends HdmiClient { }; } - private static IHdmiRecordRequestListener getCallbackWrapper( - final RecordRequestListener listener) { - return new IHdmiRecordRequestListener.Stub() { + private static IHdmiRecordListener getListenerWrapper(final HdmiRecordListener callback) { + return new IHdmiRecordListener.Stub() { @Override - public byte[] onRecordRequestReceived(int recorderAddress) throws RemoteException { + public byte[] getOneTouchRecordSource(int recorderAddress) { HdmiRecordSources.RecordSource source = - listener.onRecordRequestReceived(recorderAddress); + callback.getOneTouchRecordSource(recorderAddress); if (source == null) { return EmptyArray.BYTE; } @@ -296,6 +282,16 @@ public final class HdmiTvClient extends HdmiClient { source.toByteArray(true, data, 0); return data; } + + @Override + public void onOneTouchRecordResult(int result) { + callback.onOneTouchRecordResult(result); + } + + @Override + public void onTimerRecordingResult(int result) { + callback.onTimerRecordingResult(result); + } }; } } diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index 95e0ee0a1307f..808e0c9d572ee 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -22,7 +22,7 @@ import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.hdmi.IHdmiDeviceEventListener; import android.hardware.hdmi.IHdmiHotplugEventListener; import android.hardware.hdmi.IHdmiInputChangeListener; -import android.hardware.hdmi.IHdmiRecordRequestListener; +import android.hardware.hdmi.IHdmiRecordListener; import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; import android.hardware.hdmi.IHdmiVendorCommandListener; @@ -62,7 +62,7 @@ interface IHdmiControlService { void sendVendorCommand(int deviceType, int targetAddress, in byte[] params, boolean hasVendorId); void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType); - void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener); + void setHdmiRecordListener(IHdmiRecordListener callback); void startOneTouchRecord(int recorderAddress, in byte[] recordSource); void stopOneTouchRecord(int recorderAddress); void startTimerRecording(int recorderAddress, int sourceType, in byte[] recordSource); diff --git a/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl b/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl new file mode 100644 index 0000000000000..fba4b054996d8 --- /dev/null +++ b/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl @@ -0,0 +1,42 @@ +/* + * 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 android.hardware.hdmi; + +/** + * @hide + */ + interface IHdmiRecordListener { + /** + * Called when TV received one touch record request from record device. + * + * @param recorderAddress + * @return record source in byte array. + */ + byte[] getOneTouchRecordSource(int recorderAddress); + + /** + * Called when one touch record is started or failed during initialization. + * + * @param result result code for one touch record + */ + void onOneTouchRecordResult(int result); + /** + * Called when timer recording is started or failed during initialization. + * @param result result code for timer recording + */ + void onTimerRecordingResult(int result); + } \ No newline at end of file diff --git a/core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl b/core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl deleted file mode 100644 index f8f9e5f698a1f..0000000000000 --- a/core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 android.hardware.hdmi; - -/** - * Callback interface definition for HDMI client to fill record source info - * when it gets record start request. - * - * @hide - */ -interface IHdmiRecordRequestListener { - byte[] onRecordRequestReceived(int recorderAddress); -} \ No newline at end of file diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index bbecafa8f7797..5da8aef3f0926 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -16,10 +16,18 @@ package com.android.server.hdmi; +import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED; +import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION; +import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN; +import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED; +import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION; +import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE; + import android.content.Intent; import android.hardware.hdmi.HdmiCecDeviceInfo; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiRecordSources; +import android.hardware.hdmi.HdmiTimerRecordSources; import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioManager; import android.media.AudioSystem; @@ -47,7 +55,7 @@ import java.util.Locale; final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { private static final String TAG = "HdmiCecLocalDeviceTv"; - // Whether ARC is available or not. "true" means that ARC is estabilished between TV and + // Whether ARC is available or not. "true" means that ARC is established between TV and // AVR as audio receiver. @ServiceThreadOnly private boolean mArcEstablished = false; @@ -819,7 +827,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Assumes only one OneTouchRecordAction. OneTouchRecordAction action = actions.get(0); if (action.getRecorderAddress() != message.getSource()) { - displayOsd(HdmiControlManager.MESSAGE_NO_RECORDING_PREVIOUS_RECORDING_IN_PROGRESS); + announceOneTouchRecordResult( + HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS); } return super.handleRecordTvScreen(message); } @@ -830,6 +839,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return true; } + void announceOneTouchRecordResult(int result) { + mService.invokeOneTouchRecordResult(result); + } + + void announceTimerRecordingResult(int result) { + mService.invokeTimerRecordingResult(result); + } + private boolean isMessageForSystemAudio(HdmiCecMessage message) { if (message.getSource() != Constants.ADDR_AUDIO_SYSTEM || message.getDestination() != Constants.ADDR_TV @@ -1241,16 +1258,19 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); if (!mService.isControlEnabled()) { Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); + announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED); return; } if (!checkRecorder(recorderAddress)) { Slog.w(TAG, "Invalid recorder address:" + recorderAddress); + announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); return; } if (!checkRecordSource(recordSource)) { Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); + announceOneTouchRecordResult(ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); return; } @@ -1264,11 +1284,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); if (!mService.isControlEnabled()) { Slog.w(TAG, "Can not stop one touch record. CEC control is disabled."); + announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED); return; } if (!checkRecorder(recorderAddress)) { Slog.w(TAG, "Invalid recorder address:" + recorderAddress); + announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); return; } @@ -1292,8 +1314,35 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { assertRunOnServiceThread(); + if (!mService.isControlEnabled()) { + Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); + announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED); + return; + } - // TODO: implement this. + if (!checkRecorder(recorderAddress)) { + Slog.w(TAG, "Invalid recorder address:" + recorderAddress); + announceTimerRecordingResult( + TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); + return; + } + + if (!checkTimerRecordingSource(sourceType, recordSource)) { + Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); + announceTimerRecordingResult( + TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); + return; + } + + addAndStartAction( + new TimerRecordingAction(this, recorderAddress, sourceType, recordSource)); + Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:" + + sourceType + ", RecordSource:" + Arrays.toString(recordSource)); + } + + private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) { + return (recordSource != null) + && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource); } @ServiceThreadOnly diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 79f1964812669..0855bfa1a7043 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -478,6 +478,78 @@ public class HdmiCecMessageBuilder { return buildCommand(src, dest, Constants.MESSAGE_RECORD_OFF); } + /** + * Build <Set Digital Timer> command. + * + * @param src source address of command + * @param dest destination address of command + * @param params byte array of timing information and digital service information to be recorded + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildSetDigitalTimer(int src, int dest, byte[] params) { + return buildCommand(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params); + } + + /** + * Build <Set Analogue Timer> command. + * + * @param src source address of command + * @param dest destination address of command + * @param params byte array of timing information and analog service information to be recorded + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildSetAnalogueTimer(int src, int dest, byte[] params) { + return buildCommand(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params); + } + + /** + * Build <Set External Timer> command. + * + * @param src source address of command + * @param dest destination address of command + * @param params byte array of timing information and external source information to be recorded + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildSetExternalTimer(int src, int dest, byte[] params) { + return buildCommand(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params); + } + + /** + * Build <Clear Digital Timer> command. + * + * @param src source address of command + * @param dest destination address of command + * @param params byte array of timing information and digital service information to be cleared + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildClearDigitalTimer(int src, int dest, byte[] params) { + return buildCommand(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params); + } + + /** + * Build <Clear Analog Timer> command. + * + * @param src source address of command + * @param dest destination address of command + * @param params byte array of timing information and analog service information to be cleared + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildClearAnalogueTimer(int src, int dest, byte[] params) { + return buildCommand(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params); + } + + /** + * Build <Clear Digital Timer> command. + * + * @param src source address of command + * @param dest destination address of command + * @param params byte array of timing information and external source information to be cleared + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildClearExternalTimer(int src, int dest, byte[] params) { + return buildCommand(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params); + } + /***** 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 76722325a1822..60421e348dbaf 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -32,7 +32,7 @@ import android.hardware.hdmi.IHdmiControlService; import android.hardware.hdmi.IHdmiDeviceEventListener; import android.hardware.hdmi.IHdmiHotplugEventListener; import android.hardware.hdmi.IHdmiInputChangeListener; -import android.hardware.hdmi.IHdmiRecordRequestListener; +import android.hardware.hdmi.IHdmiRecordListener; import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; import android.hardware.hdmi.IHdmiVendorCommandListener; import android.media.AudioManager; @@ -160,10 +160,10 @@ public final class HdmiControlService extends SystemService { private InputChangeListenerRecord mInputChangeListenerRecord; @GuardedBy("mLock") - private IHdmiRecordRequestListener mRecordRequestListener; + private IHdmiRecordListener mRecordListener; @GuardedBy("mLock") - private HdmiRecordRequestListenerRecord mRecordRequestListenerRecord; + private HdmiRecordListenerRecord mRecordListenerRecord; // 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. @@ -692,11 +692,11 @@ public final class HdmiControlService extends SystemService { } } - private class HdmiRecordRequestListenerRecord implements IBinder.DeathRecipient { + private class HdmiRecordListenerRecord implements IBinder.DeathRecipient { @Override public void binderDied() { synchronized (mLock) { - mRecordRequestListener = null; + mRecordListener = null; } } } @@ -1027,11 +1027,11 @@ public final class HdmiControlService extends SystemService { } } }); - } + } @Override - public void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) { - HdmiControlService.this.setOneTouchRecordRequestListener(listener); + public void setHdmiRecordListener(IHdmiRecordListener listener) { + HdmiControlService.this.setHdmiRecordListener(listener); } @Override @@ -1234,32 +1234,55 @@ public final class HdmiControlService extends SystemService { } } - private void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) { + private void setHdmiRecordListener(IHdmiRecordListener listener) { synchronized (mLock) { - mRecordRequestListenerRecord = new HdmiRecordRequestListenerRecord(); + mRecordListenerRecord = new HdmiRecordListenerRecord(); try { - listener.asBinder().linkToDeath(mRecordRequestListenerRecord, 0); + listener.asBinder().linkToDeath(mRecordListenerRecord, 0); } catch (RemoteException e) { - Slog.w(TAG, "Listener already died", e); - return; + Slog.w(TAG, "Listener already died.", e); } - mRecordRequestListener = listener; + mRecordListener = listener; } } byte[] invokeRecordRequestListener(int recorderAddress) { synchronized (mLock) { - try { - if (mRecordRequestListener != null) { - return mRecordRequestListener.onRecordRequestReceived(recorderAddress); + if (mRecordListener != null) { + try { + return mRecordListener.getOneTouchRecordSource(recorderAddress); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to start record.", e); } - } catch (RemoteException e) { - Slog.w(TAG, "Failed to start record.", e); } return EmptyArray.BYTE; } } + void invokeOneTouchRecordResult(int result) { + synchronized (mLock) { + if (mRecordListener != null) { + try { + mRecordListener.onOneTouchRecordResult(result); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); + } + } + } + } + + void invokeTimerRecordingResult(int result) { + synchronized (mLock) { + if (mRecordListener != null) { + try { + mRecordListener.onTimerRecordingResult(result); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); + } + } + } + } + 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 9ecbc5ebb338a..befc640835a5f 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java @@ -16,26 +16,24 @@ package com.android.server.hdmi; -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 static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION; +import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE; +import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE; +import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE; +import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT; 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. + * Feature action that performs one touch record. */ public class OneTouchRecordAction extends FeatureAction { private static final String TAG = "OneTouchRecordAction"; - // Timer out for waiting - private static final int RECORD_STATUS_TIMEOUT = 120000; + // Timer out for waiting 120s + private static final int RECORD_STATUS_TIMEOUT_MS = 120000; // State that waits for once sending private static final int STATE_WAITING_FOR_RECORD_STATUS = 1; @@ -65,13 +63,14 @@ public class OneTouchRecordAction extends FeatureAction { 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); + tv().announceOneTouchRecordResult( + ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); finish(); return; } mState = STATE_WAITING_FOR_RECORD_STATUS; - addTimer(mState, RECORD_STATUS_TIMEOUT); + addTimer(mState, RECORD_STATUS_TIMEOUT_MS); } }); } @@ -97,18 +96,16 @@ public class OneTouchRecordAction extends FeatureAction { } int recordStatus = cmd.getParams()[0]; + tv().announceOneTouchRecordResult(recordStatus); 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: + switch (recordStatus) { + case ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE: + case ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE: + case ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE: + case ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT: mState = STATE_RECORDING_IN_PROGRESS; mActionTimer.clearTimerMessage(); break; @@ -126,7 +123,7 @@ public class OneTouchRecordAction extends FeatureAction { return; } - tv().displayOsd(MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION); + tv().announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); finish(); } diff --git a/services/core/java/com/android/server/hdmi/TimerRecordingAction.java b/services/core/java/com/android/server/hdmi/TimerRecordingAction.java new file mode 100644 index 0000000000000..1dc26f175ee67 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/TimerRecordingAction.java @@ -0,0 +1,169 @@ +/* + * 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 android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE; +import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; +import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; +import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION; + +import android.util.Slog; + +import com.android.server.hdmi.HdmiControlService.SendMessageCallback; + +import java.util.Arrays; + +/** + * Feature action that performs timer recording. + */ +public class TimerRecordingAction extends FeatureAction { + private static final String TAG = "TimerRecordingAction"; + + // Timer out for waiting 120s. + private static final int TIMER_STATUS_TIMEOUT_MS = 120000; + + // State that waits for once sending + private static final int STATE_WAITING_FOR_TIMER_STATUS = 1; + + private final int mRecorderAddress; + private final int mSourceType; + private final byte[] mRecordSource; + + TimerRecordingAction(HdmiCecLocalDevice source, int recorderAddress, int sourceType, + byte[] recordSource) { + super(source); + mRecorderAddress = recorderAddress; + mSourceType = sourceType; + mRecordSource = recordSource; + } + + @Override + boolean start() { + sendTimerMessage(); + return true; + } + + private void sendTimerMessage() { + HdmiCecMessage message = null; + switch (mSourceType) { + case TIMER_RECORDING_TYPE_DIGITAL: + message = HdmiCecMessageBuilder.buildSetDigitalTimer(getSourceAddress(), + mRecorderAddress, mRecordSource); + break; + case TIMER_RECORDING_TYPE_ANALOGUE: + message = HdmiCecMessageBuilder.buildSetAnalogueTimer(getSourceAddress(), + mRecorderAddress, mRecordSource); + break; + case TIMER_RECORDING_TYPE_EXTERNAL: + message = HdmiCecMessageBuilder.buildSetExternalTimer(getSourceAddress(), + mRecorderAddress, mRecordSource); + break; + default: + tv().announceTimerRecordingResult( + TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); + finish(); + return; + } + sendCommand(message, new SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + if (error != Constants.SEND_RESULT_SUCCESS) { + mState = STATE_WAITING_FOR_TIMER_STATUS; + addTimer(mState, TIMER_STATUS_TIMEOUT_MS); + finish(); + return; + } + } + }); + } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WAITING_FOR_TIMER_STATUS) { + return false; + } + + if (cmd.getSource() != mRecorderAddress) { + return false; + } + + switch (cmd.getOpcode()) { + case Constants.MESSAGE_TIMER_STATUS: + return handleTimerStatus(cmd); + case Constants.MESSAGE_FEATURE_ABORT: + return handleFeatureAbort(cmd); + } + return false; + } + + private boolean handleTimerStatus(HdmiCecMessage cmd) { + byte[] timerStatusData = cmd.getParams(); + // [Timer Status Data] should be one or three bytes. + if (timerStatusData.length == 1 || timerStatusData.length == 3) { + tv().announceTimerRecordingResult(bytesToInt(timerStatusData)); + Slog.i(TAG, "Received [Timer Status Data]:" + Arrays.toString(timerStatusData)); + } else { + Slog.w(TAG, "Invalid [Timer Status Data]:" + Arrays.toString(timerStatusData)); + } + + // Unlike one touch record, finish timer record when is received. + finish(); + return true; + } + + private boolean handleFeatureAbort(HdmiCecMessage cmd) { + byte[] params = cmd.getParams(); + int messageType = params[0]; + switch (messageType) { + case Constants.MESSAGE_SET_DIGITAL_TIMER: // fall through + case Constants.MESSAGE_SET_ANALOG_TIMER: // fall through + case Constants.MESSAGE_SET_EXTERNAL_TIMER: // fall through + break; + default: + return false; + } + int reason = params[1]; + Slog.i(TAG, "[Feature Abort] for " + messageType + " reason:" + reason); + tv().announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); + finish(); + return true; + } + + // Convert byte array to int. + private static int bytesToInt(byte[] data) { + if (data.length > 4) { + throw new IllegalArgumentException("Invalid data size:" + Arrays.toString(data)); + } + int result = 0; + for (int i = 0; i < data.length; ++i) { + int shift = (3 - i) * 8; + result |= ((data[i] & 0xFF) << shift); + } + return result; + } + + @Override + void handleTimerEvent(int state) { + if (mState != state) { + Slog.w(TAG, "Timeout in invalid state:[Expected:" + mState + ", Actual:" + state + "]"); + return; + } + + tv().announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); + finish(); + } +}