diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 30629add6a39f..056d470034fdb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1095,6 +1095,13 @@
+
+
+
getAvailableTvStreamConfigList(in String inputId, int userId);
+ boolean captureFrame(in String inputId, in Surface surface, in TvStreamConfig config,
+ int userId);
}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 5bed40bbbceda..b427cbd18a7e7 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -682,6 +682,41 @@ public final class TvInputManager {
}
}
+ /**
+ * Returns the TvStreamConfig list of the given TV input.
+ *
+ * @param inputId the id of the TV input.
+ * @return List of {@link TvStreamConfig} which is available for capturing
+ * of the given TV input.
+ * @hide
+ */
+ @SystemApi
+ public List getAvailableTvStreamConfigList(String inputId) {
+ try {
+ return mService.getAvailableTvStreamConfigList(inputId, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Take a snapshot of the given TV input into the provided Surface.
+ *
+ * @param inputId the id of the TV input.
+ * @param surface the {@link Surface} to which the snapshot is captured.
+ * @param config the {@link TvStreamConfig} which is used for capturing.
+ * @return true when the {@link Surface} is ready to be captured.
+ * @hide
+ */
+ @SystemApi
+ public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config) {
+ try {
+ return mService.captureFrame(inputId, surface, config, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* The Session provides the per-session functionality of TV inputs.
* @hide
diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java
index c011fd3013d86..c6213f9df9a62 100644
--- a/services/core/java/com/android/server/tv/TvInputHal.java
+++ b/services/core/java/com/android/server/tv/TvInputHal.java
@@ -40,16 +40,17 @@ final class TvInputHal implements Handler.Callback {
public final static int ERROR_STALE_CONFIG = -2;
public final static int ERROR_UNKNOWN = -3;
- // Below should be in sync with hardware/libhardware/include/hardware/tv_input.h
public static final int EVENT_DEVICE_AVAILABLE = 1;
public static final int EVENT_DEVICE_UNAVAILABLE = 2;
public static final int EVENT_STREAM_CONFIGURATION_CHANGED = 3;
+ public static final int EVENT_FIRST_FRAME_CAPTURED = 4;
public interface Callback {
public void onDeviceAvailable(
TvInputHardwareInfo info, TvStreamConfig[] configs);
public void onDeviceUnavailable(int deviceId);
public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs);
+ public void onFirstFrameCaptured(int deviceId, int streamId);
}
private native long nativeOpen();
@@ -131,6 +132,11 @@ final class TvInputHal implements Handler.Callback {
mHandler.obtainMessage(EVENT_STREAM_CONFIGURATION_CHANGED, deviceId, 0).sendToTarget();
}
+ private void firstFrameCapturedFromNative(int deviceId, int streamId) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(EVENT_STREAM_CONFIGURATION_CHANGED, deviceId, streamId));
+ }
+
// Handler.Callback implementation
private Queue mPendingMessageQueue = new LinkedList();
@@ -167,6 +173,13 @@ final class TvInputHal implements Handler.Callback {
break;
}
+ case EVENT_FIRST_FRAME_CAPTURED: {
+ int deviceId = msg.arg1;
+ int streamId = msg.arg2;
+ mCallback.onFirstFrameCaptured(deviceId, streamId);
+ break;
+ }
+
default:
Slog.e(TAG, "Unknown event: " + msg);
return false;
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index ea1901280f30c..d05515d198253 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -172,6 +172,23 @@ class TvInputHardwareManager implements TvInputHal.Callback {
}
}
+ @Override
+ public void onFirstFrameCaptured(int deviceId, int streamId) {
+ synchronized (mLock) {
+ Connection connection = mConnections.get(deviceId);
+ if (connection == null) {
+ Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
+ + deviceId);
+ return;
+ }
+ Runnable runnable = connection.getOnFirstFrameCapturedLocked();
+ if (runnable != null) {
+ runnable.run();
+ connection.setOnFirstFrameCapturedLocked(null);
+ }
+ }
+ }
+
public List getHardwareList() {
synchronized (mLock) {
return mInfoList;
@@ -337,6 +354,74 @@ class TvInputHardwareManager implements TvInputHal.Callback {
return null;
}
+ private int findDeviceIdForInputIdLocked(String inputId) {
+ for (int i = 0; i < mConnections.size(); ++i) {
+ Connection connection = mConnections.get(i);
+ if (connection.getInfoLocked().getId().equals(inputId)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Get the list of TvStreamConfig which is buffered mode.
+ */
+ public List getAvailableTvStreamConfigList(String inputId, int callingUid,
+ int resolvedUserId) {
+ List configsList = new ArrayList();
+ synchronized (mLock) {
+ int deviceId = findDeviceIdForInputIdLocked(inputId);
+ if (deviceId < 0) {
+ Slog.e(TAG, "Invalid inputId : " + inputId);
+ return configsList;
+ }
+ Connection connection = mConnections.get(deviceId);
+ for (TvStreamConfig config : connection.getConfigsLocked()) {
+ if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
+ configsList.add(config);
+ }
+ }
+ }
+ return configsList;
+ }
+
+ /**
+ * Take a snapshot of the given TV input into the provided Surface.
+ */
+ public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
+ int callingUid, int resolvedUserId) {
+ synchronized (mLock) {
+ int deviceId = findDeviceIdForInputIdLocked(inputId);
+ if (deviceId < 0) {
+ Slog.e(TAG, "Invalid inputId : " + inputId);
+ return false;
+ }
+ Connection connection = mConnections.get(deviceId);
+ final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
+ if (hardwareImpl != null) {
+ // Stop previous capture.
+ Runnable runnable = connection.getOnFirstFrameCapturedLocked();
+ if (runnable != null) {
+ runnable.run();
+ connection.setOnFirstFrameCapturedLocked(null);
+ }
+
+ boolean result = hardwareImpl.startCapture(surface, config);
+ if (result) {
+ connection.setOnFirstFrameCapturedLocked(new Runnable() {
+ @Override
+ public void run() {
+ hardwareImpl.stopCapture(config);
+ }
+ });
+ }
+ return result;
+ }
+ }
+ return false;
+ }
+
private class Connection implements IBinder.DeathRecipient {
private final TvInputHardwareInfo mHardwareInfo;
private TvInputInfo mInfo;
@@ -345,6 +430,7 @@ class TvInputHardwareManager implements TvInputHal.Callback {
private TvStreamConfig[] mConfigs = null;
private Integer mCallingUid = null;
private Integer mResolvedUserId = null;
+ private Runnable mOnFirstFrameCaptured;
public Connection(TvInputHardwareInfo hardwareInfo) {
mHardwareInfo = hardwareInfo;
@@ -367,6 +453,7 @@ class TvInputHardwareManager implements TvInputHal.Callback {
mInfo = info;
mCallingUid = callingUid;
mResolvedUserId = resolvedUserId;
+ mOnFirstFrameCaptured = null;
if (mHardware != null && mCallback != null) {
try {
@@ -393,6 +480,10 @@ class TvInputHardwareManager implements TvInputHal.Callback {
return mHardware;
}
+ public TvInputHardwareImpl getHardwareImplLocked() {
+ return mHardware;
+ }
+
public ITvInputHardwareCallback getCallbackLocked() {
return mCallback;
}
@@ -409,6 +500,14 @@ class TvInputHardwareManager implements TvInputHal.Callback {
return mResolvedUserId;
}
+ public void setOnFirstFrameCapturedLocked(Runnable runnable) {
+ mOnFirstFrameCaptured = runnable;
+ }
+
+ public Runnable getOnFirstFrameCapturedLocked() {
+ return mOnFirstFrameCaptured;
+ }
+
@Override
public void binderDied() {
synchronized (mLock) {
@@ -559,6 +658,37 @@ class TvInputHardwareManager implements TvInputHal.Callback {
// TODO(hdmi): mHdmiClient.sendKeyEvent(event);
return false;
}
+
+ private boolean startCapture(Surface surface, TvStreamConfig config) {
+ synchronized (mImplLock) {
+ if (mReleased) {
+ return false;
+ }
+ if (surface == null || config == null) {
+ return false;
+ }
+ if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
+ return false;
+ }
+
+ int result = mHal.addStream(mInfo.getDeviceId(), surface, config);
+ return result == TvInputHal.SUCCESS;
+ }
+ }
+
+ private boolean stopCapture(TvStreamConfig config) {
+ synchronized (mImplLock) {
+ if (mReleased) {
+ return false;
+ }
+ if (config == null) {
+ return false;
+ }
+
+ int result = mHal.removeStream(mInfo.getDeviceId(), config);
+ return result == TvInputHal.SUCCESS;
+ }
+ }
}
interface Listener {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index d7ecd7a507cd7..b7314bba415b8 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -51,6 +51,7 @@ import android.media.tv.TvContract;
import android.media.tv.TvInputHardwareInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputService;
+import android.media.tv.TvStreamConfig;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
import android.os.Binder;
@@ -1256,6 +1257,49 @@ public final class TvInputManagerService extends SystemService {
}
}
+ @Override
+ public List getAvailableTvStreamConfigList(String inputId, int userId)
+ throws RemoteException {
+ if (mContext.checkCallingPermission(
+ android.Manifest.permission.CAPTURE_TV_INPUT)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "getAvailableTvStreamConfigList");
+ try {
+ return mTvInputHardwareManager.getAvailableTvStreamConfigList(
+ inputId, callingUid, resolvedUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
+ int userId)
+ throws RemoteException {
+ if (mContext.checkCallingPermission(
+ android.Manifest.permission.CAPTURE_TV_INPUT)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "captureFrame");
+ try {
+ return mTvInputHardwareManager.captureFrame(
+ inputId, surface, config, callingUid, resolvedUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
@SuppressWarnings("resource")
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index a9d5c7279d287..41976ffb39105 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -36,6 +36,7 @@ static struct {
jmethodID deviceAvailable;
jmethodID deviceUnavailable;
jmethodID streamConfigsChanged;
+ jmethodID firstFrameCaptured;
} gTvInputHalClassInfo;
static struct {
@@ -539,6 +540,14 @@ void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succ
thread = connection.mThread;
}
thread->onCaptured(seq, succeeded);
+ if (seq == 0) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(
+ mThiz,
+ gTvInputHalClassInfo.firstFrameCaptured,
+ deviceId,
+ streamId);
+ }
}
////////////////////////////////////////////////////////////////////////////////
@@ -638,6 +647,9 @@ int register_android_server_tv_TvInputHal(JNIEnv* env) {
GET_METHOD_ID(
gTvInputHalClassInfo.streamConfigsChanged, clazz,
"streamConfigsChangedFromNative", "(I)V");
+ GET_METHOD_ID(
+ gTvInputHalClassInfo.firstFrameCaptured, clazz,
+ "firstFrameCapturedFromNative", "(II)V");
FIND_CLASS(gTvStreamConfigClassInfo.clazz, "android/media/tv/TvStreamConfig");
gTvStreamConfigClassInfo.clazz = jclass(env->NewGlobalRef(gTvStreamConfigClassInfo.clazz));