TIF: handle inputs holding the overlay view even after the session release
Bug: 17336221 Change-Id: I7fb7890039bc3731b62410caa9802db5870ec2e2
This commit is contained in:
@@ -166,6 +166,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
|
mTvInputSessionImpl.scheduleOverlayViewCleanup();
|
||||||
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
|
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,12 @@ import android.graphics.PixelFormat;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.hardware.hdmi.HdmiDeviceInfo;
|
import android.hardware.hdmi.HdmiDeviceInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import android.os.Process;
|
||||||
import android.os.RemoteCallbackList;
|
import android.os.RemoteCallbackList;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -44,6 +46,7 @@ import android.view.Surface;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.accessibility.CaptioningManager;
|
import android.view.accessibility.CaptioningManager;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.os.SomeArgs;
|
import com.android.internal.os.SomeArgs;
|
||||||
@@ -242,12 +245,16 @@ public abstract class TvInputService extends Service {
|
|||||||
* Base class for derived classes to implement to provide a TV input session.
|
* Base class for derived classes to implement to provide a TV input session.
|
||||||
*/
|
*/
|
||||||
public abstract static class Session implements KeyEvent.Callback {
|
public abstract static class Session implements KeyEvent.Callback {
|
||||||
|
private static final int DETACH_OVERLAY_VIEW_TIMEOUT = 5000;
|
||||||
private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
|
private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
|
||||||
private final WindowManager mWindowManager;
|
private final WindowManager mWindowManager;
|
||||||
final Handler mHandler;
|
final Handler mHandler;
|
||||||
private WindowManager.LayoutParams mWindowParams;
|
private WindowManager.LayoutParams mWindowParams;
|
||||||
private Surface mSurface;
|
private Surface mSurface;
|
||||||
|
private Context mContext;
|
||||||
|
private FrameLayout mOverlayViewContainer;
|
||||||
private View mOverlayView;
|
private View mOverlayView;
|
||||||
|
private OverlayViewCleanUpTask mOverlayViewCleanUpTask;
|
||||||
private boolean mOverlayViewEnabled;
|
private boolean mOverlayViewEnabled;
|
||||||
private IBinder mWindowToken;
|
private IBinder mWindowToken;
|
||||||
private Rect mOverlayFrame;
|
private Rect mOverlayFrame;
|
||||||
@@ -264,6 +271,7 @@ public abstract class TvInputService extends Service {
|
|||||||
* @param context The context of the application
|
* @param context The context of the application
|
||||||
*/
|
*/
|
||||||
public Session(Context context) {
|
public Session(Context context) {
|
||||||
|
mContext = context;
|
||||||
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||||
mHandler = new Handler(context.getMainLooper());
|
mHandler = new Handler(context.getMainLooper());
|
||||||
}
|
}
|
||||||
@@ -853,7 +861,6 @@ public abstract class TvInputService extends Service {
|
|||||||
* session.
|
* session.
|
||||||
*/
|
*/
|
||||||
void release() {
|
void release() {
|
||||||
removeOverlayView(true);
|
|
||||||
onRelease();
|
onRelease();
|
||||||
if (mSurface != null) {
|
if (mSurface != null) {
|
||||||
mSurface.release();
|
mSurface.release();
|
||||||
@@ -863,6 +870,9 @@ public abstract class TvInputService extends Service {
|
|||||||
mSessionCallback = null;
|
mSessionCallback = null;
|
||||||
mPendingActions.clear();
|
mPendingActions.clear();
|
||||||
}
|
}
|
||||||
|
// Removes the overlay view lastly so that any hanging on the main thread can be handled
|
||||||
|
// in {@link #scheduleOverlayViewCleanup}.
|
||||||
|
removeOverlayView(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -947,9 +957,8 @@ public abstract class TvInputService extends Service {
|
|||||||
* @param frame A position of the overlay view.
|
* @param frame A position of the overlay view.
|
||||||
*/
|
*/
|
||||||
void createOverlayView(IBinder windowToken, Rect frame) {
|
void createOverlayView(IBinder windowToken, Rect frame) {
|
||||||
if (mOverlayView != null) {
|
if (mOverlayViewContainer != null) {
|
||||||
mWindowManager.removeView(mOverlayView);
|
removeOverlayView(false);
|
||||||
mOverlayView = null;
|
|
||||||
}
|
}
|
||||||
if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
|
if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
|
||||||
mWindowToken = windowToken;
|
mWindowToken = windowToken;
|
||||||
@@ -962,6 +971,15 @@ public abstract class TvInputService extends Service {
|
|||||||
if (mOverlayView == null) {
|
if (mOverlayView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (mOverlayViewCleanUpTask != null) {
|
||||||
|
mOverlayViewCleanUpTask.cancel(true);
|
||||||
|
mOverlayViewCleanUpTask = null;
|
||||||
|
}
|
||||||
|
// Creates a container view to check hanging on the overlay view detaching.
|
||||||
|
// Adding/removing the overlay view to/from the container make the view attach/detach
|
||||||
|
// logic run on the main thread.
|
||||||
|
mOverlayViewContainer = new FrameLayout(mContext);
|
||||||
|
mOverlayViewContainer.addView(mOverlayView);
|
||||||
// TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
|
// 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.
|
// an overlay window above the media window but below the application window.
|
||||||
int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
|
int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
|
||||||
@@ -978,7 +996,7 @@ public abstract class TvInputService extends Service {
|
|||||||
WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
|
WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
|
||||||
mWindowParams.gravity = Gravity.START | Gravity.TOP;
|
mWindowParams.gravity = Gravity.START | Gravity.TOP;
|
||||||
mWindowParams.token = windowToken;
|
mWindowParams.token = windowToken;
|
||||||
mWindowManager.addView(mOverlayView, mWindowParams);
|
mWindowManager.addView(mOverlayViewContainer, mWindowParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -995,32 +1013,50 @@ public abstract class TvInputService extends Service {
|
|||||||
onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
|
onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
|
||||||
}
|
}
|
||||||
mOverlayFrame = frame;
|
mOverlayFrame = frame;
|
||||||
if (!mOverlayViewEnabled || mOverlayView == null) {
|
if (!mOverlayViewEnabled || mOverlayViewContainer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mWindowParams.x = frame.left;
|
mWindowParams.x = frame.left;
|
||||||
mWindowParams.y = frame.top;
|
mWindowParams.y = frame.top;
|
||||||
mWindowParams.width = frame.right - frame.left;
|
mWindowParams.width = frame.right - frame.left;
|
||||||
mWindowParams.height = frame.bottom - frame.top;
|
mWindowParams.height = frame.bottom - frame.top;
|
||||||
mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
|
mWindowManager.updateViewLayout(mOverlayViewContainer, mWindowParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the current overlay view.
|
* Removes the current overlay view.
|
||||||
*/
|
*/
|
||||||
void removeOverlayView(boolean clearWindowToken) {
|
void removeOverlayView(boolean clearWindowToken) {
|
||||||
if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")");
|
if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayViewContainer + ")");
|
||||||
if (clearWindowToken) {
|
if (clearWindowToken) {
|
||||||
mWindowToken = null;
|
mWindowToken = null;
|
||||||
mOverlayFrame = null;
|
mOverlayFrame = null;
|
||||||
}
|
}
|
||||||
if (mOverlayView != null) {
|
if (mOverlayViewContainer != null) {
|
||||||
mWindowManager.removeView(mOverlayView);
|
// Removes the overlay view from the view hierarchy in advance so that it can be
|
||||||
|
// cleaned up in the {@link OverlayViewCleanUpTask} if the remove process is
|
||||||
|
// hanging.
|
||||||
|
mOverlayViewContainer.removeView(mOverlayView);
|
||||||
mOverlayView = null;
|
mOverlayView = null;
|
||||||
|
mWindowManager.removeView(mOverlayViewContainer);
|
||||||
|
mOverlayViewContainer = null;
|
||||||
mWindowParams = null;
|
mWindowParams = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules a task which checks whether the overlay view is detached and kills the process
|
||||||
|
* if it is not. Note that this method is expected to be called in a non-main thread.
|
||||||
|
*/
|
||||||
|
void scheduleOverlayViewCleanup() {
|
||||||
|
View overlayViewParent = mOverlayViewContainer;
|
||||||
|
if (overlayViewParent != null) {
|
||||||
|
mOverlayViewCleanUpTask = new OverlayViewCleanUpTask();
|
||||||
|
mOverlayViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
|
||||||
|
overlayViewParent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes care of dispatching incoming input events and tells whether the event was handled.
|
* Takes care of dispatching incoming input events and tells whether the event was handled.
|
||||||
*/
|
*/
|
||||||
@@ -1050,22 +1086,22 @@ public abstract class TvInputService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) {
|
if (mOverlayViewContainer == null || !mOverlayViewContainer.isAttachedToWindow()) {
|
||||||
return TvInputManager.Session.DISPATCH_NOT_HANDLED;
|
return TvInputManager.Session.DISPATCH_NOT_HANDLED;
|
||||||
}
|
}
|
||||||
if (!mOverlayView.hasWindowFocus()) {
|
if (!mOverlayViewContainer.hasWindowFocus()) {
|
||||||
mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
|
mOverlayViewContainer.getViewRootImpl().windowFocusChanged(true, true);
|
||||||
}
|
}
|
||||||
if (isNavigationKey && mOverlayView.hasFocusable()) {
|
if (isNavigationKey && mOverlayViewContainer.hasFocusable()) {
|
||||||
// If mOverlayView has focusable views, navigation key events should be always
|
// If mOverlayView has focusable views, navigation key events should be always
|
||||||
// handled. If not, it can make the application UI navigation messed up.
|
// handled. If not, it can make the application UI navigation messed up.
|
||||||
// For example, in the case that the left-most view is focused, a left key event
|
// For example, in the case that the left-most view is focused, a left key event
|
||||||
// will not be handled in ViewRootImpl. Then, the left key event will be handled in
|
// will not be handled in ViewRootImpl. Then, the left key event will be handled in
|
||||||
// the application during the UI navigation of the TV input.
|
// the application during the UI navigation of the TV input.
|
||||||
mOverlayView.getViewRootImpl().dispatchInputEvent(event);
|
mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event);
|
||||||
return TvInputManager.Session.DISPATCH_HANDLED;
|
return TvInputManager.Session.DISPATCH_HANDLED;
|
||||||
} else {
|
} else {
|
||||||
mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
|
mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event, receiver);
|
||||||
return TvInputManager.Session.DISPATCH_IN_PROGRESS;
|
return TvInputManager.Session.DISPATCH_IN_PROGRESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1095,6 +1131,27 @@ public abstract class TvInputService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class OverlayViewCleanUpTask extends AsyncTask<View, Void, Void> {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(View... views) {
|
||||||
|
View overlayViewParent = views[0];
|
||||||
|
try {
|
||||||
|
Thread.sleep(DETACH_OVERLAY_VIEW_TIMEOUT);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isCancelled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (overlayViewParent.isAttachedToWindow()) {
|
||||||
|
Log.e(TAG, "Time out on releasing overlay view. Killing "
|
||||||
|
+ overlayViewParent.getContext().getPackageName());
|
||||||
|
Process.killProcess(Process.myPid());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user