TIF: Add DVB device handling to TvInputManagerService

Added an API to pass an open file descriptor of DVB devices and
addressed the security issue of setting the permissions on DVB devices
to 0666.

Bug: 20436120
Change-Id: I4649e76084f3356ec22b7e776fb87c6a8fdc00d6
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index edff537..5977a01 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1107,6 +1107,11 @@
     <permission android:name="android.permission.CAPTURE_TV_INPUT"
         android:protectionLevel="signatureOrSystem" />
 
+    <!-- @hide Allows TvInputService to access DVB device.
+   <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.DVB_DEVICE"
+        android:protectionLevel="signatureOrSystem" />
+
     <!-- @hide Allows enabling/disabling OEM unlock
    <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.OEM_UNLOCK_STATE"
diff --git a/media/java/android/media/tv/DvbDeviceInfo.aidl b/media/java/android/media/tv/DvbDeviceInfo.aidl
new file mode 100644
index 0000000..4851050
--- /dev/null
+++ b/media/java/android/media/tv/DvbDeviceInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ *
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+parcelable DvbDeviceInfo;
\ No newline at end of file
diff --git a/media/java/android/media/tv/DvbDeviceInfo.java b/media/java/android/media/tv/DvbDeviceInfo.java
new file mode 100644
index 0000000..1885a34
--- /dev/null
+++ b/media/java/android/media/tv/DvbDeviceInfo.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.media.tv.TvInputManager;
+import android.util.Log;
+
+import java.lang.IllegalArgumentException;
+
+/**
+ * Simple container for information about DVB device.
+ * Not for third-party developers.
+ *
+ * @hide
+ */
+public final class DvbDeviceInfo implements Parcelable {
+    static final String TAG = "DvbDeviceInfo";
+
+    public static final Parcelable.Creator<DvbDeviceInfo> CREATOR =
+            new Parcelable.Creator<DvbDeviceInfo>() {
+                @Override
+                public DvbDeviceInfo createFromParcel(Parcel source) {
+                    try {
+                        return new DvbDeviceInfo(source);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Exception creating DvbDeviceInfo from parcel", e);
+                        return null;
+                    }
+                }
+
+                @Override
+                public DvbDeviceInfo[] newArray(int size) {
+                    return new DvbDeviceInfo[size];
+                }
+            };
+
+    private final int mAdapterId;
+    private final int mDeviceId;
+
+    private DvbDeviceInfo(Parcel source) {
+        mAdapterId = source.readInt();
+        mDeviceId = source.readInt();
+    }
+
+    /**
+     * Constructs a new {@link DvbDeviceInfo} with the given adapter ID and device ID.
+     */
+    public DvbDeviceInfo(int adapterId, int deviceId) {
+        mAdapterId = adapterId;
+        mDeviceId = deviceId;
+    }
+
+    /**
+     * Returns the adapter ID of DVB device, in terms of enumerating the DVB device adapters
+     * installed in the system. The adapter ID counts from zero.
+     */
+    public int getAdapterId() {
+        return mAdapterId;
+    }
+
+    /**
+     * Returns the device ID of DVB device, in terms of enumerating the DVB devices attached to
+     * the same device adapter. The device ID counts from zero.
+     */
+    public int getDeviceId() {
+        return mDeviceId;
+    }
+
+    // Parcelable
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mAdapterId);
+        dest.writeInt(mDeviceId);
+    }
+}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 078fb2f..6fe5dbb 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.graphics.Rect;
+import android.media.tv.DvbDeviceInfo;
 import android.media.tv.ITvInputClient;
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
@@ -29,6 +30,7 @@
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.view.Surface;
 
 /**
@@ -91,4 +93,8 @@
     boolean captureFrame(in String inputId, in Surface surface, in TvStreamConfig config,
             int userId);
     boolean isSingleSessionActive(int userId);
+
+    // For DVB device binding
+    List<DvbDeviceInfo> getDvbDeviceList();
+    ParcelFileDescriptor openDvbDevice(in DvbDeviceInfo info, int device);
 }
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 705aa3d6..e74860e 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -21,12 +21,14 @@
 import android.annotation.SystemApi;
 import android.graphics.Rect;
 import android.media.MediaPlayer;
+import android.media.tv.DvbDeviceInfo;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -42,6 +44,7 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.lang.IllegalArgumentException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -55,6 +58,25 @@
 public final class TvInputManager {
     private static final String TAG = "TvInputManager";
 
+    static final int DVB_DEVICE_START = 0;
+    static final int DVB_DEVICE_END = 2;
+
+    /**
+     * A demux device of DVB API for controlling the filters of DVB hardware/software.
+     * @hide
+     */
+    public static final int DVB_DEVICE_DEMUX = DVB_DEVICE_START;
+     /**
+     * A DVR device of DVB API for reading transport streams.
+     * @hide
+     */
+    public static final int DVB_DEVICE_DVR = 1;
+    /**
+     * A frontend device of DVB API for controlling the tuner and DVB demodulator hardware.
+     * @hide
+     */
+    public static final int DVB_DEVICE_FRONTEND = DVB_DEVICE_END;
+
     static final int VIDEO_UNAVAILABLE_REASON_START = 0;
     static final int VIDEO_UNAVAILABLE_REASON_END = 4;
 
