AndroidX86自动化虚拟机调研编译之路

227 阅读11分钟

前言

因需求,急需用于跑安卓自动化接口类任务的虚拟机。 满足以下条件:

  • 1、开启root权限
  • 2、x86架构兼容arm
  • 3、实现可静态配置ip
  • 4、最好是iso镜像文件
  • 5、系统版本最低为9
  • 6、系统性能好流畅
  • 7、adb connect自动信任连接

1、调研开源可用的虚拟机

1)google开源docker虚拟机

优点:

  • ①支持安卓11
  • ②系统较为流畅
  • ③官方开源

缺点:

  • ①不能root
  • ②x86_64架构不兼容arm
  • ③需要安装docker使用

2)三方开源docker虚拟机

优点:

  • ①可root
  • ②系统较为流畅
  • ③支持安卓9

缺点:

  • ①x86_64架构不兼容arm
  • ②需要安装docker使用

3)Android Studio自带模拟器

优点:

  • ①安卓9和安卓11模拟器可用
  • ②可root
  • ③x86架构兼容arm
  • ④系统较为流畅

缺点:

  • ①依赖ide

4)Androidx86开源镜像

优点:

  • 现成iso镜像
  • ②可root
  • ③系统较为流畅
  • ④支持安卓9
  • ⑤x86架构兼容arm

缺点:

  • ①仅支持wifi连接模式

总结

根据以上调研,Androidx86开源镜像最贴近需求,决定尝试拉取源码更改支持静态ip模式编译iso文件即满足需求。

2、Androidx86 9.0编译

编译系统环境:Ubuntu18.04 jdk1.8和python3.8等安装自动百度即可

1)官方拉取源码构建教程

2)修改源码支持静态ip

参考文档:

源码修改

仅展示android9.0中与android8.1设置静态ip主要逻辑有区别的三个类,其余修改逻辑参考文档②。

[1]frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.java
/*
 * Copyright (C) 2014 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.ethernet;

import static android.net.ConnectivityManager.TYPE_ETHERNET;

import android.content.Context;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
import android.net.StringNetworkSpecifier;
import android.net.ip.IpClient;
import android.net.ip.IpClient.ProvisioningConfiguration;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.util.IndentingPrintWriter;

import java.io.FileDescriptor;
import java.util.concurrent.ConcurrentHashMap;

/**
 * {@link NetworkFactory} that represents Ethernet networks.
 *
 * This class reports a static network score of 70 when it is tracking an interface and that
 * interface's link is up, and a score of 0 otherwise.
 */
public class EthernetNetworkFactory extends NetworkFactory {
    private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
    final static boolean DBG = true;

    private final static int NETWORK_SCORE = 70;
    private static final String NETWORK_TYPE = "Ethernet";

    private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
            new ConcurrentHashMap<>();
    private final Handler mHandler;
    private final Context mContext;

    public EthernetNetworkFactory(Handler handler, Context context, NetworkCapabilities filter) {
        super(handler.getLooper(), context, NETWORK_TYPE, filter);

        mHandler = handler;
        mContext = context;

        setScoreFilter(NETWORK_SCORE);
    }

    @Override
    public boolean acceptRequest(NetworkRequest request, int score) {
        if (DBG) {
            Log.d(TAG, "acceptRequest, request: " + request + ", score: " + score);
        }

        return networkForRequest(request) != null;
    }

    @Override
    protected void needNetworkFor(NetworkRequest networkRequest, int score) {
        NetworkInterfaceState network = networkForRequest(networkRequest);

        if (network == null) {
            Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
            return;
        }

        if (++network.refCount == 1) {
            network.start();
        }
    }

    @Override
    protected void releaseNetworkFor(NetworkRequest networkRequest) {
        NetworkInterfaceState network = networkForRequest(networkRequest);
        if (network == null) {
            Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
            return;
        }

        if (--network.refCount == 1) {
            network.stop();
        }
    }

    /**
     * Returns an array of available interface names. The array is sorted: unrestricted interfaces
     * goes first, then sorted by name.
     */
    String[] getAvailableInterfaces(boolean includeRestricted) {
        return mTrackingInterfaces.values()
                .stream()
                .filter(iface -> !iface.isRestricted() || includeRestricted)
                .sorted((iface1, iface2) -> {
                    int r = Boolean.compare(iface1.isRestricted(), iface2.isRestricted());
                    return r == 0 ? iface1.name.compareTo(iface2.name) : r;
                })
                .map(iface -> iface.name)
                .toArray(String[]::new);
    }

    NetworkInterfaceState addInterface(String ifaceName, String hwAddress, NetworkCapabilities capabilities,
             IpConfiguration ipConfiguration) {
        if (mTrackingInterfaces.containsKey(ifaceName)) {
            Log.e(TAG, "Interface with name " + ifaceName + " already exists.");
            return null;
        }

        if (DBG) {
            Log.d(TAG, "addInterface, iface: " + ifaceName + ", capabilities: " + capabilities);
        }

        NetworkInterfaceState iface = new NetworkInterfaceState(
                ifaceName, hwAddress, mHandler, mContext, capabilities);
        iface.setIpConfig(ipConfiguration);
        mTrackingInterfaces.put(ifaceName, iface);

        updateCapabilityFilter();
        
        return iface;
    }

