Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5f18f8f8 authored by Shubang Lu's avatar Shubang Lu
Browse files

[CSAI] handle media view

Bug: 320779253
Test: mmm
Change-Id: I4102d7b57cda36433aa7ef86b91f670bb9af4ff6
parent e8c4c3b0
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.media.tv.ad;

import android.graphics.Rect;
import android.media.tv.ad.ITvAdClient;
import android.media.tv.ad.ITvAdManagerCallback;
import android.media.tv.ad.TvAdServiceInfo;
@@ -37,4 +38,9 @@ interface ITvAdManager {

    void registerCallback(in ITvAdManagerCallback callback, int userId);
    void unregisterCallback(in ITvAdManagerCallback callback, int userId);

    void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
            int userId);
    void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
    void removeMediaView(in IBinder sessionToken, int userId);
}
+5 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.media.tv.ad;

import android.graphics.Rect;
import android.view.Surface;

/**
@@ -27,4 +28,8 @@ oneway interface ITvAdSession {
    void startAdService();
    void setSurface(in Surface surface);
    void dispatchSurfaceChanged(int format, int width, int height);

    void createMediaView(in IBinder windowToken, in Rect frame);
    void relayoutMediaView(in Rect frame);
    void removeMediaView();
}
+36 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package android.media.tv.ad;

import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -43,6 +45,9 @@ public class ITvAdSessionWrapper
    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_CREATE_MEDIA_VIEW = 4;
    private static final int DO_RELAYOUT_MEDIA_VIEW = 5;
    private static final int DO_REMOVE_MEDIA_VIEW = 6;

    private final HandlerCaller mCaller;
    private TvAdService.Session mSessionImpl;
@@ -61,6 +66,7 @@ public class ITvAdSessionWrapper

    @Override
    public void release() {
        mSessionImpl.scheduleMediaViewCleanup();
        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
    }

@@ -97,6 +103,20 @@ public class ITvAdSessionWrapper
                args.recycle();
                break;
            }
            case DO_CREATE_MEDIA_VIEW: {
                SomeArgs args = (SomeArgs) msg.obj;
                mSessionImpl.createMediaView((IBinder) args.arg1, (Rect) args.arg2);
                args.recycle();
                break;
            }
            case DO_RELAYOUT_MEDIA_VIEW: {
                mSessionImpl.relayoutMediaView((Rect) msg.obj);
                break;
            }
            case DO_REMOVE_MEDIA_VIEW: {
                mSessionImpl.removeMediaView(true);
                break;
            }
            default: {
                Log.w(TAG, "Unhandled message code: " + msg.what);
                break;
@@ -129,6 +149,22 @@ public class ITvAdSessionWrapper
                mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
    }

    @Override
    public void createMediaView(IBinder windowToken, Rect frame) {
        mCaller.executeOrSendMessage(
                mCaller.obtainMessageOO(DO_CREATE_MEDIA_VIEW, windowToken, frame));
    }

    @Override
    public void relayoutMediaView(Rect frame) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_MEDIA_VIEW, frame));
    }

    @Override
    public void removeMediaView() {
        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW));
    }

    private final class TvAdEventReceiver extends InputEventReceiver {
        TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
+63 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
import android.graphics.Rect;
import android.media.tv.TvInputManager;
import android.media.tv.flags.Flags;
import android.os.Handler;
@@ -35,6 +36,7 @@ import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.Surface;
import android.view.View;

import com.android.internal.util.Preconditions;

@@ -285,6 +287,67 @@ public class TvAdManager {
            }
        }

        /**
         * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
         * should be called whenever the layout of its containing view is changed.
         * {@link #removeMediaView()} should be called to remove the media view.
         * Since a session can have only one media view, this method should be called only once
         * or it can be called again after calling {@link #removeMediaView()}.
         *
         * @param view A view for AD service.
         * @param frame A position of the media view.
         * @throws IllegalStateException if {@code view} is not attached to a window.
         */
        void createMediaView(@NonNull View view, @NonNull Rect frame) {
            Preconditions.checkNotNull(view);
            Preconditions.checkNotNull(frame);
            if (view.getWindowToken() == null) {
                throw new IllegalStateException("view must be attached to a window");
            }
            if (mToken == null) {
                Log.w(TAG, "The session has been already released");
                return;
            }
            try {
                mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Relayouts the current media view.
         *
         * @param frame A new position of the media view.
         */
        void relayoutMediaView(@NonNull Rect frame) {
            Preconditions.checkNotNull(frame);
            if (mToken == null) {
                Log.w(TAG, "The session has been already released");
                return;
            }
            try {
                mService.relayoutMediaView(mToken, frame, mUserId);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Removes the current media view.
         */
        void removeMediaView() {
            if (mToken == null) {
                Log.w(TAG, "The session has been already released");
                return;
            }
            try {
                mService.removeMediaView(mToken, mUserId);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Notifies of any structural changes (format or size) of the surface passed in
         * {@link #setSurface}.
+219 −0
Original line number Diff line number Diff line
@@ -20,19 +20,25 @@ import android.annotation.CallSuper;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -42,6 +48,7 @@ import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;

import com.android.internal.os.SomeArgs;

@@ -56,6 +63,8 @@ public abstract class TvAdService extends Service {
    private static final boolean DEBUG = false;
    private static final String TAG = "TvAdService";

    private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;

    /**
     * Name under which a TvAdService component publishes information about itself. This meta-data
     * must reference an XML resource containing an
@@ -151,7 +160,14 @@ public abstract class TvAdService extends Service {
        private final Context mContext;
        final Handler mHandler;
        private final WindowManager mWindowManager;
        private WindowManager.LayoutParams mWindowParams;
        private Surface mSurface;
        private FrameLayout mMediaViewContainer;
        private View mMediaView;
        private MediaViewCleanUpTask mMediaViewCleanUpTask;
        private boolean mMediaViewEnabled;
        private IBinder mWindowToken;
        private Rect mMediaFrame;


        /**
@@ -165,6 +181,48 @@ public abstract class TvAdService extends Service {
            mHandler = new Handler(context.getMainLooper());
        }

        /**
         * Enables or disables the media view.
         *
         * <p>By default, the media view is disabled. Must be called explicitly after the
         * session is created to enable the media view.
         *
         * <p>The TV AD service can disable its media view when needed.
         *
         * @param enable {@code true} if you want to enable the media view. {@code false}
         *            otherwise.
         * @hide
         */
        @CallSuper
        public void setMediaViewEnabled(final boolean enable) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (enable == mMediaViewEnabled) {
                        return;
                    }
                    mMediaViewEnabled = enable;
                    if (enable) {
                        if (mWindowToken != null) {
                            createMediaView(mWindowToken, mMediaFrame);
                        }
                    } else {
                        removeMediaView(false);
                    }
                }
            });
        }

        /**
         * Returns {@code true} if media view is enabled, {@code false} otherwise.
         *
         * @see #setMediaViewEnabled(boolean)
         * @hide
         */
        public boolean isMediaViewEnabled() {
            return mMediaViewEnabled;
        }

        /**
         * Releases TvAdService session.
         */
@@ -180,6 +238,9 @@ public abstract class TvAdService extends Service {
                mSessionCallback = null;
                mPendingActions.clear();
            }
            // Removes the media view lastly so that any hanging on the main thread can be handled
            // in {@link #scheduleMediaViewCleanup}.
            removeMediaView(true);
        }

        /**
@@ -306,6 +367,33 @@ public abstract class TvAdService extends Service {
        public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
        }

        /**
         * Called when the size of the media view is changed by the application.
         *
         * <p>This is always called at least once when the session is created regardless of whether
         * the media view is enabled or not. The media view container size is the same as the
         * containing {@link TvAdView}. Note that the size of the underlying surface can
         * be different if the surface was changed by calling {@link #layoutSurface}.
         *
         * @param width The width of the media view, in pixels.
         * @param height The height of the media view, in pixels.
         * @hide
         */
        public void onMediaViewSizeChanged(@Px int width, @Px int height) {
        }

        /**
         * Called when the application requests to create a media view. Each session
         * implementation can override this method and return its own view.
         *
         * @return a view attached to the media window. {@code null} if no media view is created.
         * @hide
         */
        @Nullable
        public View onCreateMediaView() {
            return null;
        }

        /**
         * Takes care of dispatching incoming input events and tells whether the event was handled.
         */
@@ -388,6 +476,137 @@ public abstract class TvAdService extends Service {
                }
            }
        }

        /**
         * Creates a media view. This calls {@link #onCreateMediaView} to get a view to attach
         * to the media window.
         *
         * @param windowToken A window token of the application.
         * @param frame A position of the media view.
         */
        void createMediaView(IBinder windowToken, Rect frame) {
            if (mMediaViewContainer != null) {
                removeMediaView(false);
            }
            if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
            mWindowToken = windowToken;
            mMediaFrame = frame;
            onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
            if (!mMediaViewEnabled) {
                return;
            }
            mMediaView = onCreateMediaView();
            if (mMediaView == null) {
                return;
            }
            if (mMediaViewCleanUpTask != null) {
                mMediaViewCleanUpTask.cancel(true);
                mMediaViewCleanUpTask = null;
            }
            // Creates a container view to check hanging on the media view detaching.
            // Adding/removing the media view to/from the container make the view attach/detach
            // logic run on the main thread.
            mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
            mMediaViewContainer.addView(mMediaView);

            int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
            // 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 flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
            if (ActivityManager.isHighEndGfx()) {
                flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
            mWindowParams = new WindowManager.LayoutParams(
                    frame.right - frame.left, frame.bottom - frame.top,
                    frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
            mWindowParams.privateFlags |=
                    WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
            mWindowParams.gravity = Gravity.START | Gravity.TOP;
            mWindowParams.token = windowToken;
            mWindowManager.addView(mMediaViewContainer, mWindowParams);
        }

        /**
         * Relayouts the current media view.
         *
         * @param frame A new position of the media view.
         */
        void relayoutMediaView(Rect frame) {
            if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
            if (mMediaFrame == null || mMediaFrame.width() != frame.width()
                    || mMediaFrame.height() != frame.height()) {
                // Note: relayoutMediaView is called whenever TvAdView's layout is
                // changed regardless of setMediaViewEnabled.
                onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
            }
            mMediaFrame = frame;
            if (!mMediaViewEnabled || mMediaViewContainer == null) {
                return;
            }
            mWindowParams.x = frame.left;
            mWindowParams.y = frame.top;
            mWindowParams.width = frame.right - frame.left;
            mWindowParams.height = frame.bottom - frame.top;
            mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
        }

        /**
         * Removes the current media view.
         */
        void removeMediaView(boolean clearWindowToken) {
            if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
            if (clearWindowToken) {
                mWindowToken = null;
                mMediaFrame = null;
            }
            if (mMediaViewContainer != null) {
                // Removes the media view from the view hierarchy in advance so that it can be
                // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
                // hanging.
                mMediaViewContainer.removeView(mMediaView);
                mMediaView = null;
                mWindowManager.removeView(mMediaViewContainer);
                mMediaViewContainer = null;
                mWindowParams = null;
            }
        }

        /**
         * Schedules a task which checks whether the media 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 scheduleMediaViewCleanup() {
            View mediaViewParent = mMediaViewContainer;
            if (mediaViewParent != null) {
                mMediaViewCleanUpTask = new MediaViewCleanUpTask();
                mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
                        mediaViewParent);
            }
        }
    }

    private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
        @Override
        protected Void doInBackground(View... views) {
            View mediaViewParent = views[0];
            try {
                Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
            } catch (InterruptedException e) {
                return null;
            }
            if (isCancelled()) {
                return null;
            }
            if (mediaViewParent.isAttachedToWindow()) {
                Log.e(TAG, "Time out on releasing media view. Killing "
                        + mediaViewParent.getContext().getPackageName());
                android.os.Process.killProcess(Process.myPid());
            }
            return null;
        }
    }


Loading