Android P 9.0 MTK平台 增加以太网静态IP功能

简介: Android P 9.0 MTK平台 增加以太网静态IP功能

前言


朋友们,最近又开始搞 Android P了,同样的以太网静态 IP 是少不了的功能,今天我们就开始来整一下。之前弄6.0 和 8.1 的都 ok 了。

没想到 9.0 改动还是略微有点大的。来来来,先看图。


效果图


20191022100220372.gif



上代码


app层


Settings 中的代码和之前的一样就不贴了,可以去看之前的这篇中代码 Android8.1 MTK平台 增加以太网静态IP功能

或者下载这篇的源码资源


framework 层


驱动大哥已经把驱动都搞定了,现在直接插上网线,设备就能上网,网卡图标也正常显示。我们需要控制网卡的开关,先来简单看下流程。


frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetService.java

public final class EthernetService extends SystemService {
    private static final String TAG = "EthernetService";
    final EthernetServiceImpl mImpl;
    public EthernetService(Context context) {
        super(context);
        mImpl = new EthernetServiceImpl(context);
    }
    @Override
    public void onStart() {
        Log.i(TAG, "Registering service " + Context.ETHERNET_SERVICE);
        publishBinderService(Context.ETHERNET_SERVICE, mImpl);
    }
    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            mImpl.start();
        }
    }
}


EthernetService 继承了系统服务,那自然也就是系统服务,如果挂掉会自动重新创建,严重情况会导致系统重启,我就试出来过。看下当 PHASE_SYSTEM_SERVICES_READY 准备完成,调用 EthernetServiceImpl 的 start()


来看下这个 start() 都干了啥


frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetServiceImpl.java


    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);
}

主要创建了 EthernetTracker,这个类是 9.0 中新增出来的,用于监听以太网的切换、以太网判断当前网络是否可用等一系列操作。之前 8.1 中都集成在 EthernetNetworkFactory 中,继续跟进


frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetTracker.java

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();
        Log.e(TAG, "mIpConfigForDefaultInterface== " + mIpConfigForDefaultInterface);
        Log.i(TAG, "IpConfiguration size== " + configs.size());
        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);
    }

mConfigStore 对象用来管理保存的 IpConfigStore 信息,EthernetConfigStore 中通过读取 /misc/ethernet/ipconfig.txt 中保存的信息进行维护一个 ArrayMap<String, IpConfiguration>,根据打印的日志看,start() 中每次获取到的 size 都为 0,基本上没起作用。值得一提的是,在 EthernetTracker 的构造方法中通过解析 config_ethernet_interfaces 字符串也可向 map 中添加初始信息。

EthernetTracker(Context context, Handler handler) {
        mHandler = handler;
        // The services we use.
        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
        mNMService = INetworkManagementService.Stub.asInterface(b);
        // 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();
    }
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);
        }
    }

config_ethernet_interfaces 的初始值位置在

frameworks\base\core\res\res\values\config.xml

<!-- Regex of wired ethernet ifaces -->
    <string translatable="false" name="config_ethernet_iface_regex">eth\\d</string>
    <!-- Configuration of Ethernet interfaces in the following format:
         <interface name|mac address>;[Network Capabilities];[IP config]
         Where
               [Network Capabilities] Optional. A comma seprated list of network capabilities.
                   Values must be from NetworkCapabilities#NET_CAPABILITIES_* constants.
               [IP config] Optional. If empty or not specified - DHCP will be used, otherwise
                   use the following format to specify static IP configuration:
           ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
                       domains=<comma-sep-domains> 
         -->
    <string-array translatable="false" name="config_ethernet_interfaces">
        <!--
        <item>eth1;12,13,14,15;ip=192.168.0.10/24 gateway=192.168.0.1 dns=4.4.4.4,8.8.8.8</item>
        <item>eth2;;ip=192.168.0.11/24</item>
        -->
    </string-array>

好了继续回到刚刚的 start() 中,接下来应该调用 trackAvailableInterfaces(),来看下完整的流程,maybeTrackInterface 中先进行 iface 判断,这个指代要使用的网口名称,通过命令 ifconfig -a 可以看到你设备下的所有网口名称。

aHR0cHM6Ly9zMi5heDF4LmNvbS8yMDE5LzEwLzIxL0tseEo2ZS5wbmc.png


mIfaceMatch 对应刚刚的 config.xml 中配置的 eth\d, 所以只有是 eth 打头并且 mFactory 中不存在的才能设置以太网连接,这很关键


