diff --git a/api/current.txt b/api/current.txt index 399e92ceb02c0..621185c932687 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16721,6 +16721,22 @@ package android.media.tv { method public void onInputStateChanged(java.lang.String, int); } + public abstract class TvInputPassthroughWrapperService extends android.media.tv.TvInputService { + ctor public TvInputPassthroughWrapperService(); + method public abstract java.lang.String getPassthroughInputId(java.lang.String); + method public abstract android.media.tv.TvInputPassthroughWrapperService.PassthroughWrapperSession onCreatePassthroughWrapperSession(); + method public final android.media.tv.TvInputService.Session onCreateSession(java.lang.String); + } + + public abstract class TvInputPassthroughWrapperService.PassthroughWrapperSession extends android.media.tv.TvInputService.Session { + ctor public TvInputPassthroughWrapperService.PassthroughWrapperSession(); + method public abstract void onPassthroughSessionCreationFailed(); + method public abstract void onPassthroughSessionReleased(); + method public abstract void onPassthroughVideoAvailable(); + method public abstract void onPassthroughVideoUnavailable(int); + method public final boolean onSetSurface(android.view.Surface); + } + public abstract class TvInputService extends android.app.Service { ctor public TvInputService(); method public final android.os.IBinder onBind(android.content.Intent); diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index eddc6c923bd41..f7dd75c37e305 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -43,7 +43,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_RELEASE = 1; private static final int DO_SET_SURFACE = 2; private static final int DO_DISPATCH_SURFACE_CHANGED = 3; - private static final int DO_SET_VOLUME = 4; + private static final int DO_SET_STREAM_VOLUME = 4; private static final int DO_TUNE = 5; private static final int DO_SET_CAPTION_ENABLED = 6; private static final int DO_SELECT_TRACK = 7; @@ -99,8 +99,8 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand args.recycle(); return; } - case DO_SET_VOLUME: { - mTvInputSessionImpl.setVolume((Float) msg.obj); + case DO_SET_STREAM_VOLUME: { + mTvInputSessionImpl.setStreamVolume((Float) msg.obj); return; } case DO_TUNE: { @@ -162,7 +162,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand @Override public final void setVolume(float volume) { - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VOLUME, volume)); + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_STREAM_VOLUME, volume)); } @Override diff --git a/media/java/android/media/tv/TvInputPassthroughWrapperService.java b/media/java/android/media/tv/TvInputPassthroughWrapperService.java new file mode 100644 index 0000000000000..562dc2fbcee37 --- /dev/null +++ b/media/java/android/media/tv/TvInputPassthroughWrapperService.java @@ -0,0 +1,258 @@ +/* + * 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.media.tv; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.Surface; + +/** + * TvInputPassthroughWrapperService represents a TV input which controls an external device + * connected to a pass-through TV input (e.g. HDMI 1). + *

+ * This service wraps around a pass-through TV input and delegates the {@link Surface} to the + * connected TV input so that the application can show the pass-through TV input while + * TvInputPassthroughWrapperService controls the underlying external device via a separate + * connection. In the setup activity, the TV input should get the pass-through TV input ID, around + * which this service will wrap. The service implementation should pass the ID via + * {@link TvInputPassthroughWrapperService#getPassthroughInputId(String)}. In addition, + * it needs to implement {@link TvInputPassthroughWrapperService#onCreatePassthroughWrapperSession} + * to handle requests from the application. + *

+ */ +public abstract class TvInputPassthroughWrapperService extends TvInputService { + private static final String TAG = "TvInputPassthroughWrapperService"; + // STOPSHIP: Turn debugging off. + private static final boolean DEBUG = true; + private TvInputManager mTvInputManager; + private Handler mHandler; + + @Override + public void onCreate() { + if (DEBUG) Log.d(TAG, "onCreate()"); + super.onCreate(); + mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); + mHandler = new Handler(); + } + + @Override + public final Session onCreateSession(String inputId) { + if (DEBUG) Log.d(TAG, "onCreateSession()"); + // Checks the pass-through TV input is properly setup. + String passthroughInputId = getPassthroughInputId(inputId); + if (passthroughInputId == null) { + Log.w(TAG, "The passthrough TV input for input id(" + inputId + ") is not setup yet."); + return null; + } + // Checks if input id from the derived class is really pass-through type. + TvInputInfo info = mTvInputManager.getTvInputInfo(passthroughInputId); + if (info == null || !info.isPassthroughInputType()) { + Log.w(TAG, "Invalid TV input id from derived class: " + passthroughInputId); + return null; + } + // Creates a PassthroughWrapperSession. + PassthroughWrapperSession session = onCreatePassthroughWrapperSession(); + if (session == null) { + return null; + } + // Connects to the pass-through input the external device is connected to. + if (!session.connect(passthroughInputId)) { + throw new IllegalStateException("WrapperSession cannot be reused."); + } + return session; + } + + /** + * Returns an implementation of {@link PassthroughWrapperSession}. + *

+ * May return {@code null} if {@link TvInputPassthroughWrapperService} fails to create a + * session. + *

+ */ + public abstract PassthroughWrapperSession onCreatePassthroughWrapperSession(); + + /** + * Returns the TV input id the external device is connected to. + *

+ * {@link TvInputPassthroughWrapperService} is expected to identify the pass-though TV + * input the external device is connected to in the setup phase of this TV input. + * May return {@code null} if the pass-though TV input is not identified yet. + *

+ * @param inputId The ID of the TV input which controls the external device. + */ + public abstract String getPassthroughInputId(String inputId); + + /** + * Base session class for derived classes to handle the request from the application. This + * creates additional session to the pass-through TV input internally and delegates the + * {@link Surface} given from the application. + */ + public abstract class PassthroughWrapperSession extends Session { + private static final float VOLUME_ON = 1.0f; + private static final float VOLUME_OFF = 0f; + private TvInputManager.Session mSession; + private Surface mSurface; + private Float mVolume; + private boolean mReleased; + private int mSurfaceFormat; + private int mSurfaceWidth; + private int mSurfaceHeight; + private boolean mSurfaceChanged; + private boolean mConnectionRequested; + + private final TvInputManager.SessionCallback mSessionCallback = + new TvInputManager.SessionCallback() { + @Override + public void onSessionCreated(TvInputManager.Session session) { + if (session == null) { + Log.w(TAG, "Failed to create session."); + onPassthroughSessionCreationFailed(); + return; + } + if (mReleased) { + session.release(); + return; + } + if (mSurface != null) { + session.setSurface(mSurface); + mSurface = null; + } + if (mVolume != null) { + session.setStreamVolume(mVolume); + mVolume = null; + } + if (mSurfaceChanged) { + session.dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight); + mSurfaceChanged = false; + } + mSession = session; + } + + @Override + public void onSessionReleased(TvInputManager.Session session) { + mReleased = true; + mSession = null; + onPassthroughSessionReleased(); + } + + @Override + public void onVideoAvailable(TvInputManager.Session session) { + if (mSession == session) { + onPassthroughVideoAvailable(); + } + } + + @Override + public void onVideoUnavailable(TvInputManager.Session session, int reason) { + if (mSession == session) { + onPassthroughVideoUnavailable(reason); + } + } + + @Override + public void onSessionEvent(TvInputManager.Session session, String eventType, + Bundle eventArgs) { + if (mSession == session) { + dispatchSessionEvent(eventType, eventArgs); + } + } + }; + + /** + * Called when failed to create a session for pass-through TV input. + */ + public abstract void onPassthroughSessionCreationFailed(); + + /** + * Called when the pass-through TV input session is released. This typically happens when + * the process hosting the pass-through TV input has crashed or been killed. + */ + public abstract void onPassthroughSessionReleased(); + + /** + * Called when the underlying pass-through TV input session calls + * {@link #dispatchVideoAvailable()}. + */ + public abstract void onPassthroughVideoAvailable(); + + /** + * Called when the underlying pass-through TV input session calls + * {@link #dispatchVideoUnavailable(int)}. + * + * @param reason The reason why the pass-through TV input stopped the playback. + */ + public abstract void onPassthroughVideoUnavailable(int reason); + + @Override + public final boolean onSetSurface(Surface surface) { + if (DEBUG) Log.d(TAG, "onSetSurface(" + surface + ")"); + if (mSession == null) { + mSurface = surface; + } else { + mSession.setSurface(surface); + } + return true; + } + + private boolean connect(String inputId) { + if (mConnectionRequested) { + return false; + } + mTvInputManager.createSession(inputId, mSessionCallback, mHandler); + mConnectionRequested = true; + return true; + } + + @Override + void release() { + super.release(); + mReleased = true; + if (mSession != null) { + mSession.release(); + } + } + + @Override + void dispatchSurfaceChanged(int format, int width, int height) { + super.dispatchSurfaceChanged(format, width, height); + if (mSession == null) { + mSurfaceFormat = format; + mSurfaceWidth = width; + mSurfaceHeight = height; + mSurfaceChanged = true; + } else { + mSession.dispatchSurfaceChanged(format, width, height); + } + } + + @Override + void setStreamVolume(float volume) { + super.setStreamVolume(volume); + // Here, we let the pass-through TV input know only whether volume is on or off and + // make the fine control done in the derived class to prevent that the volume is + // controlled in the both side. + float volumeForPassthriughInput = (volume > 0.0f) ? VOLUME_ON : VOLUME_OFF; + if (mSession == null) { + mVolume = Float.valueOf(volumeForPassthriughInput); + } else { + mSession.setStreamVolume(volumeForPassthriughInput); + } + } + } +} diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 718e8756af63f..b23cec7e78342 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -681,7 +681,7 @@ public abstract class TvInputService extends Service { /** * Calls {@link #onSetStreamVolume}. */ - void setVolume(float volume) { + void setStreamVolume(float volume) { onSetStreamVolume(volume); }