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

Commit 187423c0 authored by Wonsik Kim's avatar Wonsik Kim
Browse files

TIF: one-to-many relationship for TvInputService to TvInputInfo

The scope of this change is to provide a skeleton code for supporting
multiple TV input per service.

Bug: 16138420
Change-Id: Ic51355902d5e0424b8fc8a75c495d4781a7ed744
parent fe61a67b
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.media.tv;

import android.media.tv.ITvInputServiceCallback;
import android.media.tv.ITvInputSessionCallback;
import android.media.tv.TvInputHardwareInfo;
import android.view.InputChannel;

/**
@@ -27,5 +28,10 @@ import android.view.InputChannel;
oneway interface ITvInputService {
    void registerCallback(ITvInputServiceCallback callback);
    void unregisterCallback(in ITvInputServiceCallback callback);
    void createSession(in InputChannel channel, ITvInputSessionCallback callback);
    void createSession(in InputChannel channel, ITvInputSessionCallback callback,
            in String inputId);

    // For hardware TvInputService
    void notifyHardwareAdded(in TvInputHardwareInfo info);
    void notifyHardwareRemoved(int deviceId);
}
+5 −4
Original line number Diff line number Diff line
@@ -16,13 +16,14 @@

package android.media.tv;

import android.content.ComponentName;
import android.media.tv.TvInputInfo;

/**
 * Helper interface for ITvInputService to allow the TV input to notify the client when its status
 * has been changed.
 * Helper interface for ITvInputService to allow the service to notify the
 * TvInputManagerService.
 * @hide
 */
