Replace osd message for record status with IHdmiRecordCallback.

Add new interface, IHdmiRecordCallback, in order to handle
result of all recording features, One Touch Record and
Timer Recording.
HdmiRecordCallback is clientside interface which uses
RecordSource as return of getOneTouchRecordSource,
instead of raw byte array.

Along with it, add TimerRecordingAction.

Bug: 16160962

Change-Id: If0ed7abc5f45b020b39555cac585becbe8ee7da4
This commit is contained in:
Jungshik Jang
2014-07-24 15:27:44 +09:00
parent 366c693b60
commit 12e5dcefe1
14 changed files with 568 additions and 134 deletions

View File

@@ -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 \

View File

@@ -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.

View File

@@ -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) {
}
}

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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);
}
};
}
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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. ******/
/**

View File

@@ -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);

View File

@@ -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 <Record Status>
private static final int RECORD_STATUS_TIMEOUT = 120000;
// Timer out for waiting <Record Status> 120s
private static final int RECORD_STATUS_TIMEOUT_MS = 120000;
// State that waits for <Record Status> once sending <Record On>
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 <Record On>, 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 <Record Off>
// 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();
}

View File

@@ -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 <Timer Status> 120s.
private static final int TIMER_STATUS_TIMEOUT_MS = 120000;
// State that waits for <Timer Status> once sending <Set XXX Timer>
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 <Timer Status> 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();
}
}