Merge "TIAF: handle surface and surface view"

This commit is contained in:
Shubang Lu
2021-11-05 07:36:28 +00:00
committed by Android (Google) Code Review
8 changed files with 460 additions and 4 deletions

View File

@@ -24,4 +24,5 @@ package android.media.tv.interactive;
oneway interface ITvIAppClient {
void onSessionCreated(in String iAppServiceId, IBinder token, int seq);
void onSessionReleased(int seq);
void onLayoutSurface(int left, int top, int right, int bottom, int seq);
}

View File

@@ -17,6 +17,7 @@
package android.media.tv.interactive;
import android.media.tv.interactive.ITvIAppClient;
import android.view.Surface;
/**
* Interface to the TV interactive app service.
@@ -27,4 +28,7 @@ interface ITvIAppManager {
void createSession(
in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
int userId);
}

View File

@@ -16,6 +16,8 @@
package android.media.tv.interactive;
import android.view.Surface;
/**
* Sub-interface of ITvIAppService.aidl which is created per session and has its own context.
* @hide
@@ -23,4 +25,6 @@ package android.media.tv.interactive;
oneway interface ITvIAppSession {
void startIApp();
void release();
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
}

View File

@@ -25,4 +25,5 @@ import android.media.tv.interactive.ITvIAppSession;
*/
oneway interface ITvIAppSessionCallback {
void onSessionCreated(in ITvIAppSession session);
void onLayoutSurface(int left, int top, int right, int bottom);
}

View File

@@ -25,6 +25,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import android.view.Surface;
import com.android.internal.util.Preconditions;
@@ -88,6 +89,18 @@ public final class TvIAppManager {
record.postSessionReleased();
}
}
@Override
public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq " + seq);
return;
}
record.postLayoutSurface(left, top, right, bottom);
}
}
};
}
@@ -158,6 +171,44 @@ public final class TvIAppManager {
}
}
/**
* Sets the {@link android.view.Surface} for this session.
*
* @param surface A {@link android.view.Surface} used to render video.
*/
public void setSurface(Surface surface) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
}
// surface can be null.
try {
mService.setSurface(mToken, surface, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Notifies of any structural changes (format or size) of the surface passed in
* {@link #setSurface}.
*
* @param format The new PixelFormat of the surface.
* @param width The new width of the surface.
* @param height The new height of the surface.
*/
public void dispatchSurfaceChanged(int format, int width, int height) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
}
try {
mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Releases this session.
*/
@@ -211,6 +262,16 @@ public final class TvIAppManager {
}
});
}
void postLayoutSurface(final int left, final int top, final int right,
final int bottom) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
}
});
}
}
/**
@@ -235,5 +296,18 @@ public final class TvIAppManager {
*/
public void onSessionReleased(@NonNull Session session) {
}
/**
* This is called when {@link TvIAppService.Session#layoutSurface} is called to change the
* layout of surface.
*
* @param session A {@link TvIAppManager.Session} associated with this callback.
* @param left Left position.
* @param top Top position.
* @param right Right position.
* @param bottom Bottom position.
*/
public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
}
}
}

View File

