Add overlay view in Tv Input Framework
A TvInputService app developers sometimes want to draw UI above a surface
playing TV. For this purpose, we add a window in TIS and allow developers to
attach their customized view on the TV surface.
Change-Id: I65c3dffa17580b8d4c42fac58bbfc8dad338c185
diff --git a/api/current.txt b/api/current.txt
index 42b1e18..61c17f1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27822,7 +27822,6 @@
public static final class TvInputManager.Session {
method public void release();
- method public void setSurface(android.view.Surface);
method public void setVolume(float);
method public void tune(android.net.Uri);
}
@@ -27844,12 +27843,22 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.tv.TvInputService";
}
- public static abstract class TvInputService.TvInputSessionImpl {
+ public abstract class TvInputService.TvInputSessionImpl {
ctor public TvInputService.TvInputSessionImpl();
+ method public android.view.View onCreateOverlayView();
method public abstract void onRelease();
method public abstract boolean onSetSurface(android.view.Surface);
method public abstract void onSetVolume(float);
method public abstract boolean onTune(android.net.Uri);
+ method public void setOverlayViewEnabled(boolean);
+ }
+
+ public class TvView extends android.view.SurfaceView {
+ ctor public TvView(android.content.Context);
+ ctor public TvView(android.content.Context, android.util.AttributeSet);
+ ctor public TvView(android.content.Context, android.util.AttributeSet, int);
+ method public void bindTvInput(android.content.ComponentName, android.tv.TvInputManager.SessionCreateCallback);
+ method public void unbindTvInput();
}
}
diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl
index a927dc9..a4c99e4 100644
--- a/core/java/android/tv/ITvInputManager.aidl
+++ b/core/java/android/tv/ITvInputManager.aidl
@@ -17,6 +17,7 @@
package android.tv;
import android.content.ComponentName;
+import android.graphics.Rect;
import android.net.Uri;
import android.tv.ITvInputClient;
import android.tv.TvInputInfo;
@@ -40,4 +41,9 @@
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void setVolume(in IBinder sessionToken, float volume, int userId);
void tune(in IBinder sessionToken, in Uri channelUri, int userId);
+
+ void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+ int userId);
+ void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId);
+ void removeOverlayView(in IBinder sessionToken, int userId);
}
diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl
index d80f286..672784f 100644
--- a/core/java/android/tv/ITvInputService.aidl
+++ b/core/java/android/tv/ITvInputService.aidl
@@ -17,7 +17,6 @@
package android.tv;
import android.tv.ITvInputServiceCallback;
-import android.tv.ITvInputSession;
import android.tv.ITvInputSessionCallback;
/**
diff --git a/core/java/android/tv/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl
index d379d2d..32fee4b 100644
--- a/core/java/android/tv/ITvInputSession.aidl
+++ b/core/java/android/tv/ITvInputSession.aidl
@@ -16,6 +16,7 @@
package android.tv;
+import android.graphics.Rect;
import android.net.Uri;
import android.view.Surface;
@@ -31,4 +32,8 @@
// is to introduce some new concepts that will solve a number of problems in audio policy today.
void setVolume(float volume);
void tune(in Uri channelUri);
+
+ void createOverlayView(in IBinder windowToken, in Rect frame);
+ void relayoutOverlayView(in Rect frame);
+ void removeOverlayView();
}
diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java
index 66fe5e1..a6e0877 100644
--- a/core/java/android/tv/ITvInputSessionWrapper.java
+++ b/core/java/android/tv/ITvInputSessionWrapper.java
@@ -17,13 +17,16 @@
package android.tv;
import android.content.Context;
+import android.graphics.Rect;
import android.net.Uri;
+import android.os.IBinder;
import android.os.Message;
import android.tv.TvInputService.TvInputSessionImpl;
import android.util.Log;
import android.view.Surface;
import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
/**
* Implements the internal ITvInputSession interface to convert incoming calls on to it back to
@@ -38,6 +41,9 @@
private static final int DO_SET_SURFACE = 2;
private static final int DO_SET_VOLUME = 3;
private static final int DO_TUNE = 4;
+ private static final int DO_CREATE_OVERLAY_VIEW = 5;
+ private static final int DO_RELAYOUT_OVERLAY_VIEW = 6;
+ private static final int DO_REMOVE_OVERLAY_VIEW = 7;
private TvInputSessionImpl mTvInputSession;
private final HandlerCaller mCaller;
@@ -71,6 +77,20 @@
mTvInputSession.tune((Uri) msg.obj);
return;
}
+ case DO_CREATE_OVERLAY_VIEW: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSession.createOverlayView((IBinder) args.arg1, (Rect) args.arg2);
+ args.recycle();
+ return;
+ }
+ case DO_RELAYOUT_OVERLAY_VIEW: {
+ mTvInputSession.relayoutOverlayView((Rect) msg.obj);
+ return;
+ }
+ case DO_REMOVE_OVERLAY_VIEW: {
+ mTvInputSession.removeOverlayView(true);
+ return;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
@@ -97,4 +117,20 @@
public void tune(Uri channelUri) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri));
}
+
+ @Override
+ public void createOverlayView(IBinder windowToken, Rect frame) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken,
+ frame));
+ }
+
+ @Override
+ public void relayoutOverlayView(Rect frame) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame));
+ }
+
+ @Override
+ public void removeOverlayView() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
+ }
}
diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java
index 4cf2b35..05f0b9c 100644
--- a/core/java/android/tv/TvInputManager.java
+++ b/core/java/android/tv/TvInputManager.java
@@ -17,6 +17,7 @@
package android.tv;
import android.content.ComponentName;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
@@ -24,6 +25,7 @@
import android.util.Log;
import android.util.SparseArray;
import android.view.Surface;
+import android.view.View;
import java.util.ArrayList;
import java.util.HashMap;
@@ -320,8 +322,8 @@
/** The Session provides the per-session functionality of TV inputs. */
public static final class Session {
private final ITvInputManager mService;
- private final IBinder mToken;
private final int mUserId;
+ private IBinder mToken;
/** @hide */
private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) {
@@ -332,10 +334,16 @@
/**
* Releases this session.
+ *
+ * @throws IllegalStateException if the session has been already released.
*/
public void release() {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
try {
mService.releaseSession(mToken, mUserId);
+ mToken = null;
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -345,8 +353,12 @@
* Sets the {@link android.view.Surface} for this session.
*
* @param surface A {@link android.view.Surface} used to render video.
+ * @throws IllegalStateException if the session has been already released.
*/
- public void setSurface(Surface surface) {
+ void setSurface(Surface surface) {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
// surface can be null.
try {
mService.setSurface(mToken, surface, mUserId);
@@ -360,8 +372,12 @@
*
* @param volume A volume value between 0.0f to 1.0f.
* @throws IllegalArgumentException if the volume value is out of range.
+ * @throws IllegalStateException if the session has been already released.
*/
public void setVolume(float volume) {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
try {
if (volume < 0.0f || volume > 1.0f) {
throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
@@ -377,16 +393,90 @@
*
* @param channelUri The URI of a channel.
* @throws IllegalArgumentException if the argument is {@code null}.
+ * @throws IllegalStateException if the session has been already released.
*/
public void tune(Uri channelUri) {
if (channelUri == null) {
throw new IllegalArgumentException("channelUri cannot be null");
}
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
try {
mService.tune(mToken, channelUri, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
+
+ /**
+ * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
+ * should be called whenever the layout of its containing view is changed.
+ * {@link #removeOverlayView()} should be called to remove the overlay view.
+ * Since a session can have only one overlay view, this method should be called only once
+ * or it can be called again after calling {@link #removeOverlayView()}.
+ *
+ * @param view A view playing TV.
+ * @param frame A position of the overlay view.
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ * @throws IllegalStateException if {@code view} is not attached to a window or
+ * if the session has been already released.
+ */
+ void createOverlayView(View view, Rect frame) {
+ if (view == null) {
+ throw new IllegalArgumentException("view cannot be null");
+ }
+ if (frame == null) {
+ throw new IllegalArgumentException("frame cannot be null");
+ }
+ if (view.getWindowToken() == null) {
+ throw new IllegalStateException("view must be attached to a window");
+ }
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Relayouts the current overlay view.
+ *
+ * @param frame A new position of the overlay view.
+ * @throws IllegalArgumentException if the arguments is {@code null}.
+ * @throws IllegalStateException if the session has been already released.
+ */
+ void relayoutOverlayView(Rect frame) {
+ if (frame == null) {
+ throw new IllegalArgumentException("frame cannot be null");
+ }
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.relayoutOverlayView(mToken, frame, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Removes the current overlay view.
+ *
+ * @throws IllegalStateException if the session has been already released.
+ */
+ void removeOverlayView() {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.removeOverlayView(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
}
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
index d7f6c32..80eb407 100644
--- a/core/java/android/tv/TvInputService.java
+++ b/core/java/android/tv/TvInputService.java
@@ -18,7 +18,10 @@
import android.app.Service;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
@@ -26,7 +29,10 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
+import android.view.Gravity;
import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -125,7 +131,37 @@
/**
* Base class for derived classes to implement to provide {@link TvInputManager.Session}.
*/
- public abstract static class TvInputSessionImpl {
+ public abstract class TvInputSessionImpl {
+ private final WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
+ private View mOverlayView;
+ private boolean mOverlayViewEnabled;
+ private IBinder mWindowToken;
+ private Rect mOverlayFrame;
+
+ public TvInputSessionImpl() {
+ mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ public void setOverlayViewEnabled(final boolean enable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (enable == mOverlayViewEnabled) {
+ return;
+ }
+ mOverlayViewEnabled = enable;
+ if (enable) {
+ if (mWindowToken != null) {
+ createOverlayView(mWindowToken, mOverlayFrame);
+ }
+ } else {
+ removeOverlayView(false);
+ }
+ }
+ });
+ }
+
/**
* Called when the session is released.
*/
@@ -157,11 +193,22 @@
public abstract boolean onTune(Uri channelUri);
/**
+ * Called when an application requests to create an overlay view. Each session
+ * implementation can override this method and return its own view.
+ *
+ * @return a view attached to the overlay window
+ */
+ public View onCreateOverlayView() {
+ return null;
+ }
+
+ /**
* This method is called when the application would like to stop using the current input
* session.
*/
void release() {
onRelease();
+ removeOverlayView(true);
}
/**
@@ -186,6 +233,87 @@
onTune(channelUri);
// TODO: Handle failure.
}
+
+ /**
+ * Creates an overlay view. This calls {@link onCreateOverlayView} to get
+ * a view to attach to the overlay window.
+ *
+ * @param windowToken A window token of an application.
+ * @param frame A position of the overlay view.
+ */
+ void createOverlayView(IBinder windowToken, Rect frame) {
+ if (mOverlayView != null) {
+ mWindowManager.removeView(mOverlayView);
+ mOverlayView = null;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "create overlay view(" + frame + ")");
+ }
+ mWindowToken = windowToken;
+ mOverlayFrame = frame;
+ if (!mOverlayViewEnabled) {
+ return;
+ }
+ mOverlayView = onCreateOverlayView();
+ if (mOverlayView == null) {
+ return;
+ }
+ // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
+ // an overlay window above the media window but below the application window.
+ int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+ // We make the overlay view non-focusable and non-touchable so that
+ // the application that owns the window token can decide whether to consume or
+ // dispatch the input events.
+ int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ mWindowParams = new WindowManager.LayoutParams(
+ frame.right - frame.left, frame.bottom - frame.top,
+ frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mWindowParams.gravity = Gravity.START | Gravity.TOP;
+ mWindowParams.token = windowToken;
+ mWindowManager.addView(mOverlayView, mWindowParams);
+ }
+
+ /**
+ * Relayouts the current overlay view.
+ *
+ * @param frame A new position of the overlay view.
+ */
+ void relayoutOverlayView(Rect frame) {
+ if (DEBUG) {
+ Log.d(TAG, "relayout overlay view(" + frame + ")");
+ }
+ mOverlayFrame = frame;
+ if (!mOverlayViewEnabled || mOverlayView == null) {
+ return;
+ }
+ mWindowParams.x = frame.left;
+ mWindowParams.y = frame.top;
+ mWindowParams.width = frame.right - frame.left;
+ mWindowParams.height = frame.bottom - frame.top;
+ mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
+ }
+
+ /**
+ * Removes the current overlay view.
+ */
+ void removeOverlayView(boolean clearWindowToken) {
+ if (DEBUG) {
+ Log.d(TAG, "remove overlay view(" + mOverlayView + ")");
+ }
+ if (clearWindowToken) {
+ mWindowToken = null;
+ mOverlayFrame = null;
+ }
+ if (mOverlayView != null) {
+ mWindowManager.removeView(mOverlayView);
+ mOverlayView = null;
+ mWindowParams = null;
+ }
+ }
}
private final class ServiceHandler extends Handler {
diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java
new file mode 100644
index 0000000..325950d
--- /dev/null
+++ b/core/java/android/tv/TvView.java
@@ -0,0 +1,228 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.tv.TvInputManager;
+import android.tv.TvInputManager.Session;
+import android.tv.TvInputManager.SessionCreateCallback;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewTreeObserver;
+
+/**
+ * View playing TV
+ */
+public class TvView extends SurfaceView {
+ private static final String TAG = "TvView";
+
+ private final Handler mHandler = new Handler();
+ private TvInputManager.Session mSession;
+ private Surface mSurface;
+ private boolean mOverlayViewCreated;
+ private Rect mOverlayViewFrame;
+ private boolean mGlobalListenersAdded;
+ private TvInputManager mTvInputManager;
+ private SessionCreateCallback mSessionCreateCallback;
+
+ private SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width
+ + ", height=" + height + ")");
+ if (holder.getSurface() == mSurface) {
+ return;
+ }
+ mSurface = holder.getSurface();
+ setSessionSurface(mSurface);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurface = holder.getSurface();
+ setSessionSurface(mSurface);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mSurface = null;
+ setSessionSurface(null);
+ }
+ };
+
+ public TvView(Context context) {
+ this(context, null, 0);
+ }
+
+ public TvView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ getHolder().addCallback(mSurfaceHolderCallback);
+ mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
+ }
+
+ /**
+ * Binds a TV input to this view. {@link SessionCreateCallback#onSessionCreated} will be
+ * called to send the result of this binding with {@link TvInputManager.Session}.
+ * If a TV input is already bound, the input will be unbound from this view and its session
+ * will be released.
+ *
+ * @param name TV input name will be bound to this view.
+ * @param callback called when TV input is bound. The callback sends
+ * {@link TvInputManager.Session}
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ */
+ public void bindTvInput(ComponentName name, SessionCreateCallback callback) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (mSession != null) {
+ release();
+ }
+ // When bindTvInput is called multiple times before the callback is called,
+ // only the callback of the last bindTvInput call will be actually called back.
+ // The previous callbacks will be ignored. For the logic, mSessionCreateCallback
+ // is newly assigned for every bindTvInput call and compared with
+ // MySessionCreateCallback.this.
+ mSessionCreateCallback = new MySessionCreateCallback(callback);
+ mTvInputManager.createSession(name, mSessionCreateCallback, mHandler);
+ }
+
+ /**
+ * Unbinds a TV input currently bound. Its corresponding {@link TvInputManager.Session}
+ * is released.
+ */
+ public void unbindTvInput() {
+ if (mSession != null) {
+ release();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ createSessionOverlayView();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeSessionOverlayView();
+ super.onDetachedFromWindow();
+ }
+
+ /** @hide */
+ @Override
+ protected void updateWindow(boolean force, boolean redrawNeeded) {
+ super.updateWindow(force, redrawNeeded);
+ relayoutSessionOverlayView();
+ }
+
+ private void release() {
+ setSessionSurface(null);
+ removeSessionOverlayView();
+ mSession.release();
+ mSession = null;
+ }
+
+ private void setSessionSurface(Surface surface) {
+ if (mSession == null) {
+ return;
+ }
+ mSession.setSurface(surface);
+ }
+
+ private void createSessionOverlayView() {
+ if (mSession == null || !isAttachedToWindow()
+ || mOverlayViewCreated) {
+ return;
+ }
+ mOverlayViewFrame = getViewFrameOnScreen();
+ mSession.createOverlayView(this, mOverlayViewFrame);
+ mOverlayViewCreated = true;
+ }
+
+ private void removeSessionOverlayView() {
+ if (mSession == null || !mOverlayViewCreated) {
+ return;
+ }
+ mSession.removeOverlayView();
+ mOverlayViewCreated = false;
+ mOverlayViewFrame = null;
+ }
+
+ private void relayoutSessionOverlayView() {
+ if (mSession == null || !isAttachedToWindow()
+ || !mOverlayViewCreated) {
+ return;
+ }
+ Rect viewFrame = getViewFrameOnScreen();
+ if (viewFrame.equals(mOverlayViewFrame)) {
+ return;
+ }
+ mSession.relayoutOverlayView(viewFrame);
+ mOverlayViewFrame = viewFrame;
+ }
+
+ private Rect getViewFrameOnScreen() {
+ int[] location = new int[2];
+ getLocationOnScreen(location);
+ return new Rect(location[0], location[1],
+ location[0] + getWidth(), location[1] + getHeight());
+ }
+
+ private class MySessionCreateCallback implements SessionCreateCallback {
+ final SessionCreateCallback mExternalCallback;
+
+ MySessionCreateCallback(SessionCreateCallback externalCallback) {
+ mExternalCallback = externalCallback;
+ }
+
+ @Override
+ public void onSessionCreated(Session session) {
+ if (this != mSessionCreateCallback) {
+ // This callback is obsolete.
+ session.release();
+ return;
+ }
+ mSession = session;
+ if (session != null) {
+ // mSurface may not be ready yet as soon as starting an application.
+ // In the case, we don't send Session.setSurface(null) unnecessarily.
+ // setSessionSurface will be called in surfaceCreated.
+ if (mSurface != null) {
+ setSessionSurface(mSurface);
+ }
+ createSessionOverlayView();
+ }
+ if (mExternalCallback != null) {
+ mExternalCallback.onSessionCreated(session);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 23123dd..4a2cc1a 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -422,7 +422,8 @@
mWindowType = type;
}
- private void updateWindow(boolean force, boolean redrawNeeded) {
+ /** @hide */
+ protected void updateWindow(boolean force, boolean redrawNeeded) {
if (!mHaveFrame) {
return;
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 16ed7fe..a72d4c6 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -26,6 +26,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
@@ -42,6 +43,7 @@
import android.tv.TvInputService;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.Surface;
@@ -116,17 +118,19 @@
UserState userState = getUserStateLocked(userId);
userState.inputList.clear();
+ if (DEBUG) Slog.d(TAG, "buildTvInputList");
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServices(
new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES);
for (ResolveInfo ri : services) {
ServiceInfo si = ri.serviceInfo;
if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
- Log.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
+ Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
+ android.Manifest.permission.BIND_TV_INPUT);
continue;
}
TvInputInfo info = new TvInputInfo(ri);
+ if (DEBUG) Slog.d(TAG, "add " + info.getId());
userState.inputList.add(info);
}
}
@@ -161,7 +165,7 @@
try {
state.session.release();
} catch (RemoteException e) {
- Log.e(TAG, "error in release", e);
+ Slog.e(TAG, "error in release", e);
}
}
}
@@ -173,7 +177,7 @@
try {
serviceState.service.unregisterCallback(serviceState.callback);
} catch (RemoteException e) {
- Log.e(TAG, "error in unregisterCallback", e);
+ Slog.e(TAG, "error in unregisterCallback", e);
}
}
serviceState.clients.clear();
@@ -244,7 +248,7 @@
return;
}
if (DEBUG) {
- Log.i(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId
+ Slog.d(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId
+ ")");
}
Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name);
@@ -255,7 +259,7 @@
// This means that the service is already connected but its state indicates that we have
// nothing to do with it. Then, disconnect the service.
if (DEBUG) {
- Log.i(TAG, "unbindService(name=" + name.getClassName() + ")");
+ Slog.d(TAG, "unbindService(name=" + name.getClassName() + ")");
}
mContext.unbindService(serviceState.connection);
userState.serviceStateMap.remove(name);
@@ -265,7 +269,7 @@
private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
final SessionState sessionState, final int userId) {
if (DEBUG) {
- Log.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
+ Slog.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
+ ")");
}
// Set up a callback to send the session token.
@@ -273,7 +277,7 @@
@Override
public void onSessionCreated(ITvInputSession session) {
if (DEBUG) {
- Log.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")");
+ Slog.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")");
}
synchronized (mLock) {
sessionState.session = session;
@@ -293,7 +297,7 @@
try {
service.createSession(callback);
} catch (RemoteException e) {
- Log.e(TAG, "error in createSession", e);
+ Slog.e(TAG, "error in createSession", e);
removeSessionStateLocked(sessionToken, userId);
sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
sessionState.seq, userId);
@@ -305,7 +309,7 @@
try {
client.onSessionCreated(name, sessionToken, seq);
} catch (RemoteException exception) {
- Log.e(TAG, "error in onSessionCreated", exception);
+ Slog.e(TAG, "error in onSessionCreated", exception);
}
if (sessionToken == null) {
@@ -394,7 +398,7 @@
try {
serviceState.service.registerCallback(serviceState.callback);
} catch (RemoteException e) {
- Log.e(TAG, "error in registerCallback", e);
+ Slog.e(TAG, "error in registerCallback", e);
}
} else {
updateServiceConnectionLocked(name, resolvedUserId);
@@ -430,7 +434,7 @@
try {
serviceState.service.unregisterCallback(serviceState.callback);
} catch (RemoteException e) {
- Log.e(TAG, "error in unregisterCallback", e);
+ Slog.e(TAG, "error in unregisterCallback", e);
} finally {
serviceState.callback = null;
updateServiceConnectionLocked(name, resolvedUserId);
@@ -491,7 +495,7 @@
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId).release();
} catch (RemoteException e) {
- Log.e(TAG, "error in release", e);
+ Slog.e(TAG, "error in release", e);
}
removeSessionStateLocked(sessionToken, resolvedUserId);
@@ -513,7 +517,7 @@
getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
surface);
} catch (RemoteException e) {
- Log.e(TAG, "error in setSurface", e);
+ Slog.e(TAG, "error in setSurface", e);
}
}
} finally {
@@ -533,7 +537,7 @@
getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
volume);
} catch (RemoteException e) {
- Log.e(TAG, "error in setVolume", e);
+ Slog.e(TAG, "error in setVolume", e);
}
}
} finally {
@@ -549,13 +553,10 @@
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- SessionState sessionState = getUserStateLocked(resolvedUserId)
- .sessionStateMap.get(sessionToken);
- final String serviceName = sessionState.name.getClassName();
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
} catch (RemoteException e) {
- Log.e(TAG, "error in tune", e);
+ Slog.e(TAG, "error in tune", e);
return;
}
}
@@ -563,6 +564,67 @@
Binder.restoreCallingIdentity(identity);
}
}
+
+ @Override
+ public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "createOverlayView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .createOverlayView(windowToken, frame);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in createOverlayView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "relayoutOverlayView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .relayoutOverlayView(frame);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in relayoutOverlayView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void removeOverlayView(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "removeOverlayView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .removeOverlayView();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in removeOverlayView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
private static final class UserState {
@@ -620,7 +682,7 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) {
- Log.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
+ Slog.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
}
synchronized (mLock) {
ServiceState serviceState = getServiceStateLocked(name, mUserId);
@@ -632,7 +694,7 @@
try {
serviceState.service.registerCallback(serviceState.callback);
} catch (RemoteException e) {
- Log.e(TAG, "error in registerCallback", e);
+ Slog.e(TAG, "error in registerCallback", e);
}
}
@@ -648,7 +710,7 @@
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) {
- Log.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
+ Slog.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
}
}
}
@@ -664,7 +726,7 @@
public void onAvailabilityChanged(ComponentName name, boolean isAvailable)
throws RemoteException {
if (DEBUG) {
- Log.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
+ Slog.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
+ isAvailable + ")");
}
synchronized (mLock) {