1/*
2 * Copyright 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.tv;
18
19import android.media.tv.TvInputHardwareInfo;
20import android.media.tv.TvStreamConfig;
21import android.os.Handler;
22import android.os.Message;
23import android.os.MessageQueue;
24import android.util.Slog;
25import android.util.SparseArray;
26import android.util.SparseIntArray;
27import android.view.Surface;
28
29import java.util.LinkedList;
30import java.util.Queue;
31
32/**
33 * Provides access to the low-level TV input hardware abstraction layer.
34 */
35final class TvInputHal implements Handler.Callback {
36    private final static boolean DEBUG = false;
37    private final static String TAG = TvInputHal.class.getSimpleName();
38
39    public final static int SUCCESS = 0;
40    public final static int ERROR_NO_INIT = -1;
41    public final static int ERROR_STALE_CONFIG = -2;
42    public final static int ERROR_UNKNOWN = -3;
43
44    public static final int EVENT_DEVICE_AVAILABLE = 1;
45    public static final int EVENT_DEVICE_UNAVAILABLE = 2;
46    public static final int EVENT_STREAM_CONFIGURATION_CHANGED = 3;
47    public static final int EVENT_FIRST_FRAME_CAPTURED = 4;
48
49    public interface Callback {
50        void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs);
51        void onDeviceUnavailable(int deviceId);
52        void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs);
53        void onFirstFrameCaptured(int deviceId, int streamId);
54    }
55
56    private native long nativeOpen(MessageQueue queue);
57
58    private static native int nativeAddOrUpdateStream(long ptr, int deviceId, int streamId,
59            Surface surface);
60    private static native int nativeRemoveStream(long ptr, int deviceId, int streamId);
61    private static native TvStreamConfig[] nativeGetStreamConfigs(long ptr, int deviceId,
62            int generation);
63    private static native void nativeClose(long ptr);
64
65    private final Object mLock = new Object();
66    private long mPtr = 0;
67    private final Callback mCallback;
68    private final Handler mHandler;
69    private final SparseIntArray mStreamConfigGenerations = new SparseIntArray();
70    private final SparseArray<TvStreamConfig[]> mStreamConfigs = new SparseArray<>();
71
72    public TvInputHal(Callback callback) {
73        mCallback = callback;
74        mHandler = new Handler(this);
75    }
76
77    public void init() {
78        synchronized (mLock) {
79            mPtr = nativeOpen(mHandler.getLooper().getQueue());
80        }
81    }
82
83    public int addOrUpdateStream(int deviceId, Surface surface, TvStreamConfig streamConfig) {
84        synchronized (mLock) {
85            if (mPtr == 0) {
86                return ERROR_NO_INIT;
87            }
88            int generation = mStreamConfigGenerations.get(deviceId, 0);
89            if (generation != streamConfig.getGeneration()) {
90                return ERROR_STALE_CONFIG;
91            }
92            if (nativeAddOrUpdateStream(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) {
93                return SUCCESS;
94            } else {
95                return ERROR_UNKNOWN;
96            }
97        }
98    }
99
100    public int removeStream(int deviceId, TvStreamConfig streamConfig) {
101        synchronized (mLock) {
102            if (mPtr == 0) {
103                return ERROR_NO_INIT;
104            }
105            int generation = mStreamConfigGenerations.get(deviceId, 0);
106            if (generation != streamConfig.getGeneration()) {
107                return ERROR_STALE_CONFIG;
108            }
109            if (nativeRemoveStream(mPtr, deviceId, streamConfig.getStreamId()) == 0) {
110                return SUCCESS;
111            } else {
112                return ERROR_UNKNOWN;
113            }
114        }
115    }
116
117    public void close() {
118        synchronized (mLock) {
119            if (mPtr != 0L) {
120                nativeClose(mPtr);
121            }
122        }
123    }
124
125    private void retrieveStreamConfigsLocked(int deviceId) {
126        int generation = mStreamConfigGenerations.get(deviceId, 0) + 1;
127        mStreamConfigs.put(deviceId, nativeGetStreamConfigs(mPtr, deviceId, generation));
128        mStreamConfigGenerations.put(deviceId, generation);
129    }
130
131    // Called from native
132    private void deviceAvailableFromNative(TvInputHardwareInfo info) {
133        if (DEBUG) {
134            Slog.d(TAG, "deviceAvailableFromNative: info = " + info);
135        }
136        mHandler.obtainMessage(EVENT_DEVICE_AVAILABLE, info).sendToTarget();
137    }
138
139    private void deviceUnavailableFromNative(int deviceId) {
140        mHandler.obtainMessage(EVENT_DEVICE_UNAVAILABLE, deviceId, 0).sendToTarget();
141    }
142
143    private void streamConfigsChangedFromNative(int deviceId) {
144        mHandler.obtainMessage(EVENT_STREAM_CONFIGURATION_CHANGED, deviceId, 0).sendToTarget();
145    }
146
147    private void firstFrameCapturedFromNative(int deviceId, int streamId) {
148        mHandler.sendMessage(
149                mHandler.obtainMessage(EVENT_STREAM_CONFIGURATION_CHANGED, deviceId, streamId));
150    }
151
152    // Handler.Callback implementation
153
154    private final Queue<Message> mPendingMessageQueue = new LinkedList<>();
155
156    @Override
157    public boolean handleMessage(Message msg) {
158        switch (msg.what) {
159            case EVENT_DEVICE_AVAILABLE: {
160                TvStreamConfig[] configs;
161                TvInputHardwareInfo info = (TvInputHardwareInfo)msg.obj;
162                synchronized (mLock) {
163                    retrieveStreamConfigsLocked(info.getDeviceId());
164                    if (DEBUG) {
165                        Slog.d(TAG, "EVENT_DEVICE_AVAILABLE: info = " + info);
166                    }
167                    configs = mStreamConfigs.get(info.getDeviceId());
168                }
169                mCallback.onDeviceAvailable(info, configs);
170                break;
171            }
172
173            case EVENT_DEVICE_UNAVAILABLE: {
174                int deviceId = msg.arg1;
175                if (DEBUG) {
176                    Slog.d(TAG, "EVENT_DEVICE_UNAVAILABLE: deviceId = " + deviceId);
177                }
178                mCallback.onDeviceUnavailable(deviceId);
179                break;
180            }
181
182            case EVENT_STREAM_CONFIGURATION_CHANGED: {
183                TvStreamConfig[] configs;
184                int deviceId = msg.arg1;
185                synchronized (mLock) {
186                    if (DEBUG) {
187                        Slog.d(TAG, "EVENT_STREAM_CONFIGURATION_CHANGED: deviceId = " + deviceId);
188                    }
189                    retrieveStreamConfigsLocked(deviceId);
190                    configs = mStreamConfigs.get(deviceId);
191                }
192                mCallback.onStreamConfigurationChanged(deviceId, configs);
193                break;
194            }
195
196            case EVENT_FIRST_FRAME_CAPTURED: {
197                int deviceId = msg.arg1;
198                int streamId = msg.arg2;
199                mCallback.onFirstFrameCaptured(deviceId, streamId);
200                break;
201            }
202
203            default:
204                Slog.e(TAG, "Unknown event: " + msg);
205                return false;
206        }
207
208        return true;
209    }
210}
211