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