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