oneway interface ITvInputServiceCallback {
    void onInputStateChanged(int state);
    void addTvInput(in TvInputInfo inputInfo);
    void removeTvInput(in String inputId);
}
+71 −7
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.media.tv.TvInputHardwareInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -88,9 +90,46 @@ public final class TvInputInfo implements Parcelable {
     * instantiating it from the given Context and ResolveInfo.
     *
     * @param service The ResolveInfo returned from the package manager about this TV input service.
     * @hide */
     * @hide
     */
    public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service)
            throws XmlPullParserException, IOException {
        return createTvInputInfo(context, service, generateInputIdForComponentName(
                new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name)));
    }

    /**
     * Create a new instance of the TvInputInfo class,
     * instantiating it from the given Context, ResolveInfo, and HdmiCecDeviceInfo.
     *
     * @param service The ResolveInfo returned from the package manager about this TV input service.
     * @param cecInfo The HdmiCecDeviceInfo for a HDMI CEC logical device.
     * @hide
     */
    public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
            HdmiCecDeviceInfo cecInfo) throws XmlPullParserException, IOException {
        return createTvInputInfo(context, service, generateInputIdForHdmiCec(
                new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
                cecInfo));
    }

    /**
     * Create a new instance of the TvInputInfo class,
     * instantiating it from the given Context, ResolveInfo, and TvInputHardwareInfo.
     *
     * @param service The ResolveInfo returned from the package manager about this TV input service.
     * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
     * @hide
     */
    public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
            TvInputHardwareInfo hardwareInfo) throws XmlPullParserException, IOException {
        return createTvInputInfo(context, service, generateInputIdForHardware(
                new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
                hardwareInfo));
    }

    private static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
            String id) throws XmlPullParserException, IOException {
        ServiceInfo si = service.serviceInfo;
        PackageManager pm = context.getPackageManager();
        XmlResourceParser parser = null;
@@ -115,7 +154,7 @@ public final class TvInputInfo implements Parcelable {
                        "Meta-data does not start with tv-input-service tag in " + si.name);
            }

            TvInputInfo input = new TvInputInfo(context, service, null);
            TvInputInfo input = new TvInputInfo(context, service, id, null);
            TypedArray sa = res.obtainAttributes(attrs,
                    com.android.internal.R.styleable.TvInputService);
            input.mSetupActivity = sa.getString(
@@ -153,12 +192,13 @@ public final class TvInputInfo implements Parcelable {
     * Constructor.
     *
     * @param service The ResolveInfo returned from the package manager about this TV input service.
     * @hide
     * @param id ID of this TV input. Should be generated via generateInputId*().
     * @param parentId ID of this TV input's parent input. {@code null} if none exists.
     */
    private TvInputInfo(Context context, ResolveInfo service, String parentId) {
    private TvInputInfo(Context context, ResolveInfo service, String id, String parentId) {
        mService = service;
        ServiceInfo si = service.serviceInfo;
        mId = generateInputIdForComponentName(new ComponentName(si.packageName, si.name));
        mId = id;
        mParentId = parentId;
    }

@@ -311,12 +351,36 @@ public final class TvInputInfo implements Parcelable {
     *
     * @param name the component name for generating an input id.
     * @return the generated input id for the given {@code name}.
     * @hide
     */
    public static final String generateInputIdForComponentName(ComponentName name) {
    private static final String generateInputIdForComponentName(ComponentName name) {
        return name.flattenToShortString();
    }

    /**
     * Used to generate an input id from a ComponentName and HdmiCecDeviceInfo.
     *
     * @param name the component name for generating an input id.
     * @param cecInfo HdmiCecDeviceInfo describing this TV input.
     * @return the generated input id for the given {@code name} and {@code cecInfo}.
     */
    private static final String generateInputIdForHdmiCec(
            ComponentName name, HdmiCecDeviceInfo cecInfo) {
        return name.flattenToShortString() + String.format("|CEC%08X%08X",
                cecInfo.getPhysicalAddress(), cecInfo.getLogicalAddress());
    }

    /**
     * Used to generate an input id from a ComponentName and TvInputHardwareInfo
     *
     * @param name the component name for generating an input id.
     * @param hardwareInfo TvInputHardwareInfo describing this TV input.
     * @return the generated input id for the given {@code name} and {@code hardwareInfo}.
     */
    private static final String generateInputIdForHardware(
            ComponentName name, TvInputHardwareInfo hardwareInfo) {
        return name.flattenToShortString() + String.format("|HW%d", hardwareInfo.getDeviceId());
    }

    /**
     * Used to make this class parcelable.
     *
+98 −2
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.media.tv;

import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -104,7 +105,8 @@ public abstract class TvInputService extends Service {
            }

            @Override
            public void createSession(InputChannel channel, ITvInputSessionCallback cb) {
            public void createSession(InputChannel channel, ITvInputSessionCallback cb,
                    String inputId) {
                if (channel == null) {
                    Log.w(TAG, "Creating session without input channel");
                }
@@ -114,8 +116,21 @@ public abstract class TvInputService extends Service {
                SomeArgs args = SomeArgs.obtain();
                args.arg1 = channel;
                args.arg2 = cb;
                args.arg3 = inputId;
                mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
            }

            @Override
            public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
                mHandler.obtainMessage(ServiceHandler.DO_ADD_TV_INPUT_FROM_HARDWARE,
                        hardwareInfo).sendToTarget();
            }

            @Override
            public void notifyHardwareRemoved(int deviceId) {
                mHandler.obtainMessage(ServiceHandler.DO_REMOVE_TV_INPUT_FROM_HARDWARE,
                        deviceId, 0).sendToTarget();
            }
        };
    }

@@ -137,6 +152,44 @@ public abstract class TvInputService extends Service {
     */
    public abstract Session onCreateSession();

    /**
     * Returns a concrete implementation of {@link Session}.
     * <p>
     * May return {@code null} if this TV input service fails to create a session for some reason.
     * </p>
     * @param inputId The ID of the TV input associated with the session.
     *
     * @hide
     */
    @SystemApi
    public Session onCreateSession(String inputId) {
        return onCreateSession();
    }

    /**
     * Returns a new TvInputInfo object if this service is responsible for {@code hardwareInfo};
     * otherwise, return {@code null}. Override to modify default behavior of ignoring all input.
     *
     * @param hardwareInfo TvInputHardwareInfo object just added.
     *
     * @hide
     */
    @SystemApi
    public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
        return null;
    }

    /**
     * Returns the input ID for {@code deviceId} if it is handled by this service;
     * otherwise, return {@code null}. Override to modify default behavior of ignoring all input.
     *
     * @hide
     */
    @SystemApi
    public String onHardwareRemoved(int deviceId) {
        return null;
    }

    /**
     * Base class for derived classes to implement to provide a TV input session.
     */
@@ -715,6 +768,32 @@ public abstract class TvInputService extends Service {
    @SuppressLint("HandlerLeak")
    private final class ServiceHandler extends Handler {
        private static final int DO_CREATE_SESSION = 1;
        private static final int DO_ADD_TV_INPUT_FROM_HARDWARE = 2;
        private static final int DO_REMOVE_TV_INPUT_FROM_HARDWARE = 3;

        private void broadcastAddTvInput(TvInputInfo inputInfo) {
            int n = mCallbacks.beginBroadcast();
            for (int i = 0; i < n; ++i) {
                try {
                    mCallbacks.getBroadcastItem(i).addTvInput(inputInfo);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error while broadcasting: " + e);
                }
            }
            mCallbacks.finishBroadcast();
        }

        private void broadcastRemoveTvInput(String inputId) {
            int n = mCallbacks.beginBroadcast();
            for (int i = 0; i < n; ++i) {
                try {
                    mCallbacks.getBroadcastItem(i).removeTvInput(inputId);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error while broadcasting: " + e);
                }
            }
            mCallbacks.finishBroadcast();
        }

        @Override
        public final void handleMessage(Message msg) {
@@ -723,8 +802,9 @@ public abstract class TvInputService extends Service {
                    SomeArgs args = (SomeArgs) msg.obj;
                    InputChannel channel = (InputChannel) args.arg1;
                    ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
                    String inputId = (String) args.arg3;
                    try {
                        Session sessionImpl = onCreateSession();
                        Session sessionImpl = onCreateSession(inputId);
                        if (sessionImpl == null) {
                            // Failed to create a session.
                            cb.onSessionCreated(null);
@@ -740,6 +820,22 @@ public abstract class TvInputService extends Service {
                    args.recycle();
                    return;
                }
                case DO_ADD_TV_INPUT_FROM_HARDWARE: {
                    TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
                    TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
                    if (inputInfo != null) {
                        broadcastAddTvInput(inputInfo);
                    }
                    return;
                }
                case DO_REMOVE_TV_INPUT_FROM_HARDWARE: {
                    int deviceId = msg.arg1;
                    String inputId = onHardwareRemoved(deviceId);
                    if (inputId != null) {
                        broadcastRemoveTvInput(inputId);
                    }
                    return;
                }
                default: {
                    Log.w(TAG, "Unhandled message code: " + msg.what);
                    return;
+59 −21
Original line number Diff line number Diff line
@@ -20,8 +20,10 @@ import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;

import android.content.Context;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiHotplugEvent;
import android.hardware.hdmi.IHdmiDeviceEventListener;
import android.media.AudioDevicePort;
import android.media.AudioManager;
import android.media.AudioPatch;
@@ -33,7 +35,6 @@ import android.media.tv.TvInputHardwareInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvStreamConfig;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -66,27 +67,24 @@ class TvInputHardwareManager
    private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
    private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
    private final Context mContext;
    private final TvInputManagerService.Client mClient;
    private final Listener mListener;
    private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
    private final AudioManager mAudioManager;
    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
    // TODO: Should handle INACTIVE case.
    private final SparseArray<TvInputInfo> mTvInputInfoMap = new SparseArray<TvInputInfo>();
    private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();

    // Calls to mClient should happen here.
    private final HandlerThread mHandlerThread = new HandlerThread(TAG);
    private final Handler mHandler;
    // Calls to mListener should happen here.
    private final Handler mHandler = new ListenerHandler();

    private final Object mLock = new Object();

    public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
    public TvInputHardwareManager(Context context, Listener listener) {
        mContext = context;
        mClient = client;
        mListener = listener;
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        mHal.init();

        mHandlerThread.start();
        mHandler = new ClientHandler(mHandlerThread.getLooper());
    }

    public void onBootPhase(int phase) {
@@ -105,7 +103,8 @@ class TvInputHardwareManager
            connection.updateConfigsLocked(configs);
            mConnections.put(info.getDeviceId(), connection);
            buildInfoListLocked();
            // TODO: notify if necessary
            mHandler.obtainMessage(
                    ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
        }
    }

@@ -127,7 +126,8 @@ class TvInputHardwareManager
            connection.resetLocked(null, null, null, null, null);
            mConnections.remove(deviceId);
            buildInfoListLocked();
            // TODO: notify if necessary
            mHandler.obtainMessage(
                    ListenerHandler.HARDWARE_DEVICE_REMOVED, deviceId, 0).sendToTarget();
        }
    }

@@ -191,7 +191,7 @@ class TvInputHardwareManager
            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
                String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
                if (inputId != null && inputId.equals(info.getId())) {
                    mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
                    mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
                            inputId).sendToTarget();
                }
@@ -273,7 +273,7 @@ class TvInputHardwareManager
            if (inputId == null) {
                return;
            }
            mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
            mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
                    convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
        }
    }
@@ -502,20 +502,48 @@ class TvInputHardwareManager
        }
    }

    private class ClientHandler extends Handler {
        private static final int DO_SET_AVAILABLE = 1;

        ClientHandler(Looper looper) {
            super(looper);
    interface Listener {
        public void onStateChanged(String inputId, int state);
        public void onHardwareDeviceAdded(TvInputHardwareInfo info);
        public void onHardwareDeviceRemoved(int deviceId);
        public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice);
        public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice);
    }

    private class ListenerHandler extends Handler {
        private static final int STATE_CHANGED = 1;
        private static final int HARDWARE_DEVICE_ADDED = 2;
        private static final int HARDWARE_DEVICE_REMOVED = 3;
        private static final int CEC_DEVICE_ADDED = 4;
        private static final int CEC_DEVICE_REMOVED = 5;

        @Override
        public final void handleMessage(Message msg) {
            switch (msg.what) {
                case DO_SET_AVAILABLE: {
                case STATE_CHANGED: {
                    String inputId = (String) msg.obj;
                    int state = msg.arg1;
                    mClient.setState(inputId, state);
                    mListener.onStateChanged(inputId, state);
                    break;
                }
                case HARDWARE_DEVICE_ADDED: {
                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
                    mListener.onHardwareDeviceAdded(info);
                    break;
                }
                case HARDWARE_DEVICE_REMOVED: {
                    int deviceId = msg.arg1;
                    mListener.onHardwareDeviceRemoved(deviceId);
                    break;
                }
                case CEC_DEVICE_ADDED: {
                    HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
                    mListener.onHdmiCecDeviceAdded(info);
                    break;
                }
                case CEC_DEVICE_REMOVED: {
                    HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
                    mListener.onHdmiCecDeviceRemoved(info);
                    break;
                }
                default: {
@@ -525,4 +553,14 @@ class TvInputHardwareManager
            }
        }
    }

    private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
        @Override
        public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) {
            mHandler.obtainMessage(
                    activated ? ListenerHandler.CEC_DEVICE_ADDED
                    : ListenerHandler.CEC_DEVICE_REMOVED,
                    0, 0, deviceInfo).sendToTarget();
        }
    }
}
Loading