    private void updateCapabilityFilter() {
        NetworkCapabilities capabilitiesFilter = new NetworkCapabilities();
        capabilitiesFilter.clearAll();

        for (NetworkInterfaceState iface:  mTrackingInterfaces.values()) {
            capabilitiesFilter.combineCapabilities(iface.mCapabilities);
        }

        if (DBG) Log.d(TAG, "updateCapabilityFilter: " + capabilitiesFilter);
        setCapabilityFilter(capabilitiesFilter);
    }

    void removeInterface(String interfaceName) {
        NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
        if (iface != null) {
            iface.stop();
        }

        updateCapabilityFilter();
    }

    /** Returns true if state has been modified */
    boolean updateInterfaceLinkState(String ifaceName, boolean up) {
        if (!mTrackingInterfaces.containsKey(ifaceName)) {
            return false;
        }

        if (DBG) {
            Log.d(TAG, "updateInterfaceLinkState, iface: " + ifaceName + ", up: " + up);
        }

        NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
        return iface.updateLinkState(up);
    }

    boolean hasInterface(String interfacName) {
        return mTrackingInterfaces.containsKey(interfacName);
    }

    void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
        NetworkInterfaceState network = mTrackingInterfaces.get(iface);
        if (network != null) {
            network.setIpConfig(ipConfiguration);
        }
    }

    private NetworkInterfaceState networkForRequest(NetworkRequest request) {
        String requestedIface = null;

        NetworkSpecifier specifier = request.networkCapabilities.getNetworkSpecifier();
        if (specifier instanceof StringNetworkSpecifier) {
            requestedIface = ((StringNetworkSpecifier) specifier).specifier;
        }

        NetworkInterfaceState network = null;
        if (!TextUtils.isEmpty(requestedIface)) {
            NetworkInterfaceState n = mTrackingInterfaces.get(requestedIface);
            if (n != null && n.statisified(request.networkCapabilities)) {
                network = n;
            }
        } else {
            for (NetworkInterfaceState n : mTrackingInterfaces.values()) {
                if (n.statisified(request.networkCapabilities)) {
                    network = n;
                    break;
                }
            }
        }

        if (DBG) {
            Log.i(TAG, "networkForRequest, request: " + request + ", network: " + network);
        }

        return network;
    }

    public static class NetworkInterfaceState {
        final String name;

        private final String mHwAddress;
        private final NetworkCapabilities mCapabilities;
        private final Handler mHandler;
        private final Context mContext;
        private final NetworkInfo mNetworkInfo;

        private static String sTcpBufferSizes = null;  // Lazy initialized.

        private boolean mLinkUp;
        public LinkProperties mLinkProperties = new LinkProperties();

        private IpClient mIpClient;
        private NetworkAgent mNetworkAgent;
        private IpConfiguration mIpConfig;

        long refCount = 0;

        private final IpClient.Callback mIpClientCallback = new IpClient.Callback() {
            @Override
            public void onProvisioningSuccess(LinkProperties newLp) {
                mHandler.post(() -> onIpLayerStarted(newLp));
            }

            @Override
            public void onProvisioningFailure(LinkProperties newLp) {
                mHandler.post(() -> onIpLayerStopped(newLp));
            }

            @Override
            public void onLinkPropertiesChange(LinkProperties newLp) {
                mHandler.post(() -> updateLinkProperties(newLp));
            }
        };

        NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
                NetworkCapabilities capabilities) {
            name = ifaceName;
            mCapabilities = capabilities;
            mHandler = handler;
            mContext = context;

            mHwAddress = hwAddress;
            mNetworkInfo = new NetworkInfo(TYPE_ETHERNET, 0, NETWORK_TYPE, "");
            mNetworkInfo.setExtraInfo(mHwAddress);
            mNetworkInfo.setIsAvailable(true);
        }

        void setIpConfig(IpConfiguration ipConfig) {

            this.mIpConfig = ipConfig;
        }

        LinkProperties getLinkProperties(){
            return this.mLinkProperties;
        }

        boolean statisified(NetworkCapabilities requestedCapabilities) {
            return requestedCapabilities.satisfiedByNetworkCapabilities(mCapabilities);
        }

        boolean isRestricted() {
            return mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
        }

        private void start() {
            if (mIpClient != null) {
                if (DBG) Log.d(TAG, "IpClient already started");
                return;
            }
            if (DBG) {
                Log.d(TAG, String.format("starting IpClient(%s): mNetworkInfo=%s", name,
                        mNetworkInfo));
            }

            mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddress);

            mIpClient = new IpClient(mContext, name, mIpClientCallback);

            if (sTcpBufferSizes == null) {
                sTcpBufferSizes = mContext.getResources().getString(
                        com.android.internal.R.string.config_ethernet_tcp_buffers);
            }
            provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
        }

        void onIpLayerStarted(LinkProperties linkProperties) {
            if (mNetworkAgent != null) {
                Log.e(TAG, "Already have a NetworkAgent - aborting new request");
                stop();
                return;
            }
            mLinkProperties = linkProperties;
            mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddress);
            mNetworkInfo.setIsAvailable(true);

            // Create our NetworkAgent.
            mNetworkAgent = new NetworkAgent(mHandler.getLooper(), mContext,
                    NETWORK_TYPE, mNetworkInfo, mCapabilities, mLinkProperties,
                    NETWORK_SCORE) {
                public void unwanted() {
                    if (this == mNetworkAgent) {
                        stop();
                    } else if (mNetworkAgent != null) {
                        Log.d(TAG, "Ignoring unwanted as we have a more modern " +
                                "instance");
                    }  // Otherwise, we've already called stop.
                }
            };
        }

        void onIpLayerStopped(LinkProperties linkProperties) {
            // This cannot happen due to provisioning timeout, because our timeout is 0. It can only
            // happen if we're provisioned and we lose provisioning.
            stop();
            start();
        }

        void updateLinkProperties(LinkProperties linkProperties) {
            mLinkProperties = linkProperties;
            if (mNetworkAgent != null) {
                mNetworkAgent.sendLinkProperties(linkProperties);
            }
        }

        /** Returns true if state has been modified */
        boolean updateLinkState(boolean up) {
            if (mLinkUp == up) return false;
            mLinkUp = up;

            stop();
            if (up) {
                start();
            }

            return true;
        }

        void stop() {
            if (mIpClient != null) {
                mIpClient.shutdown();
                mIpClient.awaitShutdown();
                mIpClient = null;
            }

            // ConnectivityService will only forget our NetworkAgent if we send it a NetworkInfo object
            // with a state of DISCONNECTED or SUSPENDED. So we can't simply clear our NetworkInfo here:
            // that sets the state to IDLE, and ConnectivityService will still think we're connected.
            //
            mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddress);
            if (mNetworkAgent != null) {
                updateAgent();
                mNetworkAgent = null;
            }
            clear();
        }

        private void updateAgent() {
            if (mNetworkAgent == null) return;
            if (DBG) {
                Log.i(TAG, "Updating mNetworkAgent with: " +
                        mCapabilities + ", " +
                        mNetworkInfo + ", " +
                        mLinkProperties);
            }
            mNetworkAgent.sendNetworkCapabilities(mCapabilities);
            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
            mNetworkAgent.sendLinkProperties(mLinkProperties);
            // never set the network score below 0.
            mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : 0);
        }

        private void clear() {
            mLinkProperties.clear();
            mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
            mNetworkInfo.setIsAvailable(false);
        }

        private static void provisionIpClient(IpClient ipClient, IpConfiguration config,
                String tcpBufferSizes) {
            if (config.getProxySettings() == ProxySettings.STATIC ||
                    config.getProxySettings() == ProxySettings.PAC) {
                ipClient.setHttpProxy(config.getHttpProxy());
            }

            if (!TextUtils.isEmpty(tcpBufferSizes)) {
                ipClient.setTcpBufferSizes(tcpBufferSizes);
            }

            final ProvisioningConfiguration provisioningConfiguration;
            if (config.getIpAssignment() == IpAssignment.STATIC) {
                provisioningConfiguration = IpClient.buildProvisioningConfiguration()
                        .withStaticConfiguration(config.getStaticIpConfiguration())
                        .build();
            } else {
                provisioningConfiguration = IpClient.buildProvisioningConfiguration()
                        .withProvisioningTimeoutMs(0)
                        .build();
            }

            ipClient.startProvisioning(provisioningConfiguration);
        }

        @Override
        public String toString() {
            return getClass().getSimpleName() + "{ "
                    + "iface: " + name + ", "
                    + "up: " + mLinkUp + ", "
                    + "hwAddress: " + mHwAddress + ", "
                    + "networkInfo: " + mNetworkInfo + ", "
                    + "networkAgent: " + mNetworkAgent + ", "
                    + "ipClient: " + mIpClient + ","
                    + "linkProperties: " + mLinkProperties
                    + "}";
        }
    }

    void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
        super.dump(fd, pw, args);
        pw.println(getClass().getSimpleName());
        pw.println("Tracking interfaces:");
        pw.increaseIndent();
        for (String iface: mTrackingInterfaces.keySet()) {
            NetworkInterfaceState ifaceState = mTrackingInterfaces.get(iface);
            pw.println(iface + ":" + ifaceState);
            pw.increaseIndent();
            final IpClient ipClient = ifaceState.mIpClient;
            if (ipClient != null) {
                ipClient.dump(fd, pw, args);
            } else {
                pw.println("IpClient is null");
            }
            pw.decreaseIndent();
        }
        pw.decreaseIndent();
    }
}

