1/*
2 * Copyright (C) 2015 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.tv.tuner;
18
19import android.content.Context;
20import android.media.tv.TvInputManager;
21import android.os.ParcelFileDescriptor;
22import android.support.annotation.IntDef;
23import android.support.annotation.NonNull;
24import android.util.Log;
25
26import com.android.tv.common.recording.RecordingCapability;
27import com.android.tv.tuner.tvinput.TunerTvInputService;
28
29import java.lang.annotation.Retention;
30import java.lang.annotation.RetentionPolicy;
31import java.lang.reflect.InvocationTargetException;
32import java.lang.reflect.Method;
33import java.util.ArrayList;
34import java.util.Collections;
35import java.util.List;
36import java.util.Locale;
37
38/**
39 * Provides with the file descriptors to access DVB device.
40 */
41public class DvbDeviceAccessor {
42    private static final String TAG = "DvbDeviceAccessor";
43
44    @IntDef({DVB_DEVICE_DEMUX, DVB_DEVICE_DVR, DVB_DEVICE_FRONTEND})
45    @Retention(RetentionPolicy.SOURCE)
46    public @interface DvbDevice {}
47    public static final int DVB_DEVICE_DEMUX = 0; // TvInputManager.DVB_DEVICE_DEMUX;
48    public static final int DVB_DEVICE_DVR = 1; // TvInputManager.DVB_DEVICE_DVR;
49    public static final int DVB_DEVICE_FRONTEND = 2; // TvInputManager.DVB_DEVICE_FRONTEND;
50
51    private static Method sGetDvbDeviceListMethod;
52    private static Method sOpenDvbDeviceMethod;
53
54    private final TvInputManager mTvInputManager;
55
56    static {
57        try {
58            Class tvInputManagerClass = Class.forName("android.media.tv.TvInputManager");
59            Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo");
60            sGetDvbDeviceListMethod = tvInputManagerClass.getDeclaredMethod("getDvbDeviceList");
61            sGetDvbDeviceListMethod.setAccessible(true);
62            sOpenDvbDeviceMethod = tvInputManagerClass.getDeclaredMethod(
63                    "openDvbDevice", dvbDeviceInfoClass, Integer.TYPE);
64            sOpenDvbDeviceMethod.setAccessible(true);
65        } catch (ClassNotFoundException e) {
66            Log.e(TAG, "Couldn't find class", e);
67        } catch (NoSuchMethodException e) {
68            Log.e(TAG, "Couldn't find method", e);
69        }
70    }
71
72    public DvbDeviceAccessor(Context context) {
73        mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
74    }
75
76    public List<DvbDeviceInfoWrapper> getDvbDeviceList() {
77        try {
78            List<DvbDeviceInfoWrapper> wrapperList = new ArrayList<>();
79            List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager);
80            for (Object dvbDeviceInfo : dvbDeviceInfoList) {
81                wrapperList.add(new DvbDeviceInfoWrapper(dvbDeviceInfo));
82            }
83            Collections.sort(wrapperList);
84            return wrapperList;
85        } catch (IllegalAccessException e) {
86            Log.e(TAG, "Couldn't access", e);
87        } catch (InvocationTargetException e) {
88            Log.e(TAG, "Couldn't invoke", e);
89        }
90        return null;
91    }
92
93    /**
94     * Returns the number of currently connected DVB devices.
95     */
96    public int getNumOfDvbDevices() {
97        List<DvbDeviceInfoWrapper> dvbDeviceList = getDvbDeviceList();
98        return dvbDeviceList == null ? 0 : dvbDeviceList.size();
99    }
100
101    public boolean isDvbDeviceAvailable() {
102        try {
103            List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager);
104            return (!dvbDeviceInfoList.isEmpty());
105        } catch (IllegalAccessException e) {
106            Log.e(TAG, "Couldn't access", e);
107        } catch (InvocationTargetException e) {
108            Log.e(TAG, "Couldn't invoke", e);
109        }
110        return false;
111    }
112
113    public ParcelFileDescriptor openDvbDevice(DvbDeviceInfoWrapper deviceInfo,
114            @DvbDevice int device) {
115        try {
116            return (ParcelFileDescriptor) sOpenDvbDeviceMethod.invoke(
117                    mTvInputManager, deviceInfo.getDvbDeviceInfo(), device);
118        } catch (IllegalAccessException e) {
119            Log.e(TAG, "Couldn't access", e);
120        } catch (InvocationTargetException e) {
121            Log.e(TAG, "Couldn't invoke", e);
122        }
123        return null;
124    }
125
126    /**
127     * Returns the current recording capability for USB tuner.
128     * @param inputId the input id to use.
129     */
130    public RecordingCapability getRecordingCapability(String inputId) {
131        List<DvbDeviceInfoWrapper> deviceList = getDvbDeviceList();
132        // TODO(DVR) implement accurate capabilities and updating values when needed.
133        return RecordingCapability.builder()
134                .setInputId(inputId)
135                .setMaxConcurrentPlayingSessions(1)
136                .setMaxConcurrentTunedSessions(deviceList.size())
137                .setMaxConcurrentSessionsOfAllTypes(deviceList.size() + 1)
138                .build();
139    }
140
141    public static class DvbDeviceInfoWrapper implements Comparable<DvbDeviceInfoWrapper> {
142        private static Method sGetAdapterIdMethod;
143        private static Method sGetDeviceIdMethod;
144        private final Object mDvbDeviceInfo;
145        private final int mAdapterId;
146        private final int mDeviceId;
147        private final long mId;
148
149        static {
150            try {
151                Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo");
152                sGetAdapterIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getAdapterId");
153                sGetAdapterIdMethod.setAccessible(true);
154                sGetDeviceIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getDeviceId");
155                sGetDeviceIdMethod.setAccessible(true);
156            } catch (ClassNotFoundException e) {
157                Log.e(TAG, "Couldn't find class", e);
158            } catch (NoSuchMethodException e) {
159                Log.e(TAG, "Couldn't find method", e);
160            }
161        }
162
163        public DvbDeviceInfoWrapper(Object dvbDeviceInfo) {
164            mDvbDeviceInfo = dvbDeviceInfo;
165            mAdapterId = initAdapterId();
166            mDeviceId = initDeviceId();
167            mId = (((long) getAdapterId()) << 32) | (getDeviceId() & 0xffffffffL);
168        }
169
170        public long getId() {
171            return mId;
172        }
173
174        public int getAdapterId() {
175            return mAdapterId;
176        }
177
178        private int initAdapterId() {
179            try {
180                return (int) sGetAdapterIdMethod.invoke(mDvbDeviceInfo);
181            } catch (InvocationTargetException e) {
182                Log.e(TAG, "Couldn't invoke", e);
183            } catch (IllegalAccessException e) {
184                Log.e(TAG, "Couldn't access", e);
185            }
186            return -1;
187        }
188
189        public int getDeviceId() {
190            return mDeviceId;
191        }
192
193        private int initDeviceId() {
194            try {
195                return (int) sGetDeviceIdMethod.invoke(mDvbDeviceInfo);
196            } catch (InvocationTargetException e) {
197                Log.e(TAG, "Couldn't invoke", e);
198            } catch (IllegalAccessException e) {
199                Log.e(TAG, "Couldn't access", e);
200            }
201            return -1;
202        }
203
204        public Object getDvbDeviceInfo() {
205            return mDvbDeviceInfo;
206        }
207
208        @Override
209        public int compareTo(@NonNull DvbDeviceInfoWrapper another) {
210            if (getAdapterId() != another.getAdapterId()) {
211                return getAdapterId() - another.getAdapterId();
212            }
213            return getDeviceId() - another.getDeviceId();
214        }
215
216        @Override
217        public String toString() {
218            return String.format(Locale.US, "DvbDeviceInfo {adapterId: %d, deviceId: %d}",
219                    getAdapterId(),
220                    getDeviceId());
221        }
222    }
223}
224