继续调用 addInterface(), 将 iface 对应的 ipConfiguration 通过 mFactory addInterface,再然后 updateInterfaceState 广播通知当前以太网连接状态 CONNECTED/CONNECTED

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 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)) {
            Log.d(TAG, iface + "  return ");
            return;
        }
        Log.e(TAG, "maybeTrackInterface " + iface);
        if (mIpConfigForDefaultInterface != null) {
            updateIpConfiguration(iface, mIpConfigForDefaultInterface);
            mIpConfigForDefaultInterface = null;
        }
        addInterface(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);
        mFactory.addInterface(iface, hwAddress, nc, ipConfiguration);
        // 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) {
        Log.e(TAG, "updateInterfaceState up==" + 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();
        }
    }

aHR0cHM6Ly9zMi5heDF4LmNvbS8yMDE5LzEwLzIxL0tsNmZ2NC5wbmc.png


知道了 EthernetTracker 的 start() 去连接以太网,那么我们在 EthernetServiceImpl 中增加布尔判断就能控制以太网开关状态。


frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetServiceImpl.java

public class EthernetServiceImpl extends IEthernetManager.Stub {
    private static final String TAG = "EthernetServiceImpl";
    public static final String IS_ETHERNET_OPEN = Settings.IS_ETHERNET_OPEN;
    public static final String ETHERNET_USE_STATIC_IP = Settings.IS_ETHERNET_STATUC_OPEN; 
    private final Context mContext;
    private final AtomicBoolean mStarted = new AtomicBoolean(false);
    private IpConfiguration mIpConfiguration;
    private final EthernetOpenedObserver mOpenObserver = new EthernetOpenedObserver();
    private final EthernetStaticObserver mStaticObserver = new EthernetStaticObserver(); 
    private Handler mHandler;
    private EthernetTracker mTracker;
    public EthernetServiceImpl(Context context) {
        mContext = context;
        Log.i(TAG, "Creating EthernetConfigStore");
        mContext.getContentResolver().registerContentObserver(
            System.getUriFor(IS_ETHERNET_OPEN), false, mOpenObserver);
        mContext.getContentResolver().registerContentObserver(
            System.getUriFor(ETHERNET_USE_STATIC_IP), false, mStaticObserver); 
    }
  ....
  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);
        mIpConfiguration = mTracker.getDefaultIpConfiguration();
        if (getState() == 1) {                 
            if (isStatic()) {
                StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
                staticIpConfiguration.domains = Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_NETMASK);
                try {
                    staticIpConfiguration.gateway = InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_GATEWAY));
                    staticIpConfiguration.ipAddress = new LinkAddress(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_IP)), 24);
                    staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS1)));
                    staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS2)));
                }catch (Exception e){
                        e.printStackTrace();
                }
                mIpConfiguration.ipAssignment = IpAssignment.STATIC;
                mIpConfiguration.proxySettings = ProxySettings.STATIC;
                mIpConfiguration.staticIpConfiguration = staticIpConfiguration;
            }
            mTracker.start();
            mStarted.set(true);
        }  
    }
  private boolean isStatic()
    {
        Log.e(TAG, "EthernetServiceImpl isStatic()  " 
            + Settings.System.getInt(mContext.getContentResolver(),ETHERNET_USE_STATIC_IP,0));
     return Settings.System.getInt(mContext.getContentResolver(),ETHERNET_USE_STATIC_IP,0) ==1;
    }   
    private int getState()
    {
        int state = Settings.System.getInt(mContext.getContentResolver(), IS_ETHERNET_OPEN,0);
        Log.e(TAG, "EthernetServiceImpl getState()  " + state);
       return state;
    }
  ....
   @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();
        }
        Log.e(TAG, "setConfiguration iface="+iface);
        // 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));
        //add
        mTracker.removeInterface(iface);
        mTracker.start();
    }
  ....
  private final class EthernetOpenedObserver extends ContentObserver {
        public EthernetOpenedObserver() {
            super(new Handler());
        }
        @Override
        public void onChange(boolean selfChange, Uri uri, int userId) {
          super.onChange(selfChange, uri, userId);
            Log.i(TAG, "EthernetServiceImpl isEthernetOpen onChange....");
            if (getState() == 1) {
                if (isStatic()) {
                    StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
                    staticIpConfiguration.domains = Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_NETMASK);
                    try {
                        staticIpConfiguration.gateway = InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_GATEWAY));
                        staticIpConfiguration.ipAddress = new LinkAddress(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_IP)), 24);
                        staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS1)));
                        staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS2)));
                        }
                        catch (Exception e){
                            e.printStackTrace();
                        }
                        mIpConfiguration.ipAssignment = IpAssignment.STATIC;
                        mIpConfiguration.proxySettings = ProxySettings.STATIC;
                        mIpConfiguration.staticIpConfiguration = staticIpConfiguration;
                }
                mTracker.start();
                mStarted.set(true);
            }else {    
                mTracker.stop();        
           }
        }
    }
    private final class EthernetStaticObserver extends ContentObserver {
        public EthernetStaticObserver() {
            super(new Handler());
        }
        @Override
        public void onChange(boolean selfChange, Uri uri, int userId) {
            super.onChange(selfChange, uri, userId);
            Log.i(TAG, "EthernetServiceImpl isEthernetStaticOpen onChange....");
            if (!isStatic()) {
                Log.e(TAG, " no static stop and start");
                mTracker.recoverDHCPIpConfiguration();
                mTracker.stop();
                mTracker.start();           
                mStarted.set(true);
           }  
        }
     }