[2]frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetServiceImpl.java

/*
 * Copyright (C) 2014 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.ethernet;

import android.content.Context;
import android.content.pm.PackageManager;
import android.net.IEthernetManager;
import android.net.IEthernetServiceListener;
import android.net.IpConfiguration;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
import android.util.PrintWriterPrinter;

import com.android.internal.util.IndentingPrintWriter;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * EthernetServiceImpl handles remote Ethernet operation requests by implementing
 * the IEthernetManager interface.
 */
public class EthernetServiceImpl extends IEthernetManager.Stub {
    private static final String TAG = "EthernetServiceImpl";

    private final Context mContext;
    private final AtomicBoolean mStarted = new AtomicBoolean(false);

    private Handler mHandler;
    private EthernetTracker mTracker;

    public EthernetServiceImpl(Context context) {
        mContext = context;
    }

    private void enforceAccessPermission() {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.ACCESS_NETWORK_STATE,
                "EthernetService");
    }

    private void enforceConnectivityInternalPermission() {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.CONNECTIVITY_INTERNAL,
                "ConnectivityService");
    }

    private void enforceUseRestrictedNetworksPermission() {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS,
                "ConnectivityService");
    }

    private boolean checkUseRestrictedNetworksPermission() {
        return mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
                == PackageManager.PERMISSION_GRANTED;
    }

    public void start() {
        Log.i(TAG, "Starting Ethernet service");

        HandlerThread handlerThread = new HandlerThread("EthernetServiceThread");
        handlerThread.start();
        mHandler = new Handler(handlerThread.getLooper());

        mTracker = new EthernetTracker(mContext, mHandler);
        mTracker.start();

        mStarted.set(true);
    }

    @Override
    public String[] getAvailableInterfaces() throws RemoteException {
        return mTracker.getInterfaces(checkUseRestrictedNetworksPermission());
    }

    /**
     * Get Ethernet configuration
     * @return the Ethernet Configuration, contained in {@link IpConfiguration}.
     */
    @Override
    public IpConfiguration getConfiguration(String iface) {
        enforceAccessPermission();

        if (mTracker.isRestrictedInterface(iface)) {
            enforceUseRestrictedNetworksPermission();
        }

        return new IpConfiguration(mTracker.getIpConfiguration(iface));
    }

    /**
     * Set Ethernet configuration
     */
    @Override
    public void setConfiguration(String iface, IpConfiguration config) {
        if (!mStarted.get()) {
            Log.w(TAG, "System isn't ready enough to change ethernet configuration");
        }

        enforceConnectivityInternalPermission();

        if (mTracker.isRestrictedInterface(iface)) {
            enforceUseRestrictedNetworksPermission();
        }

        // TODO: this does not check proxy settings, gateways, etc.
        // Fix this by making IpConfiguration a complete representation of static configuration.
        mTracker.updateIpConfiguration(iface, new IpConfiguration(config));
    }

    /**
     * Indicates whether given interface is available.
     */
    @Override
    public boolean isAvailable(String iface) {
        enforceAccessPermission();

        if (mTracker.isRestrictedInterface(iface)) {
            enforceUseRestrictedNetworksPermission();
        }

        return mTracker.isTrackingInterface(iface);
    }

    /**
     * Adds a listener.
     * @param listener A {@link IEthernetServiceListener} to add.
     */
    public void addListener(IEthernetServiceListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
        }
        enforceAccessPermission();
        mTracker.addListener(listener, checkUseRestrictedNetworksPermission());
    }

    /**
     * Removes a listener.
     * @param listener A {@link IEthernetServiceListener} to remove.
     */
    public void removeListener(IEthernetServiceListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener must not be null");
        }
        enforceAccessPermission();
        mTracker.removeListener(listener);
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                != PackageManager.PERMISSION_GRANTED) {
            pw.println("Permission Denial: can't dump EthernetService from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid());
            return;
        }

        pw.println("Current Ethernet state: ");
        pw.increaseIndent();
        mTracker.dump(fd, pw, args);
        pw.decreaseIndent();

        pw.println("Handler:");
        pw.increaseIndent();
        mHandler.dump(new PrintWriterPrinter(pw), "EthernetServiceImpl");
        pw.decreaseIndent();
    }

    @Override
    public int getEthernetCarrierState(String ifname) {
        enforceAccessPermission();
        return mTracker.getEthernetCarrierState(ifname);
    }

    @Override
    public String getEthernetMacAddress(String ifname) {
        enforceAccessPermission();
        return mTracker.getEthernetMacAddress(ifname);
    }

    @Override
    public int getEthernetConnectState() {
        enforceAccessPermission();
        return mTracker.mEthernetCurrentState;
    }

    @Override
    public String getIpAddress(String ifname) {
        enforceAccessPermission();
        return mTracker.getIpAddress(ifname);
    }

    @Override
    public String getNetmask(String ifname) {
        enforceAccessPermission();
        return mTracker.getNetmask(ifname);
    }

    @Override
    public String getGateway(String ifname) {
        enforceAccessPermission();
        return mTracker.getGateway(ifname);
    }

    @Override
    public String getDns(String ifname) {
        enforceAccessPermission();
        return mTracker.getDns(ifname);
    }

    @Override
    public String dumpCurrentState(int state) {
        enforceAccessPermission();
        return mTracker.dumpEthCurrentState(state);
    }

    @Override
    public void reconnect(String iface) {
        enforceAccessPermission();
        mTracker.reconnect(iface);
    }

    @Override
    public void disconnect(String iface) {
        enforceAccessPermission();
        mTracker.disconnect(iface);
    }
}

