Loading core/java/android/hardware/display/DisplayViewport.java +8 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.hardware.display; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Rect; import android.text.TextUtils; Loading Loading @@ -71,6 +72,9 @@ public final class DisplayViewport { // The ID used to uniquely identify this display. public String uniqueId; // The physical port that the associated display device is connected to. public @Nullable Byte physicalPort; public @ViewportType int type; public void copyFrom(DisplayViewport viewport) { Loading @@ -82,6 +86,7 @@ public final class DisplayViewport { deviceWidth = viewport.deviceWidth; deviceHeight = viewport.deviceHeight; uniqueId = viewport.uniqueId; physicalPort = viewport.physicalPort; type = viewport.type; } Loading Loading @@ -113,6 +118,7 @@ public final class DisplayViewport { && deviceWidth == other.deviceWidth && deviceHeight == other.deviceHeight && TextUtils.equals(uniqueId, other.uniqueId) && physicalPort == other.physicalPort && type == other.type; } Loading @@ -128,6 +134,7 @@ public final class DisplayViewport { result += prime * result + deviceWidth; result += prime * result + deviceHeight; result += prime * result + uniqueId.hashCode(); result += prime * result + physicalPort; result += prime * result + type; return result; } Loading @@ -139,6 +146,7 @@ public final class DisplayViewport { + ", valid=" + valid + ", displayId=" + displayId + ", uniqueId='" + uniqueId + "'" + ", physicalPort=" + physicalPort + ", orientation=" + orientation + ", logicalFrame=" + logicalFrame + ", physicalFrame=" + physicalFrame Loading core/jni/android_hardware_display_DisplayViewport.cpp +13 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ static struct { jfieldID deviceWidth; jfieldID deviceHeight; jfieldID uniqueId; jfieldID physicalPort; jfieldID type; } gDisplayViewportClassInfo; Loading @@ -54,6 +55,9 @@ static struct { status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject viewportObj, DisplayViewport* viewport) { static const jclass byteClass = FindClassOrDie(env, "java/lang/Byte"); static const jmethodID byteValue = env->GetMethodID(byteClass, "byteValue", "()B"); viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId); viewport->orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation); viewport->deviceWidth = env->GetIntField(viewportObj, gDisplayViewportClassInfo.deviceWidth); Loading @@ -65,6 +69,12 @@ status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject viewport->uniqueId = ScopedUtfChars(env, uniqueId).c_str(); } viewport->physicalPort = std::nullopt; jobject physicalPort = env->GetObjectField(viewportObj, gDisplayViewportClassInfo.physicalPort); if (physicalPort != nullptr) { viewport->physicalPort = std::make_optional(env->CallByteMethod(physicalPort, byteValue)); } viewport->type = static_cast<ViewportType>(env->GetIntField(viewportObj, gDisplayViewportClassInfo.type)); Loading Loading @@ -112,6 +122,9 @@ int register_android_hardware_display_DisplayViewport(JNIEnv* env) { gDisplayViewportClassInfo.uniqueId = GetFieldIDOrDie(env, gDisplayViewportClassInfo.clazz, "uniqueId", "Ljava/lang/String;"); gDisplayViewportClassInfo.physicalPort = GetFieldIDOrDie(env, gDisplayViewportClassInfo.clazz, "physicalPort", "Ljava/lang/Byte;"); gDisplayViewportClassInfo.type = GetFieldIDOrDie(env, gDisplayViewportClassInfo.clazz, "type", "I"); Loading services/core/java/com/android/server/display/DisplayDevice.java +2 −0 Original line number Diff line number Diff line Loading @@ -225,6 +225,8 @@ abstract class DisplayDevice { viewport.deviceHeight = isRotated ? info.width : info.height; viewport.uniqueId = info.uniqueId; // TODO(b/112898898) Use an actual port here. viewport.physicalPort = null; } /** Loading services/core/java/com/android/server/input/ConfigurationProcessor.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.input; import android.text.TextUtils; import android.util.Pair; import android.util.Slog; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; class ConfigurationProcessor { private static final String TAG = "ConfigurationProcessor"; static List<String> processExcludedDeviceNames(InputStream xml) throws Exception { List<String> names = new ArrayList<>(); try (InputStreamReader confReader = new InputStreamReader(xml)) { XmlPullParser parser = Xml.newPullParser(); parser.setInput(confReader); XmlUtils.beginDocument(parser, "devices"); while (true) { XmlUtils.nextElement(parser); if (!"device".equals(parser.getName())) { break; } String name = parser.getAttributeValue(null, "name"); if (name != null) { names.add(name); } } } return names; } /** * Parse the configuration for input port associations. * * Configuration format: * <code> * <ports> * <port display="0" input="usb-xhci-hcd.0.auto-1.4.3/input0" /> * <port display="1" input="usb-xhci-hcd.0.auto-1.4.2/input0" /> * </ports> * </code> * * In this example, any input device that has physical port of * "usb-xhci-hcd.0.auto-1.4.3/input0" will be associated with a display * that has the physical port "0". If such a display does not exist, the input device * will be disabled and no input events will be dispatched from that input device until a * matching display appears. Likewise, an input device that has port "..1.4.2.." will have * its input events forwarded to a display that has physical port of "1". * * Note: display port must be a numeric value, and this is checked at runtime for validity. * At the same time, it is specified as a string for simplicity. * * Note: do not confuse "display id" with "display port". * The "display port" is the physical port on which the display is connected. This could * be something like HDMI0, HDMI1, etc. For virtual displays, "display port" will be null. * The "display id" is a way to identify a particular display, and is not a stable API. * All displays, including virtual ones, will have a display id. * * Return the pairs of associations. The first item in the pair is the input port, * the second item in the pair is the display port. */ @VisibleForTesting static List<Pair<String, String>> processInputPortAssociations(InputStream xml) throws Exception { List<Pair<String, String>> associations = new ArrayList<>(); try (InputStreamReader confReader = new InputStreamReader(xml)) { XmlPullParser parser = Xml.newPullParser(); parser.setInput(confReader); XmlUtils.beginDocument(parser, "ports"); while (true) { XmlUtils.nextElement(parser); String entryName = parser.getName(); if (!"port".equals(entryName)) { break; } String inputPort = parser.getAttributeValue(null, "input"); String displayPort = parser.getAttributeValue(null, "display"); if (TextUtils.isEmpty(inputPort) || TextUtils.isEmpty(displayPort)) { // This is likely an error by an OEM during device configuration Slog.wtf(TAG, "Ignoring incomplete entry"); continue; } try { Integer.parseUnsignedInt(displayPort); } catch (NumberFormatException e) { Slog.wtf(TAG, "Display port should be an integer"); continue; } associations.add(new Pair<>(inputPort, displayPort)); } } return associations; } } services/core/java/com/android/server/input/InputManagerService.java +47 −30 Original line number Diff line number Diff line Loading @@ -64,18 +64,18 @@ import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; import android.view.Display; import android.view.IInputFilter; import android.view.IInputFilterHost; import android.view.IWindow; import android.view.InputChannel; import android.view.InputApplicationHandle; import android.view.InputWindowHandle; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputWindowHandle; import android.view.KeyEvent; import android.view.PointerIcon; import android.view.Surface; Loading @@ -97,14 +97,13 @@ import com.android.server.policy.WindowManagerPolicy; import libcore.io.IoUtils; import libcore.io.Streams; import org.xmlpull.v1.XmlPullParser; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; Loading @@ -124,6 +123,7 @@ public class InputManagerService extends IInputManager.Stub static final boolean DEBUG = false; private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; private static final String PORT_ASSOCIATIONS_PATH = "etc/input-port-associations.xml"; private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1; private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2; Loading Loading @@ -1852,11 +1852,9 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. private String[] getExcludedDeviceNames() { ArrayList<String> names = new ArrayList<String>(); private static String[] getExcludedDeviceNames() { List<String> names = new ArrayList<>(); // Read partner-provided list of excluded input devices XmlPullParser parser = null; // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". final File[] baseDirs = { Environment.getRootDirectory(), Loading @@ -1864,33 +1862,52 @@ public class InputManagerService extends IInputManager.Stub }; for (File baseDir: baseDirs) { File confFile = new File(baseDir, EXCLUDED_DEVICES_PATH); FileReader confreader = null; try { confreader = new FileReader(confFile); parser = Xml.newPullParser(); parser.setInput(confreader); XmlUtils.beginDocument(parser, "devices"); while (true) { XmlUtils.nextElement(parser); if (!"device".equals(parser.getName())) { break; } String name = parser.getAttributeValue(null, "name"); if (name != null) { names.add(name); } } InputStream stream = new FileInputStream(confFile); names.addAll(ConfigurationProcessor.processExcludedDeviceNames(stream)); } catch (FileNotFoundException e) { // It's ok if the file does not exist. } catch (Exception e) { Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); } finally { try { if (confreader != null) confreader.close(); } catch (IOException e) { } Slog.e(TAG, "Could not parse '" + confFile.getAbsolutePath() + "'", e); } } return names.toArray(new String[0]); } return names.toArray(new String[names.size()]); /** * Flatten a list of pairs into a list, with value positioned directly next to the key * @return Flattened list */ private static <T> List<T> flatten(@NonNull List<Pair<T, T>> pairs) { List<T> list = new ArrayList<>(pairs.size() * 2); for (Pair<T, T> pair : pairs) { list.add(pair.first); list.add(pair.second); } return list; } /** * Ports are highly platform-specific, so only allow these to be specified in the vendor * directory. */ // Native callback private static String[] getInputPortAssociations() { File baseDir = Environment.getVendorDirectory(); File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH); try { InputStream stream = new FileInputStream(confFile); List<Pair<String, String>> associations = ConfigurationProcessor.processInputPortAssociations(stream); List<String> associationList = flatten(associations); return associationList.toArray(new String[0]); } catch (FileNotFoundException e) { // Most of the time, file will not exist, which is expected. } catch (Exception e) { Slog.e(TAG, "Could not parse '" + confFile.getAbsolutePath() + "'", e); } return new String[0]; } // Native callback. Loading Loading
core/java/android/hardware/display/DisplayViewport.java +8 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.hardware.display; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Rect; import android.text.TextUtils; Loading Loading @@ -71,6 +72,9 @@ public final class DisplayViewport { // The ID used to uniquely identify this display. public String uniqueId; // The physical port that the associated display device is connected to. public @Nullable Byte physicalPort; public @ViewportType int type; public void copyFrom(DisplayViewport viewport) { Loading @@ -82,6 +86,7 @@ public final class DisplayViewport { deviceWidth = viewport.deviceWidth; deviceHeight = viewport.deviceHeight; uniqueId = viewport.uniqueId; physicalPort = viewport.physicalPort; type = viewport.type; } Loading Loading @@ -113,6 +118,7 @@ public final class DisplayViewport { && deviceWidth == other.deviceWidth && deviceHeight == other.deviceHeight && TextUtils.equals(uniqueId, other.uniqueId) && physicalPort == other.physicalPort && type == other.type; } Loading @@ -128,6 +134,7 @@ public final class DisplayViewport { result += prime * result + deviceWidth; result += prime * result + deviceHeight; result += prime * result + uniqueId.hashCode(); result += prime * result + physicalPort; result += prime * result + type; return result; } Loading @@ -139,6 +146,7 @@ public final class DisplayViewport { + ", valid=" + valid + ", displayId=" + displayId + ", uniqueId='" + uniqueId + "'" + ", physicalPort=" + physicalPort + ", orientation=" + orientation + ", logicalFrame=" + logicalFrame + ", physicalFrame=" + physicalFrame Loading
core/jni/android_hardware_display_DisplayViewport.cpp +13 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ static struct { jfieldID deviceWidth; jfieldID deviceHeight; jfieldID uniqueId; jfieldID physicalPort; jfieldID type; } gDisplayViewportClassInfo; Loading @@ -54,6 +55,9 @@ static struct { status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject viewportObj, DisplayViewport* viewport) { static const jclass byteClass = FindClassOrDie(env, "java/lang/Byte"); static const jmethodID byteValue = env->GetMethodID(byteClass, "byteValue", "()B"); viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId); viewport->orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation); viewport->deviceWidth = env->GetIntField(viewportObj, gDisplayViewportClassInfo.deviceWidth); Loading @@ -65,6 +69,12 @@ status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject viewport->uniqueId = ScopedUtfChars(env, uniqueId).c_str(); } viewport->physicalPort = std::nullopt; jobject physicalPort = env->GetObjectField(viewportObj, gDisplayViewportClassInfo.physicalPort); if (physicalPort != nullptr) { viewport->physicalPort = std::make_optional(env->CallByteMethod(physicalPort, byteValue)); } viewport->type = static_cast<ViewportType>(env->GetIntField(viewportObj, gDisplayViewportClassInfo.type)); Loading Loading @@ -112,6 +122,9 @@ int register_android_hardware_display_DisplayViewport(JNIEnv* env) { gDisplayViewportClassInfo.uniqueId = GetFieldIDOrDie(env, gDisplayViewportClassInfo.clazz, "uniqueId", "Ljava/lang/String;"); gDisplayViewportClassInfo.physicalPort = GetFieldIDOrDie(env, gDisplayViewportClassInfo.clazz, "physicalPort", "Ljava/lang/Byte;"); gDisplayViewportClassInfo.type = GetFieldIDOrDie(env, gDisplayViewportClassInfo.clazz, "type", "I"); Loading
services/core/java/com/android/server/display/DisplayDevice.java +2 −0 Original line number Diff line number Diff line Loading @@ -225,6 +225,8 @@ abstract class DisplayDevice { viewport.deviceHeight = isRotated ? info.width : info.height; viewport.uniqueId = info.uniqueId; // TODO(b/112898898) Use an actual port here. viewport.physicalPort = null; } /** Loading
services/core/java/com/android/server/input/ConfigurationProcessor.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.input; import android.text.TextUtils; import android.util.Pair; import android.util.Slog; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; class ConfigurationProcessor { private static final String TAG = "ConfigurationProcessor"; static List<String> processExcludedDeviceNames(InputStream xml) throws Exception { List<String> names = new ArrayList<>(); try (InputStreamReader confReader = new InputStreamReader(xml)) { XmlPullParser parser = Xml.newPullParser(); parser.setInput(confReader); XmlUtils.beginDocument(parser, "devices"); while (true) { XmlUtils.nextElement(parser); if (!"device".equals(parser.getName())) { break; } String name = parser.getAttributeValue(null, "name"); if (name != null) { names.add(name); } } } return names; } /** * Parse the configuration for input port associations. * * Configuration format: * <code> * <ports> * <port display="0" input="usb-xhci-hcd.0.auto-1.4.3/input0" /> * <port display="1" input="usb-xhci-hcd.0.auto-1.4.2/input0" /> * </ports> * </code> * * In this example, any input device that has physical port of * "usb-xhci-hcd.0.auto-1.4.3/input0" will be associated with a display * that has the physical port "0". If such a display does not exist, the input device * will be disabled and no input events will be dispatched from that input device until a * matching display appears. Likewise, an input device that has port "..1.4.2.." will have * its input events forwarded to a display that has physical port of "1". * * Note: display port must be a numeric value, and this is checked at runtime for validity. * At the same time, it is specified as a string for simplicity. * * Note: do not confuse "display id" with "display port". * The "display port" is the physical port on which the display is connected. This could * be something like HDMI0, HDMI1, etc. For virtual displays, "display port" will be null. * The "display id" is a way to identify a particular display, and is not a stable API. * All displays, including virtual ones, will have a display id. * * Return the pairs of associations. The first item in the pair is the input port, * the second item in the pair is the display port. */ @VisibleForTesting static List<Pair<String, String>> processInputPortAssociations(InputStream xml) throws Exception { List<Pair<String, String>> associations = new ArrayList<>(); try (InputStreamReader confReader = new InputStreamReader(xml)) { XmlPullParser parser = Xml.newPullParser(); parser.setInput(confReader); XmlUtils.beginDocument(parser, "ports"); while (true) { XmlUtils.nextElement(parser); String entryName = parser.getName(); if (!"port".equals(entryName)) { break; } String inputPort = parser.getAttributeValue(null, "input"); String displayPort = parser.getAttributeValue(null, "display"); if (TextUtils.isEmpty(inputPort) || TextUtils.isEmpty(displayPort)) { // This is likely an error by an OEM during device configuration Slog.wtf(TAG, "Ignoring incomplete entry"); continue; } try { Integer.parseUnsignedInt(displayPort); } catch (NumberFormatException e) { Slog.wtf(TAG, "Display port should be an integer"); continue; } associations.add(new Pair<>(inputPort, displayPort)); } } return associations; } }
services/core/java/com/android/server/input/InputManagerService.java +47 −30 Original line number Diff line number Diff line Loading @@ -64,18 +64,18 @@ import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; import android.view.Display; import android.view.IInputFilter; import android.view.IInputFilterHost; import android.view.IWindow; import android.view.InputChannel; import android.view.InputApplicationHandle; import android.view.InputWindowHandle; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputWindowHandle; import android.view.KeyEvent; import android.view.PointerIcon; import android.view.Surface; Loading @@ -97,14 +97,13 @@ import com.android.server.policy.WindowManagerPolicy; import libcore.io.IoUtils; import libcore.io.Streams; import org.xmlpull.v1.XmlPullParser; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; Loading @@ -124,6 +123,7 @@ public class InputManagerService extends IInputManager.Stub static final boolean DEBUG = false; private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; private static final String PORT_ASSOCIATIONS_PATH = "etc/input-port-associations.xml"; private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1; private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2; Loading Loading @@ -1852,11 +1852,9 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. private String[] getExcludedDeviceNames() { ArrayList<String> names = new ArrayList<String>(); private static String[] getExcludedDeviceNames() { List<String> names = new ArrayList<>(); // Read partner-provided list of excluded input devices XmlPullParser parser = null; // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". final File[] baseDirs = { Environment.getRootDirectory(), Loading @@ -1864,33 +1862,52 @@ public class InputManagerService extends IInputManager.Stub }; for (File baseDir: baseDirs) { File confFile = new File(baseDir, EXCLUDED_DEVICES_PATH); FileReader confreader = null; try { confreader = new FileReader(confFile); parser = Xml.newPullParser(); parser.setInput(confreader); XmlUtils.beginDocument(parser, "devices"); while (true) { XmlUtils.nextElement(parser); if (!"device".equals(parser.getName())) { break; } String name = parser.getAttributeValue(null, "name"); if (name != null) { names.add(name); } } InputStream stream = new FileInputStream(confFile); names.addAll(ConfigurationProcessor.processExcludedDeviceNames(stream)); } catch (FileNotFoundException e) { // It's ok if the file does not exist. } catch (Exception e) { Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); } finally { try { if (confreader != null) confreader.close(); } catch (IOException e) { } Slog.e(TAG, "Could not parse '" + confFile.getAbsolutePath() + "'", e); } } return names.toArray(new String[0]); } return names.toArray(new String[names.size()]); /** * Flatten a list of pairs into a list, with value positioned directly next to the key * @return Flattened list */ private static <T> List<T> flatten(@NonNull List<Pair<T, T>> pairs) { List<T> list = new ArrayList<>(pairs.size() * 2); for (Pair<T, T> pair : pairs) { list.add(pair.first); list.add(pair.second); } return list; } /** * Ports are highly platform-specific, so only allow these to be specified in the vendor * directory. */ // Native callback private static String[] getInputPortAssociations() { File baseDir = Environment.getVendorDirectory(); File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH); try { InputStream stream = new FileInputStream(confFile); List<Pair<String, String>> associations = ConfigurationProcessor.processInputPortAssociations(stream); List<String> associationList = flatten(associations); return associationList.toArray(new String[0]); } catch (FileNotFoundException e) { // Most of the time, file will not exist, which is expected. } catch (Exception e) { Slog.e(TAG, "Could not parse '" + confFile.getAbsolutePath() + "'", e); } return new String[0]; } // Native callback. Loading