1 /*
2  * Copyright (C) 2018 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 
17 package com.android.server.input;
18 
19 import android.text.TextUtils;
20 import android.util.Pair;
21 import android.util.Slog;
22 import android.util.Xml;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.internal.util.XmlUtils;
26 
27 import org.xmlpull.v1.XmlPullParser;
28 
29 import java.io.InputStream;
30 import java.io.InputStreamReader;
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 
35 class ConfigurationProcessor {
36     private static final String TAG = "ConfigurationProcessor";
37 
processExcludedDeviceNames(InputStream xml)38     static List<String> processExcludedDeviceNames(InputStream xml) throws Exception {
39         List<String> names = new ArrayList<>();
40         try (InputStreamReader confReader = new InputStreamReader(xml)) {
41             XmlPullParser parser = Xml.newPullParser();
42             parser.setInput(confReader);
43             XmlUtils.beginDocument(parser, "devices");
44             while (true) {
45                 XmlUtils.nextElement(parser);
46                 if (!"device".equals(parser.getName())) {
47                     break;
48                 }
49                 String name = parser.getAttributeValue(null, "name");
50                 if (name != null) {
51                     names.add(name);
52                 }
53             }
54         }
55         return names;
56     }
57 
58     /**
59      * Parse the configuration for input port associations.
60      *
61      * Configuration format:
62      * <code>
63      * &lt;ports>
64      *     &lt;port display="0" input="usb-xhci-hcd.0.auto-1.4.3/input0" />
65      *     &lt;port display="1" input="usb-xhci-hcd.0.auto-1.4.2/input0" />
66      * &lt;/ports>
67      * </code>
68      *
69      * In this example, any input device that has physical port of
70      * "usb-xhci-hcd.0.auto-1.4.3/input0" will be associated with a display
71      * that has the physical port "0". If such a display does not exist, the input device
72      * will be disabled and no input events will be dispatched from that input device until a
73      * matching display appears. Likewise, an input device that has port "..1.4.2.." will have
74      * its input events forwarded to a display that has physical port of "1".
75      *
76      * Note: display port must be a numeric value, and this is checked at runtime for validity.
77      * At the same time, it is specified as a string for simplicity.
78      *
79      * Note: do not confuse "display id" with "display port".
80      * The "display port" is the physical port on which the display is connected. This could
81      * be something like HDMI0, HDMI1, etc. For virtual displays, "display port" will be null.
82      * The "display id" is a way to identify a particular display, and is not a stable API.
83      * All displays, including virtual ones, will have a display id.
84      *
85      * Return the pairs of associations. The first item in the pair is the input port,
86      * the second item in the pair is the display port.
87      */
88     @VisibleForTesting
processInputPortAssociations(InputStream xml)89     static List<Pair<String, String>> processInputPortAssociations(InputStream xml)
90             throws Exception {
91         List<Pair<String, String>> associations = new ArrayList<>();
92         try (InputStreamReader confReader = new InputStreamReader(xml)) {
93             XmlPullParser parser = Xml.newPullParser();
94             parser.setInput(confReader);
95             XmlUtils.beginDocument(parser, "ports");
96 
97             while (true) {
98                 XmlUtils.nextElement(parser);
99                 String entryName = parser.getName();
100                 if (!"port".equals(entryName)) {
101                     break;
102                 }
103                 String inputPort = parser.getAttributeValue(null, "input");
104                 String displayPort = parser.getAttributeValue(null, "display");
105                 if (TextUtils.isEmpty(inputPort) || TextUtils.isEmpty(displayPort)) {
106                     // This is likely an error by an OEM during device configuration
107                     Slog.wtf(TAG, "Ignoring incomplete entry");
108                     continue;
109                 }
110                 try {
111                     Integer.parseUnsignedInt(displayPort);
112                 } catch (NumberFormatException e) {
113                     Slog.wtf(TAG, "Display port should be an integer");
114                     continue;
115                 }
116                 associations.add(new Pair<>(inputPort, displayPort));
117             }
118         }
119         return associations;
120     }
121 }
122