[3]frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java

/*
 * 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.ethernet;

import android.annotation.Nullable;
import android.content.Context;
import android.net.IEthernetServiceListener;
import android.net.InterfaceConfiguration;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkAddress;
import android.net.NetworkCapabilities;
import android.net.StaticIpConfiguration;
import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.net.BaseNetworkObserver;

import java.io.FileDescriptor;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

import android.net.EthernetManager;
import android.net.RouteInfo;
import android.net.LinkAddress;
import android.net.NetworkUtils;
import android.content.Intent;
import android.os.UserHandle;
import android.provider.Settings;

import java.io.FileDescriptor;
import java.io.File;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.Exception;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;

/**
 * Tracks Ethernet interfaces and manages interface configurations.
 *
 * <p>Interfaces may have different {@link android.net.NetworkCapabilities}. This mapping is defined
 * in {@code config_ethernet_interfaces}. Notably, some interfaces could be marked as restricted by
 * not specifying {@link android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED} flag.
 * Interfaces could have associated {@link android.net.IpConfiguration}.
 * Ethernet Interfaces may be present at boot time or appear after boot (e.g., for Ethernet adapters
 * connected over USB). This class supports multiple interfaces. When an interface appears on the
 * system (or is present at boot time) this class will start tracking it and bring it up. Only
 * interfaces whose names match the {@code config_ethernet_iface_regex} regular expression are
 * tracked.
 *
 * <p>All public or package private methods must be thread-safe unless stated otherwise.
 */