@@ -1258,6 +1280,43 @@
     }
 
     /**
+     * Returns the list of currently available DVB devices on the system.
+     *
+     * @return the list of {@link DvbDeviceInfo} objects representing available DVB devices.
+     * @hide
+     */
+    public List<DvbDeviceInfo> getDvbDeviceList() {
+        try {
+            return mService.getDvbDeviceList();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Returns a {@link ParcelFileDescriptor} of a specified DVB device for a given
+     * {@link DvbDeviceInfo}
+     *
+     * @param info A {@link DvbDeviceInfo} to open a DVB device.
+     * @param device A DVB device. The DVB device can be {@link DVB_DEVICE_DEMUX},
+     *            {@link DVB_DEVICE_DVR} or {@link DVB_DEVICE_FRONTEND}.
+     * @return a {@link ParcelFileDescriptor} of a specified DVB device for a given
+     *            {@link DvbDeviceInfo}, or {@code null} if the given {@link DvbDeviceInfo} was
+     *            invalid or the specified DVB device was busy with a previous request.
+     * @hide
+     */
+    public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device) {
+        try {
+            if (DVB_DEVICE_START > device || DVB_DEVICE_END < device) {
+                throw new IllegalArgumentException("Invalid DVB device: " + device);
+            }
+            return mService.openDvbDevice(info, device);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
      * The Session provides the per-session functionality of TV inputs.
      * @hide
      */
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e649e48..a869c20 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -39,6 +39,7 @@
 import android.graphics.Rect;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.tv.DvbDeviceInfo;
 import android.media.tv.ITvInputClient;
 import android.media.tv.ITvInputHardware;
 import android.media.tv.ITvInputHardwareCallback;
@@ -64,6 +65,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -80,23 +82,34 @@
 
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.IllegalArgumentException;
+import java.lang.Integer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /** This class provides a system service that manages television inputs. */
 public final class TvInputManagerService extends SystemService {
     private static final boolean DEBUG = false;
     private static final String TAG = "TvInputManagerService";
 
+    // Pattern for selecting the DVB frontend devices from the list of files in the /dev directory.
+    private static final Pattern sFrontEndDevicePattern =
+            Pattern.compile("^dvb([0-9]+)\\.frontend([0-9]+)$");
+
     private final Context mContext;
     private final TvInputHardwareManager mTvInputHardwareManager;
 
@@ -1507,6 +1520,74 @@
         }
 
         @Override
+        public List<DvbDeviceInfo> getDvbDeviceList() throws RemoteException {
+            if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires DVB_DEVICE permission");
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                ArrayList<DvbDeviceInfo> deviceInfos = new ArrayList<>();
+                File devDirectory = new File("/dev");
+                for (String fileName : devDirectory.list()) {
+                    Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
+                    if (matcher.find()) {
+                        int adapterId = Integer.parseInt(matcher.group(1));
+                        int deviceId = Integer.parseInt(matcher.group(2));
+                        deviceInfos.add(new DvbDeviceInfo(adapterId, deviceId));
+                    }
+                }
+                return Collections.unmodifiableList(deviceInfos);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device)
+                throws RemoteException {
+            if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires DVB_DEVICE permission");
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                String deviceFileName;
+                switch (device) {
+                    case TvInputManager.DVB_DEVICE_DEMUX:
+                        deviceFileName = String.format("/dev/dvb%d.demux%d", info.getAdapterId(),
+                                info.getDeviceId());
+                        break;
+                    case TvInputManager.DVB_DEVICE_DVR:
+                        deviceFileName = String.format("/dev/dvb%d.dvr%d", info.getAdapterId(),
+                                info.getDeviceId());
+                        break;
+                    case TvInputManager.DVB_DEVICE_FRONTEND:
+                        deviceFileName = String.format("/dev/dvb%d.frontend%d", info.getAdapterId(),
+                                info.getDeviceId());
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Invalid DVB device: " + device);
+                }
+                try {
+                    // The DVB frontend device only needs to be opened in read/write mode, which
+                    // allows performing tuning operations. The DVB demux and DVR device are enough
+                    // to be opened in read only mode.
+                    return ParcelFileDescriptor.open(new File(deviceFileName),
+                            TvInputManager.DVB_DEVICE_FRONTEND == device
+                                    ? ParcelFileDescriptor.MODE_READ_WRITE
+                                    : ParcelFileDescriptor.MODE_READ_ONLY);
+                } catch (FileNotFoundException e) {
+                    return null;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
                 throws RemoteException {
             if (mContext.checkCallingPermission(