@@ -16,10 +16,12 @@
package android.media.tv.interactive;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
@@ -27,6 +29,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Surface;
import com.android.internal.os.SomeArgs;
@@ -94,23 +97,100 @@ public abstract class TvIAppService extends Service {
// @GuardedBy("mLock")
private final List<Runnable> mPendingActions = new ArrayList<>();
private final Context mContext;
private final Handler mHandler;
private Surface mSurface;
/**
* Creates a new Session.
*
* @param context The context of the application
*/
public Session(Context context) {
mContext = context;
mHandler = new Handler(context.getMainLooper());
}
/**
* Starts TvIAppService session.
*/
public void onStartIApp() {
}
/**
* Called when the application sets the surface.
*
* <p>The TV IApp service should render interactive app UI onto the given surface. When
* called with {@code null}, the input service should immediately free any references to the
* currently set surface and stop using it.
*
* @param surface The surface to be used for interactive app UI rendering. Can be
* {@code null}.
* @return {@code true} if the surface was set successfully, {@code false} otherwise.
*/
public abstract boolean onSetSurface(@Nullable Surface surface);
/**
* Called after any structural changes (format or size) have been made to the surface passed
* in {@link #onSetSurface}. This method is always called at least once, after
* {@link #onSetSurface} is called with non-null surface.
*
* @param format The new PixelFormat of the surface.
* @param width The new width of the surface.
* @param height The new height of the surface.
*/
public void onSurfaceChanged(int format, int width, int height) {
}
/**
* Releases TvIAppService session.
*/
public void onRelease() {
}
/**
* Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
* is relative to the overlay view that sits on top of this surface.
*
* @param left Left position in pixels, relative to the overlay view.
* @param top Top position in pixels, relative to the overlay view.
* @param right Right position in pixels, relative to the overlay view.
* @param bottom Bottom position in pixels, relative to the overlay view.
*/
public void layoutSurface(final int left, final int top, final int right,
final int bottom) {
if (left > right || top > bottom) {
throw new IllegalArgumentException("Invalid parameter");
}
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
+ ", r=" + right + ", b=" + bottom + ",)");
}
if (mSessionCallback != null) {
mSessionCallback.onLayoutSurface(left, top, right, bottom);
}
} catch (RemoteException e) {
Log.w(TAG, "error in layoutSurface", e);
}
}
});
}
void startIApp() {
onStartIApp();
}
void release() {
onRelease();
if (mSurface != null) {
mSurface.release();
mSurface = null;
}
}
private void initialize(ITvIAppSessionCallback callback) {
@@ -122,6 +202,45 @@ public abstract class TvIAppService extends Service {
mPendingActions.clear();
}
}
/**
* Calls {@link #onSetSurface}.
*/
void setSurface(Surface surface) {
onSetSurface(surface);
if (mSurface != null) {
mSurface.release();
}
mSurface = surface;
// TODO: Handle failure.
}
/**
* Calls {@link #onSurfaceChanged}.
*/
void dispatchSurfaceChanged(int format, int width, int height) {
if (DEBUG) {
Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
+ ", height=" + height + ")");
}
onSurfaceChanged(format, width, height);
}
private void executeOrPostRunnableOnMainThread(Runnable action) {
synchronized (mLock) {
if (mSessionCallback == null) {
// The session is not initialized yet.
mPendingActions.add(action);
} else {
if (mHandler.getLooper().isCurrentThread()) {
action.run();
} else {
// Posts the runnable if this is not called from the main thread
mHandler.post(action);
}
}
}
}
}
/**
@@ -143,6 +262,16 @@ public abstract class TvIAppService extends Service {
public void release() {
mSessionImpl.release();
}
@Override
public void setSurface(Surface surface) {
mSessionImpl.setSurface(surface);
}
@Override
public void dispatchSurfaceChanged(int format, int width, int height) {
mSessionImpl.dispatchSurfaceChanged(format, width, height);
}
}
@SuppressLint("HandlerLeak")

View File

@@ -17,10 +17,18 @@
package android.media.tv.interactive;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.media.tv.interactive.TvIAppManager.Session;
import android.media.tv.interactive.TvIAppManager.SessionCallback;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
/**
@@ -35,18 +43,137 @@ public class TvIAppView extends ViewGroup {
private final Handler mHandler = new Handler();
private Session mSession;
private MySessionCallback mSessionCallback;
private SurfaceView mSurfaceView;
private Surface mSurface;
private boolean mSurfaceChanged;
private int mSurfaceFormat;
private int mSurfaceWidth;
private int mSurfaceHeight;
private boolean mUseRequestedSurfaceLayout;
private int mSurfaceViewLeft;
private int mSurfaceViewRight;
private int mSurfaceViewTop;
private int mSurfaceViewBottom;
private final AttributeSet mAttrs;
private final int mDefStyleAttr;
private final XmlResourceParser mParser;
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (DEBUG) {
Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format
+ ", width=" + width + ", height=" + height + ")");
}
mSurfaceFormat = format;
mSurfaceWidth = width;
mSurfaceHeight = height;
mSurfaceChanged = true;
dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mSurface = holder.getSurface();
setSessionSurface(mSurface);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mSurface = null;
mSurfaceChanged = false;
setSessionSurface(null);
}
};
public TvIAppView(Context context) {
this(context, null, 0);
}
public TvIAppView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, /* attrs = */null, /* defStyleAttr = */0);
int sourceResId = Resources.getAttributeSetSourceResId(attrs);
if (sourceResId != Resources.ID_NULL) {
Log.d(TAG, "Build local AttributeSet");
mParser = context.getResources().getXml(sourceResId);
mAttrs = Xml.asAttributeSet(mParser);
} else {
Log.d(TAG, "Use passed in AttributeSet");
mParser = null;
mAttrs = attrs;
}
mDefStyleAttr = defStyleAttr;
resetSurfaceView();
mTvIAppManager = (TvIAppManager) getContext().getSystemService("tv_interactive_app");
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (DEBUG) {
Log.d(TAG,
"onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)");
Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
+ ", bottom=" + bottom + ",)");
}
if (mUseRequestedSurfaceLayout) {
mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
mSurfaceViewBottom);
} else {
mSurfaceView.layout(0, 0, right - left, bottom - top);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
int width = mSurfaceView.getMeasuredWidth();
int height = mSurfaceView.getMeasuredHeight();
int childState = mSurfaceView.getMeasuredState();
setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
resolveSizeAndState(height, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mSurfaceView.setVisibility(visibility);
}
private void resetSurfaceView() {
if (mSurfaceView != null) {
mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
removeView(mSurfaceView);
}
mSurface = null;
mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr);
// The surface view's content should be treated as secure all the time.
mSurfaceView.setSecure(true);
mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
addView(mSurfaceView);
}
/**
* Resets this TvIAppView.
*/
public void reset() {
if (DEBUG) Log.d(TAG, "reset()");
resetInternal();
}
private void setSessionSurface(Surface surface) {
if (mSession == null) {
return;
}
mSession.setSurface(surface);
}
private void dispatchSurfaceChanged(int format, int width, int height) {
if (mSession == null) {
return;
}
mSession.dispatchSurfaceChanged(format, width, height);
}
/**
@@ -75,6 +202,17 @@ public class TvIAppView extends ViewGroup {
}
}
private void resetInternal() {
mSessionCallback = null;
if (mSession != null) {
setSessionSurface(null);
mUseRequestedSurfaceLayout = false;
mSession.release();
mSession = null;
resetSurfaceView();
}
}
private class MySessionCallback extends SessionCallback {
final String mIAppServiceId;
int mType;
@@ -99,7 +237,15 @@ public class TvIAppView extends ViewGroup {
}
mSession = session;
if (session != null) {
// TODO: handle SurfaceView and InputChannel.
// 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);
if (mSurfaceChanged) {
dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
}
} else {
// Failed to create
// Todo: forward error to Tv App
@@ -119,5 +265,23 @@ public class TvIAppView extends ViewGroup {
mSessionCallback = null;
mSession = null;
}
@Override
public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
if (DEBUG) {
Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
+ right + ", bottom=" + bottom + ",)");
}
if (this != mSessionCallback) {
Log.w(TAG, "onLayoutSurface - session not created");
return;
}
mSurfaceViewLeft = left;
mSurfaceViewTop = top;
mSurfaceViewRight = right;
mSurfaceViewBottom = bottom;
mUseRequestedSurfaceLayout = true;
requestLayout();
}
}
}

View File

@@ -36,6 +36,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.SparseArray;
import android.view.Surface;
import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
@@ -136,6 +137,16 @@ public class TvIAppManagerService extends SystemService {
return sessionState;
}
@GuardedBy("mLock")
private ITvIAppSession getSessionLocked(SessionState sessionState) {
ITvIAppSession session = sessionState.mSession;
if (session == null) {
throw new IllegalStateException("Session not yet created for token "
+ sessionState.mSessionToken);
}
return session;
}
private final class BinderService extends ITvIAppManager.Stub {
@Override
@@ -234,6 +245,55 @@ public class TvIAppManagerService extends SystemService {
Slogf.e(TAG, "error in start", e);
}
}
@Override
public void setSurface(IBinder sessionToken, Surface surface, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "setSurface");
SessionState sessionState = null;
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
sessionState = getSessionStateLocked(sessionToken, callingUid,
resolvedUserId);
getSessionLocked(sessionState).setSurface(surface);
} catch (RemoteException | SessionNotFoundException e) {
Slogf.e(TAG, "error in setSurface", e);
}
}
} finally {
if (surface != null) {
// surface is not used in TvIAppManagerService.
surface.release();
}
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
int height, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "dispatchSurfaceChanged");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
resolvedUserId);
getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
height);
} catch (RemoteException | SessionNotFoundException e) {
Slogf.e(TAG, "error in dispatchSurfaceChanged", e);
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@GuardedBy("mLock")
@@ -616,6 +676,25 @@ public class TvIAppManagerService extends SystemService {
}
}
@Override
public void onLayoutSurface(int left, int top, int right, int bottom) {
synchronized (mLock) {
if (DEBUG) {
Slogf.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
+ ", right=" + right + ", bottom=" + bottom + ",)");
}
if (mSessionState.mSession == null || mSessionState.mClient == null) {
return;
}
try {
mSessionState.mClient.onLayoutSurface(left, top, right, bottom,
mSessionState.mSeq);
} catch (RemoteException e) {
Slogf.e(TAG, "error in onLayoutSurface", e);
}
}
}
@GuardedBy("mLock")
private boolean addSessionTokenToClientStateLocked(ITvIAppSession session) {
try {