final class EthernetTracker {
    private final static String TAG = EthernetTracker.class.getSimpleName();
    private final static boolean DBG = EthernetNetworkFactory.DBG;

    /** Product-dependent regular expression of interface names we track. */
    private final String mIfaceMatch;

    /** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
    private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
            new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
            new ConcurrentHashMap<>();

    private final INetworkManagementService mNMService;
    private final Handler mHandler;
    private final EthernetNetworkFactory mFactory;
    private final EthernetConfigStore mConfigStore;

    private final RemoteCallbackList<IEthernetServiceListener> mListeners =
            new RemoteCallbackList<>();

    private volatile IpConfiguration mIpConfigForDefaultInterface;

    private boolean mReconnecting = false;
    private String mIface = "eth0";

    /** For static IP configuration */
    private EthernetManager mEthernetManager;
    public int mEthernetCurrentState = EthernetManager.ETHER_STATE_DISCONNECTED;
    private EthernetNetworkFactory.NetworkInterfaceState mInterfaceSate;
    private Context mContext;

    EthernetTracker(Context context, Handler handler) {
        mHandler = handler;
        mContext = context;

        // The services we use.
        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
        mNMService = INetworkManagementService.Stub.asInterface(b);
        mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);

        // Interface match regex.
        mIfaceMatch = context.getResources().getString(
                com.android.internal.R.string.config_ethernet_iface_regex);

        // Read default Ethernet interface configuration from resources
        final String[] interfaceConfigs = context.getResources().getStringArray(
                com.android.internal.R.array.config_ethernet_interfaces);
        for (String strConfig : interfaceConfigs) {
            parseEthernetConfig(strConfig);
        }

        mConfigStore = new EthernetConfigStore();