根据 settings 中设置的值 IS_ETHERNET_OPEN 和 ETHERNET_USE_STATIC_IP 判断是否加载,在 EthernetTracker 中新增如下几个方法


frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetTracker.java

//关闭网卡,先更新状态为 false, 再从 mFactory 中移除 eth0, 不然关闭后无法再次打开,因为上面提到的判断
public void stop() {
    Log.d(TAG, "EthernetTracker stop ethernet...");
    updateInterfaceState("eth0", false);
    android.os.SystemClock.sleep(200);
    removeInterface("eth0");
 }
//获取默认的 IpConfiguration,如果不存在则新建一个 DHCP 类型的,根据实际情况修改 ipAssignment 和 proxySettings
public IpConfiguration getDefaultIpConfiguration(){
    IpConfiguration ipConfiguration = mIpConfigurations.get("eth0");
    return ipConfiguration != null ? ipConfiguration : 
        new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
}
//从静态 IP 切换
public void recoverDHCPIpConfiguration(){
    mIpConfigurations.put("eth0", new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null));
}

测试发现频繁点击 静态IP 开关时,出现了数组角标越界的情况,应该是 add 和 remove iface导致的,直接将打印 try 一下就可以

2019-10-21 17:01:38.675 1075-1285/? E/AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: EthernetServiceThread
    java.lang.ArrayIndexOutOfBoundsException: length=2; index=-1
        at com.android.internal.util.StateMachine$SmHandler.getCurrentState(StateMachine.java:1151)
        at com.android.internal.util.StateMachine$SmHandler.access$1300(StateMachine.java:681)
        at com.android.internal.util.StateMachine.toString(StateMachine.java:2088)
        at java.lang.String.valueOf(String.java:2896)
        at java.lang.StringBuilder.append(StringBuilder.java:132)
        at com.android.server.ethernet.EthernetNetworkFactory$NetworkInterfaceState.toString(EthernetNetworkFactory.java:422)
        at java.lang.String.valueOf(String.java:2896)
        at java.lang.StringBuilder.append(StringBuilder.java:132)
        at com.android.server.ethernet.EthernetNetworkFactory.networkForRequest(EthernetNetworkFactory.java:213)
        at com.android.server.ethernet.EthernetNetworkFactory.acceptRequest(EthernetNetworkFactory.java:78)
        at android.net.NetworkFactory.evalRequest(NetworkFactory.java:234)
        at android.net.NetworkFactory.evalRequests(NetworkFactory.java:253)
        at android.net.NetworkFactory.handleSetFilter(NetworkFactory.java:204)
        at android.net.NetworkFactory.handleMessage(NetworkFactory.java:149)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.os.HandlerThread.run(HandlerThread.java:65)

frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetNetworkFactory.java

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


这样就能实现开头的动图效果了


修改文件源码下载


android 9.0 添加Ethernet功能(settings+framework).zip

目录
相关文章
|
1月前
|
XML 缓存 Android开发
Android开发,使用kotlin学习多媒体功能(详细)
Android开发,使用kotlin学习多媒体功能(详细)
103 0
|
2月前
|
Android开发
安卓SO层开发 -- 编译指定平台的SO文件
安卓SO层开发 -- 编译指定平台的SO文件
32 0
|
4天前
|
Java Android开发
Android Mediatek 应用层重置USB设备功能
Android Mediatek 应用层重置USB设备功能
11 0
|
1月前
|
运维 监控 Java
应用研发平台EMAS产品常见问题之安卓构建版本失败如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
|
1月前
|
运维 监控 Android开发
应用研发平台EMAS常见问题之安卓push的离线转通知目前无法收到如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
25 1
|
1月前
|
网络协议 数据格式
|
2月前
|
存储 缓存 网络协议
计算机网络:思科实验【2-MAC地址、IP地址、ARP协议及总线型以太网的特性】
计算机网络:思科实验【2-MAC地址、IP地址、ARP协议及总线型以太网的特性】
|
3月前
|
编解码 测试技术 开发工具
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
|
3月前
|
开发工具 Android开发
Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?
|
3月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
104 0