diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java index 31a30968cb..ecccda588a 100644 --- a/core/java/android/net/EthernetManager.java +++ b/core/java/android/net/EthernetManager.java @@ -18,9 +18,6 @@ package android.net; import android.annotation.SystemService; import android.content.Context; -import android.net.IEthernetManager; -import android.net.IEthernetServiceListener; -import android.net.IpConfiguration; import android.os.Handler; import android.os.Message; import android.os.RemoteException; @@ -45,18 +42,18 @@ public class EthernetManager { if (msg.what == MSG_AVAILABILITY_CHANGED) { boolean isAvailable = (msg.arg1 == 1); for (Listener listener : mListeners) { - listener.onAvailabilityChanged(isAvailable); + listener.onAvailabilityChanged((String) msg.obj, isAvailable); } } } }; - private final ArrayList mListeners = new ArrayList(); + private final ArrayList mListeners = new ArrayList<>(); private final IEthernetServiceListener.Stub mServiceListener = new IEthernetServiceListener.Stub() { @Override - public void onAvailabilityChanged(boolean isAvailable) { + public void onAvailabilityChanged(String iface, boolean isAvailable) { mHandler.obtainMessage( - MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, null).sendToTarget(); + MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, iface).sendToTarget(); } }; @@ -66,9 +63,10 @@ public class EthernetManager { public interface Listener { /** * Called when Ethernet port's availability is changed. - * @param isAvailable {@code true} if one or more Ethernet port exists. + * @param iface Ethernet interface name + * @param isAvailable {@code true} if Ethernet port exists. */ - public void onAvailabilityChanged(boolean isAvailable); + void onAvailabilityChanged(String iface, boolean isAvailable); } /** @@ -86,9 +84,9 @@ public class EthernetManager { * Get Ethernet configuration. * @return the Ethernet Configuration, contained in {@link IpConfiguration}. */ - public IpConfiguration getConfiguration() { + public IpConfiguration getConfiguration(String iface) { try { - return mService.getConfiguration(); + return mService.getConfiguration(iface); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -97,21 +95,29 @@ public class EthernetManager { /** * Set Ethernet configuration. */ - public void setConfiguration(IpConfiguration config) { + public void setConfiguration(String iface, IpConfiguration config) { try { - mService.setConfiguration(config); + mService.setConfiguration(iface, config); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Indicates whether the system currently has one or more - * Ethernet interfaces. + * Indicates whether the system currently has one or more Ethernet interfaces. */ public boolean isAvailable() { + return getAvailableInterfaces().length > 0; + } + + /** + * Indicates whether the system has given interface. + * + * @param iface Ethernet interface name + */ + public boolean isAvailable(String iface) { try { - return mService.isAvailable(); + return mService.isAvailable(iface); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -136,6 +142,17 @@ public class EthernetManager { } } + /** + * Returns an array of available Ethernet interface names. + */ + public String[] getAvailableInterfaces() { + try { + return mService.getAvailableInterfaces(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + /** * Removes a listener. * @param listener A {@link Listener} to remove. diff --git a/core/java/android/net/IEthernetManager.aidl b/core/java/android/net/IEthernetManager.aidl index 7a92eb955a..94960b51d3 100644 --- a/core/java/android/net/IEthernetManager.aidl +++ b/core/java/android/net/IEthernetManager.aidl @@ -26,9 +26,10 @@ import android.net.IEthernetServiceListener; /** {@hide} */ interface IEthernetManager { - IpConfiguration getConfiguration(); - void setConfiguration(in IpConfiguration config); - boolean isAvailable(); + String[] getAvailableInterfaces(); + IpConfiguration getConfiguration(String iface); + void setConfiguration(String iface, in IpConfiguration config); + boolean isAvailable(String iface); void addListener(in IEthernetServiceListener listener); void removeListener(in IEthernetServiceListener listener); } diff --git a/core/java/android/net/IEthernetServiceListener.aidl b/core/java/android/net/IEthernetServiceListener.aidl index 356690e8f7..782fa19d9d 100644 --- a/core/java/android/net/IEthernetServiceListener.aidl +++ b/core/java/android/net/IEthernetServiceListener.aidl @@ -19,5 +19,5 @@ package android.net; /** @hide */ oneway interface IEthernetServiceListener { - void onAvailabilityChanged(boolean isAvailable); + void onAvailabilityChanged(String iface, boolean isAvailable); } diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java index 4d56468f2f..e3e02e32ad 100644 --- a/services/core/java/com/android/server/net/IpConfigStore.java +++ b/services/core/java/com/android/server/net/IpConfigStore.java @@ -24,11 +24,11 @@ import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.StaticIpConfiguration; +import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.net.DelayedDiskWrite; import java.io.BufferedInputStream; import java.io.DataInputStream; @@ -38,8 +38,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.net.InetAddress; import java.net.Inet4Address; +import java.net.InetAddress; public class IpConfigStore { private static final String TAG = "IpConfigStore"; @@ -60,7 +60,7 @@ public class IpConfigStore { protected static final String EXCLUSION_LIST_KEY = "exclusionList"; protected static final String EOS = "eos"; - protected static final int IPCONFIG_FILE_VERSION = 2; + protected static final int IPCONFIG_FILE_VERSION = 3; public IpConfigStore(DelayedDiskWrite writer) { mWriter = writer; @@ -70,9 +70,14 @@ public class IpConfigStore { this(new DelayedDiskWrite()); } + private static boolean writeConfig(DataOutputStream out, String configKey, + IpConfiguration config) throws IOException { + return writeConfig(out, configKey, config, IPCONFIG_FILE_VERSION); + } + @VisibleForTesting - public static boolean writeConfig(DataOutputStream out, int configKey, - IpConfiguration config) throws IOException { + public static boolean writeConfig(DataOutputStream out, String configKey, + IpConfiguration config, int version) throws IOException { boolean written = false; try { @@ -153,7 +158,11 @@ public class IpConfigStore { if (written) { out.writeUTF(ID_KEY); - out.writeInt(configKey); + if (version < 3) { + out.writeInt(Integer.valueOf(configKey)); + } else { + out.writeUTF(configKey); + } } } catch (NullPointerException e) { loge("Failure in writing " + config + e); @@ -163,18 +172,47 @@ public class IpConfigStore { return written; } - public void writeIpAndProxyConfigurations(String filePath, + /** + * @Deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead. + * New method uses string as network identifier which could be interface name or MAC address or + * other token. + */ + @Deprecated + public void writeIpAndProxyConfigurationsToFile(String filePath, final SparseArray networks) { - mWriter.write(filePath, new DelayedDiskWrite.Writer() { - public void onWriteCalled(DataOutputStream out) throws IOException{ - out.writeInt(IPCONFIG_FILE_VERSION); - for(int i = 0; i < networks.size(); i++) { - writeConfig(out, networks.keyAt(i), networks.valueAt(i)); - } + mWriter.write(filePath, out -> { + out.writeInt(IPCONFIG_FILE_VERSION); + for(int i = 0; i < networks.size(); i++) { + writeConfig(out, String.valueOf(networks.keyAt(i)), networks.valueAt(i)); } }); } + public void writeIpConfigurations(String filePath, + ArrayMap networks) { + mWriter.write(filePath, out -> { + out.writeInt(IPCONFIG_FILE_VERSION); + for(int i = 0; i < networks.size(); i++) { + writeConfig(out, networks.keyAt(i), networks.valueAt(i)); + } + }); + } + + public static ArrayMap readIpConfigurations(String filePath) { + BufferedInputStream bufferedInputStream; + try { + bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath)); + } catch (FileNotFoundException e) { + // Return an empty array here because callers expect an empty array when the file is + // not present. + loge("Error opening configuration file: " + e); + return new ArrayMap<>(0); + } + return readIpConfigurations(bufferedInputStream); + } + + /** @Deprecated use {@link #readIpConfigurations(String)} */ + @Deprecated public static SparseArray readIpAndProxyConfigurations(String filePath) { BufferedInputStream bufferedInputStream; try { @@ -188,21 +226,40 @@ public class IpConfigStore { return readIpAndProxyConfigurations(bufferedInputStream); } + /** @Deprecated use {@link #readIpConfigurations(InputStream)} */ + @Deprecated public static SparseArray readIpAndProxyConfigurations( InputStream inputStream) { - SparseArray networks = new SparseArray(); + ArrayMap networks = readIpConfigurations(inputStream); + if (networks == null) { + return null; + } + + SparseArray networksById = new SparseArray<>(); + for (int i = 0; i < networks.size(); i++) { + int id = Integer.valueOf(networks.keyAt(i)); + networksById.put(id, networks.valueAt(i)); + } + + return networksById; + } + + /** Returns a map of network identity token and {@link IpConfiguration}. */ + public static ArrayMap readIpConfigurations( + InputStream inputStream) { + ArrayMap networks = new ArrayMap<>(); DataInputStream in = null; try { in = new DataInputStream(inputStream); int version = in.readInt(); - if (version != 2 && version != 1) { + if (version != 3 && version != 2 && version != 1) { loge("Bad version on IP configuration file, ignore read"); return null; } while (true) { - int id = -1; + String uniqueToken = null; // Default is DHCP with no proxy IpAssignment ipAssignment = IpAssignment.DHCP; ProxySettings proxySettings = ProxySettings.NONE; @@ -217,7 +274,12 @@ public class IpConfigStore { key = in.readUTF(); try { if (key.equals(ID_KEY)) { - id = in.readInt(); + if (version < 3) { + int id = in.readInt(); + uniqueToken = String.valueOf(id); + } else { + uniqueToken = in.readUTF(); + } } else if (key.equals(IP_ASSIGNMENT_KEY)) { ipAssignment = IpAssignment.valueOf(in.readUTF()); } else if (key.equals(LINK_ADDRESS_KEY)) { @@ -280,9 +342,9 @@ public class IpConfigStore { } } while (true); - if (id != -1) { + if (uniqueToken != null) { IpConfiguration config = new IpConfiguration(); - networks.put(id, config); + networks.put(uniqueToken, config); switch (ipAssignment) { case STATIC: diff --git a/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java b/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java new file mode 100644 index 0000000000..9f4b754621 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java @@ -0,0 +1,140 @@ +/* + * 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.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import android.net.IpConfiguration; +import android.net.IpConfiguration.IpAssignment; +import android.net.IpConfiguration.ProxySettings; +import android.net.LinkAddress; +import android.net.NetworkUtils; +import android.net.ProxyInfo; +import android.net.StaticIpConfiguration; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArrayMap; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Unit tests for {@link IpConfigStore} + */ +@RunWith(AndroidJUnit4.class) +public class IpConfigStoreTest { + + @Test + public void backwardCompatibility2to3() throws IOException { + final int KEY_CONFIG = 17; + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + DataOutputStream outputStream = new DataOutputStream(byteStream); + + IpConfiguration expectedConfig = new IpConfiguration(IpAssignment.DHCP, + ProxySettings.NONE, null, null); + + // Emulate writing to old format. + writeDhcpConfigV2(outputStream, KEY_CONFIG, expectedConfig); + + InputStream in = new ByteArrayInputStream(byteStream.toByteArray()); + ArrayMap configurations = IpConfigStore.readIpConfigurations(in); + + assertNotNull(configurations); + assertEquals(1, configurations.size()); + IpConfiguration actualConfig = configurations.get(String.valueOf(KEY_CONFIG)); + assertNotNull(actualConfig); + assertEquals(expectedConfig, actualConfig); + } + + @Test + public void staticIpMultiNetworks() throws Exception { + final String IFACE_1 = "eth0"; + final String IFACE_2 = "eth1"; + final String IP_ADDR_1 = "192.168.1.10/24"; + final String IP_ADDR_2 = "192.168.1.20/24"; + final String DNS_IP_ADDR_1 = "1.2.3.4"; + final String DNS_IP_ADDR_2 = "5.6.7.8"; + + StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration(); + staticIpConfiguration.ipAddress = new LinkAddress(IP_ADDR_1); + staticIpConfiguration.dnsServers.add(NetworkUtils.numericToInetAddress(DNS_IP_ADDR_1)); + staticIpConfiguration.dnsServers.add(NetworkUtils.numericToInetAddress(DNS_IP_ADDR_2)); + + ProxyInfo proxyInfo = new ProxyInfo("10.10.10.10", 88, "host1,host2"); + + IpConfiguration expectedConfig1 = new IpConfiguration(IpAssignment.STATIC, + ProxySettings.STATIC, staticIpConfiguration, proxyInfo); + IpConfiguration expectedConfig2 = new IpConfiguration(expectedConfig1); + expectedConfig2.getStaticIpConfiguration().ipAddress = new LinkAddress(IP_ADDR_2); + + ArrayMap expectedNetworks = new ArrayMap<>(); + expectedNetworks.put(IFACE_1, expectedConfig1); + expectedNetworks.put(IFACE_2, expectedConfig2); + + MockedDelayedDiskWrite writer = new MockedDelayedDiskWrite(); + IpConfigStore store = new IpConfigStore(writer); + store.writeIpConfigurations("file/path/not/used/", expectedNetworks); + + InputStream in = new ByteArrayInputStream(writer.byteStream.toByteArray()); + ArrayMap actualNetworks = IpConfigStore.readIpConfigurations(in); + assertNotNull(actualNetworks); + assertEquals(2, actualNetworks.size()); + assertEquals(expectedNetworks.get(IFACE_1), actualNetworks.get(IFACE_1)); + assertEquals(expectedNetworks.get(IFACE_2), actualNetworks.get(IFACE_2)); + } + + // This is simplified snapshot of code that was used to store values in V2 format (key as int). + private static void writeDhcpConfigV2(DataOutputStream out, int configKey, + IpConfiguration config) throws IOException { + out.writeInt(2); // VERSION 2 + switch (config.ipAssignment) { + case DHCP: + out.writeUTF("ipAssignment"); + out.writeUTF(config.ipAssignment.toString()); + break; + default: + fail("Not supported in test environment"); + } + + out.writeUTF("id"); + out.writeInt(configKey); + out.writeUTF("eos"); + } + + /** Synchronously writes into given byte steam */ + private static class MockedDelayedDiskWrite extends DelayedDiskWrite { + final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + + @Override + public void write(String filePath, Writer w) { + DataOutputStream outputStream = new DataOutputStream(byteStream); + + try { + w.onWriteCalled(outputStream); + } catch (IOException e) { + fail(); + } + } + } +}