        NetworkCapabilities nc = createNetworkCapabilities(true /* clear default capabilities */);
        mFactory = new EthernetNetworkFactory(handler, context, nc);
        mFactory.register();
    }

    void start() {
        mConfigStore.read();

        // Default interface is just the first one we want to track.
        mIpConfigForDefaultInterface = mConfigStore.getIpConfigurationForDefaultInterface();
        final ArrayMap<String, IpConfiguration> configs = mConfigStore.getIpConfigurations();
        for (int i = 0; i < configs.size(); i++) {
            mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
        }

        try {
            mNMService.registerObserver(new InterfaceObserver());
        } catch (RemoteException e) {
            Log.e(TAG, "Could not register InterfaceObserver " + e);
        }

        mHandler.post(this::trackAvailableInterfaces);
    }

    void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
        if (DBG) {
            Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
        }

        mConfigStore.write(iface, ipConfiguration);
        mIpConfigurations.put(iface, ipConfiguration);

        mHandler.post(() -> mFactory.updateIpConfiguration(iface, ipConfiguration));
    }

    IpConfiguration getIpConfiguration(String iface) {
        return mIpConfigurations.get(iface);
    }

    boolean isTrackingInterface(String iface) {
        return mFactory.hasInterface(iface);
    }

    String[] getInterfaces(boolean includeRestricted) {
        return mFactory.getAvailableInterfaces(includeRestricted);
    }

    /**
     * Returns true if given interface was configured as restricted (doesn't have
     * NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
     */
    boolean isRestrictedInterface(String iface) {
        final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
        return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
    }

    void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
        mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks));
    }

    void removeListener(IEthernetServiceListener listener) {
        mListeners.unregister(listener);
    }

    private void removeInterface(String iface) {
        mFactory.removeInterface(iface);
    }

    private void addInterface(String iface) {
        InterfaceConfiguration config = null;
        // Bring up the interface so we get link status indications.
        try {
            mNMService.setInterfaceUp(iface);
            config = mNMService.getInterfaceConfig(iface);
        } catch (RemoteException | IllegalStateException e) {
            // Either the system is crashing or the interface has disappeared. Just ignore the
            // error; we haven't modified any state because we only do that if our calls succeed.
            Log.e(TAG, "Error upping interface " + iface, e);
        }

        if (config == null) {
            Log.e(TAG, "Null interface config for " + iface + ". Bailing out.");
            return;
        }

        final String hwAddress = config.getHardwareAddress();

        NetworkCapabilities nc = mNetworkCapabilities.get(iface);
        if (nc == null) {
            // Try to resolve using mac address
            nc = mNetworkCapabilities.get(hwAddress);
            if (nc == null) {
                nc = createDefaultNetworkCapabilities();
            }
        }
        IpConfiguration ipConfiguration = mIpConfigurations.get(iface);
        if (ipConfiguration == null) {
            ipConfiguration = createDefaultIpConfiguration();
        }

        Log.d(TAG, "Started tracking interface " + iface);
        EthernetNetworkFactory.NetworkInterfaceState interfaceSate =  mFactory.addInterface(iface, hwAddress, nc, ipConfiguration);
        if (interfaceSate !=null) {
            mInterfaceSate = interfaceSate;
        }


        // Note: if the interface already has link (e.g., if we crashed and got
        // restarted while it was running), we need to fake a link up notification so we
        // start configuring it.
        if (config.hasFlag("running")) {
            updateInterfaceState(iface, true);
        }
    }

    private void updateInterfaceState(String iface, boolean up) {
        boolean modified = mFactory.updateInterfaceLinkState(iface, up);
        if (modified) {
            boolean restricted = isRestrictedInterface(iface);
            int n = mListeners.beginBroadcast();
            for (int i = 0; i < n; i++) {
                try {
                    if (restricted) {
                        ListenerInfo listenerInfo = (ListenerInfo) mListeners.getBroadcastCookie(i);
                        if (!listenerInfo.canUseRestrictedNetworks) {
                            continue;
                        }
                    }
                    mListeners.getBroadcastItem(i).onAvailabilityChanged(iface, up);
                } catch (RemoteException e) {
                    // Do nothing here.
                }
            }
            mListeners.finishBroadcast();
        }

        if (!up) {
            sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_DISCONNECTING);
        }
    }

    private void maybeTrackInterface(String iface) {
        if (DBG) Log.i(TAG, "maybeTrackInterface " + iface);
        // If we don't already track this interface, and if this interface matches
        // our regex, start tracking it.
        if (!iface.matches(mIfaceMatch) || mFactory.hasInterface(iface)) {
            return;
        }

        if (mIpConfigForDefaultInterface != null) {
            updateIpConfiguration(iface, mIpConfigForDefaultInterface);
            mIpConfigForDefaultInterface = null;
        }

        addInterface(iface);
    }

    private void trackAvailableInterfaces() {
        try {
            final String[] ifaces = mNMService.listInterfaces();
            for (String iface : ifaces) {
                maybeTrackInterface(iface);
            }
        } catch (RemoteException | IllegalStateException e) {
            Log.e(TAG, "Could not get list of interfaces " + e);
        }
    }


    private class InterfaceObserver extends BaseNetworkObserver {

        @Override
        public void interfaceLinkStateChanged(String iface, boolean up) {
            if (DBG) {
                Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
            }
            if (up) {
                sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_CONNECTED);
            } else {
                sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_DISCONNECTED);
            }
            mHandler.post(() -> updateInterfaceState(iface, up));
        }

        @Override
        public void interfaceAdded(String iface) {
            sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_CONNECTING);
            mHandler.post(() -> maybeTrackInterface(iface));
        }

        @Override
        public void interfaceRemoved(String iface) {
            sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_DISCONNECTING);
            mHandler.post(() -> removeInterface(iface));
        }
    }

    private static class ListenerInfo {

        boolean canUseRestrictedNetworks = false;

        ListenerInfo(boolean canUseRestrictedNetworks) {
            this.canUseRestrictedNetworks = canUseRestrictedNetworks;
        }
    }

    private void parseEthernetConfig(String configString) {
        String[] tokens = configString.split(";");
        String name = tokens[0];
        String capabilities = tokens.length > 1 ? tokens[1] : null;
        NetworkCapabilities nc = createNetworkCapabilities(
                !TextUtils.isEmpty(capabilities)  /* clear default capabilities */, capabilities);
        mNetworkCapabilities.put(name, nc);

        if (tokens.length > 2 && !TextUtils.isEmpty(tokens[2])) {
            IpConfiguration ipConfig = parseStaticIpConfiguration(tokens[2]);
            mIpConfigurations.put(name, ipConfig);
        }
    }

    private static NetworkCapabilities createDefaultNetworkCapabilities() {
        NetworkCapabilities nc = createNetworkCapabilities(false /* clear default capabilities */);
        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);

        return nc;
    }

    private static NetworkCapabilities createNetworkCapabilities(boolean clearDefaultCapabilities) {
        return createNetworkCapabilities(clearDefaultCapabilities, null);
    }

    private static NetworkCapabilities createNetworkCapabilities(
            boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities) {

        NetworkCapabilities nc = new NetworkCapabilities();
        if (clearDefaultCapabilities) {
            nc.clearAll();  // Remove default capabilities.
        }
        nc.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
        nc.setLinkUpstreamBandwidthKbps(100 * 1000);
        nc.setLinkDownstreamBandwidthKbps(100 * 1000);

        if (!TextUtils.isEmpty(commaSeparatedCapabilities)) {
            for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) {
                if (!TextUtils.isEmpty(strNetworkCapability)) {
                    nc.addCapability(Integer.valueOf(strNetworkCapability));
                }
            }
        }

        return nc;
    }

    /**
     * Parses static IP configuration.
     *
     * @param staticIpConfig represents static IP configuration in the following format: {@code
     * ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
     *     domains=<comma-sep-domains>}
     */
    @VisibleForTesting
    static IpConfiguration parseStaticIpConfiguration(String staticIpConfig) {
        StaticIpConfiguration ipConfig = new StaticIpConfiguration();

        for (String keyValueAsString : staticIpConfig.trim().split(" ")) {
            if (TextUtils.isEmpty(keyValueAsString)) continue;

            String[] pair = keyValueAsString.split("=");
            if (pair.length != 2) {
                throw new IllegalArgumentException("Unexpected token: " + keyValueAsString
                        + " in " + staticIpConfig);
            }

            String key = pair[0];
            String value = pair[1];

            switch (key) {
                case "ip":
                    ipConfig.ipAddress = new LinkAddress(value);
                    break;
                case "domains":
                    ipConfig.domains = value;
                    break;
                case "gateway":
                    ipConfig.gateway = InetAddress.parseNumericAddress(value);
                    break;
                case "dns": {
                    ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
                    for (String address: value.split(",")) {
                        dnsAddresses.add(InetAddress.parseNumericAddress(address));
                    }
                    ipConfig.dnsServers.addAll(dnsAddresses);
                    break;
                }
                default : {
                    throw new IllegalArgumentException("Unexpected key: " + key
                            + " in " + staticIpConfig);
                }
            }
        }
        return new IpConfiguration(IpAssignment.STATIC, ProxySettings.NONE, ipConfig, null);
    }

    private static IpConfiguration createDefaultIpConfiguration() {
        return new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
    }

    private void postAndWaitForRunnable(Runnable r) {
        mHandler.runWithScissors(r, 2000L /* timeout */);
    }

    void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
        postAndWaitForRunnable(() -> {
            pw.println(getClass().getSimpleName());
            pw.println("Ethernet interface name filter: " + mIfaceMatch);
            pw.println("Listeners: " + mListeners.getRegisteredCallbackCount());
            pw.println("IP Configurations:");
            pw.increaseIndent();
            for (String iface : mIpConfigurations.keySet()) {
                pw.println(iface + ": " + mIpConfigurations.get(iface));
            }
            pw.decreaseIndent();
            pw.println();

            pw.println("mEthernetCurrentState: " + dumpEthCurrentState(mEthernetCurrentState));

            pw.println("Network Capabilities:");
            pw.increaseIndent();
            for (String iface : mNetworkCapabilities.keySet()) {
                pw.println(iface + ": " + mNetworkCapabilities.get(iface));
            }
            pw.decreaseIndent();
            pw.println();

            mFactory.dump(fd, pw, args);
        });
    }

    public int getEthernetCarrierState(String ifname) {
        if(ifname != "") {
            try {
                File file = new File("/sys/class/net/" + ifname + "/carrier");
                String carrier = ReadFromFile(file);
                return Integer.parseInt(carrier);
            } catch(Exception e) {
                e.printStackTrace();
                return 0;
            }
        } else {
            return 0;
        }
    }
    
        public String getEthernetMacAddress(String ifname) {
        if(ifname != "") {
            try {
                File file = new File("/sys/class/net/" + ifname + "/address");
                String address = ReadFromFile(file);
                return address;
            } catch(Exception e) {
                e.printStackTrace();
                return "";
            }
        } else {
            return "";
        }
    }

    public String getIpAddress(String ifname) {
        IpConfiguration config = mEthernetManager.getConfiguration(ifname);
        if (config.getIpAssignment() == IpAssignment.STATIC) {
            return config.getStaticIpConfiguration().ipAddress.getAddress().getHostAddress();
        } else {
            for (LinkAddress l : mInterfaceSate.getLinkProperties().getLinkAddresses()) {
                InetAddress source = l.getAddress();
                //Log.d(TAG, "getIpAddress: "  source.getHostAddress());
                if (source instanceof Inet4Address) {
                    return source.getHostAddress();
                }
            }
        }
        return "";
    }

    private String prefix2netmask(int prefix) {
        // convert prefix to netmask
        if (true) {
            int mask = 0xFFFFFFFF << (32 - prefix);
            //Log.d(TAG, "mask = "  mask  " prefix = "  prefix);
            return ((mask>>>24) & 0xff) + "." + ((mask>>>16) & 0xff) + "." + ((mask>>>8) & 0xff) + "." + ((mask) & 0xff);
    	   } else {
    	       return NetworkUtils.intToInetAddress(NetworkUtils.prefixLengthToNetmaskInt(prefix)).getHostName();
    	   }
    }

    public String getNetmask(String ifname) {
        IpConfiguration config = mEthernetManager.getConfiguration(ifname);
        if (config.getIpAssignment() == IpAssignment.STATIC) {
            return prefix2netmask(config.getStaticIpConfiguration().ipAddress.getPrefixLength());
        } else {
            for (LinkAddress l : mInterfaceSate.getLinkProperties().getLinkAddresses()) {
                InetAddress source = l.getAddress();
                if (source instanceof Inet4Address) {
                    return prefix2netmask(l.getPrefixLength());
                }
            }
        }
        return "";
    }
	
    public String getGateway(String ifname) {
        IpConfiguration config = mEthernetManager.getConfiguration(ifname);
        if (config.getIpAssignment() == IpAssignment.STATIC) {
            return config.getStaticIpConfiguration().gateway.getHostAddress();
        } else {
            for (RouteInfo route : mInterfaceSate.getLinkProperties().getRoutes()) {
                if (route.hasGateway()) {
                    InetAddress gateway = route.getGateway();
                    if (route.isIPv4Default()) {
                        return gateway.getHostAddress();
                    }
                }
            }
        }
        return "";		
    }

    /*
     * return dns format: "8.8.8.8,4.4.4.4"
     */
    public String getDns(String ifname) {
        String dns = "";
        IpConfiguration config = mEthernetManager.getConfiguration(ifname);
        if (config.getIpAssignment() == IpAssignment.STATIC) {
            for (InetAddress nameserver : config.getStaticIpConfiguration().dnsServers) {
                dns = nameserver.getHostAddress() +  ",";
            }			
        } else {		
            for (InetAddress nameserver : mInterfaceSate.getLinkProperties().getDnsServers()) {
                dns = nameserver.getHostAddress() + ",";
            }
        }
        return dns;		
    }

    public String dumpEthCurrentState(int curState) {
        if (curState == EthernetManager.ETHER_STATE_DISCONNECTED)
            return "DISCONNECTED";
        else if (curState == EthernetManager.ETHER_STATE_CONNECTING)
            return "CONNECTING";
        else if (curState == EthernetManager.ETHER_STATE_CONNECTED)
            return "CONNECTED";
        else if (curState == EthernetManager.ETHER_STATE_DISCONNECTING)
            return "DISCONNECTING";
        return "DISCONNECTED";
    }

    // first disconnect, then connect
    public void reconnect(String iface) {
        Log.d(TAG, "reconnect:");
        mReconnecting = true;

        if (iface == null)
            iface = mIface;

        updateInterfaceState(iface, false);
        Log.d(TAG, "first disconnect");

        try {
            Thread.sleep(1000);
        } catch (InterruptedException ignore) {
        }

        updateInterfaceState(iface, true);
        mReconnecting = false;
        Log.d(TAG, "then connect");
    }

    public void disconnect(String iface) {
        mReconnecting = true;

        if (iface == null)
            iface = mIface;

        updateInterfaceState(iface, false);
        mReconnecting = false;
        Log.d(TAG, "disconnect:");
    }

    private String ReadFromFile(File file) {
        if((file != null) && file.exists()) {
            try {
                FileInputStream fin= new FileInputStream(file);
                BufferedReader reader= new BufferedReader(new InputStreamReader(fin));
                String flag = reader.readLine();
                fin.close();
                return flag;
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private void sendEthernetStateChangedBroadcast(int curState) {
        if (mEthernetCurrentState == curState)
            return;
        Log.d(TAG, "sendEthernetStateChangedBroadcast: curState = " + dumpEthCurrentState(curState));
        mEthernetCurrentState = curState;
        final Intent intent = new Intent(EthernetManager.ETHERNET_STATE_CHANGED_ACTION);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        intent.putExtra(EthernetManager.EXTRA_ETHERNET_STATE, curState);
        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
    }
}

其他

1)iso镜像安装开机后黑屏解决方案

2)虚拟机无法设置静态ip解决方案

3)Android 下命令方式设置IP地址、网关、DNS

4)使用qemu启动安卓镜像