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

效果图

一、Settings添加以太网的配置:

1、vendor\mediatek\proprietary\packages\apps\MtkSettings\res\xml\network_and_internet.xml

java 复制代码
<com.android.settingslib.RestrictedPreference
    android:key="ethernet_settings"
    android:title="@string/ethernet_settings_title"
    android:summary="@string/summary_placeholder"
    android:icon="@drawable/ic_ethernet_cell"
    android:fragment="com.android.settings.ethernet.EthernetSettings"
    android:order="-17"/>

在 mobile_network_settings 和 tether_settings 之间增加如上代码,

对应的 icon 资源文件是我从 SystemUI 中拷贝过来的,稍微调整了下大小,也贴给你们吧

2、vendor\mediatek\proprietary\packages\apps\MtkSettings\res\drawable\ic_ethernet_cell.xml

java 复制代码
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:autoMirrored="true"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="48"
    android:viewportHeight="48"
    android:tint="?android:attr/colorControlNormal">
    <path
        android:fillColor="#fff"
        android:pathData="M15.54 13.52l-3.08-2.55L1.64 24l10.82 13.04 3.08-2.55L6.84 24l8.7-10.48zM14 26h4v-4h-4v4zm20-4h-4v4h4v-4zm-12 4h4v-4h-4v4zm13.54-15.04l-3.08 2.55L41.16 24l-8.7 10.48 3.08 2.55L46.36 24 35.54 10.96z"/>
</vector>

vendor\mediatek\proprietary\packages\apps\MtkSettings\res\values\strings.xml

java 复制代码
<string name="ethernet_ip_settings_invalid_ip">"Please fill in the correct format."</string>
<string name="eth_ip_settings_please_complete_settings">"Network information is not complete, please fill in the complete"</string>
<string name="save_satic_ethernet">"Save"</string>
<string name="enthernet_static">"Use static settings"</string>
<string name="enthernet_ip_address">"IP address"</string>
<string name="enthernet_gateway">"gateway"</string>
<string name="enthernet_netmask">"Subnet mask"</string>
<string name="enthernet_dns1">"domain1"</string>
<string name="enthernet_dns2">"domain2"</string>
<string name="ethernet_quick_toggle_title">"Ethernet"</string>
<string name="open_ethernet">"Open Ethernet"</string>
<string name="ethernet_static_ip_settings_title">"Setting Ethernet"</string>
<string name="ethernet_settings">"Ethernet"</string>
<string name="ethernet_settings_title">"Ethernet"</string>

vendor\mediatek\proprietary\packages\apps\MtkSettings\res\values-zh-rCN\strings.xml

java 复制代码
<string name="ethernet_ip_settings_invalid_ip">"请填写正确的格式"</string>
<string name="save_satic_ethernet">"保存"</string>
<string name="eth_ip_settings_please_complete_settings">"网络信息不完整,请填写完整"</string>
<string name="enthernet_static">"使用静态设置"</string>
<string name="enthernet_ip_address">"IP地址"</string>
<string name="enthernet_gateway">"网关"</string>
<string name="enthernet_netmask">"子网掩码"</string>
<string name="enthernet_dns1">"域名1"</string>
<string name="enthernet_dns2">"域名2"</string>
<string name="ethernet_quick_toggle_title">"以太网"</string>
<string name="open_ethernet">"打开以太网"</string>
<string name="ethernet_static_ip_settings_title">"配置以太网"</string>
<string name="ethernet_settings">"以太网"</string>
<string name="ethernet_settings_title">"以太网"</string> 

3、增加对应设置的两个布局xml

vendor\mediatek\proprietary\packages\apps\MtkSettings\res\xml\ethernet_settings.xml

java 复制代码
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    android:title="@string/ethernet_settings">

<SwitchPreference
    android:key="ethernet"
    android:title="@string/ethernet_quick_toggle_title"
    android:summary="@string/open_ethernet"/>

<PreferenceScreen
    android:dependency="ethernet"
    android:fragment="com.android.settings.ethernet.EthernetStaticIP"
    android:key="ethernet_static_ip"
    android:title="@string/ethernet_static_ip_settings_title" />
        
</PreferenceScreen>

vendor\mediatek\proprietary\packages\apps\MtkSettings\res\xml\ethernet_static_ip.xml

java 复制代码
	<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
        android:title="@string/ethernet_static_ip_settings_title">

    <SwitchPreference
            android:key="use_static_ip"
            android:title="@string/enthernet_static"
            android:persistent="false"/>    
       
    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="ip_address"
            android:title="@string/enthernet_ip_address"
            android:persistent="false"
            android:singleLine="true"/>    
    
    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="gateway"
            android:title="@string/enthernet_gateway"
            android:persistent="false"
            android:singleLine="true"/>    
    
    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="netmask"
            android:title="@string/enthernet_netmask"
            android:persistent="false"
            android:singleLine="true" />    

    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="dns1"
            android:title="@string/enthernet_dns1"
            android:persistent="false"
            android:singleLine="true"/>    
    
    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="dns2"
            android:title="@string/enthernet_dns2"
            android:persistent="false"
            android:singleLine="true"/>    
   
</PreferenceScreen>

4、增加对应两个布局的 java 控制类

vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\ethernet\EthernetSettings.java

java 复制代码
package com.android.settings.ethernet;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v14.preference.SwitchPreference;
import android.provider.Settings;
import android.provider.Settings.System;
import android.provider.Settings.Secure;
import android.util.Log;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.net.InetAddress;

import android.net.EthernetManager;
import android.net.StaticIpConfiguration;
import android.net.LinkAddress;
import android.net.IpConfiguration;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.R;

public class EthernetSettings extends SettingsPreferenceFragment 
		implements Preference.OnPreferenceChangeListener{
			
    private static final String TAG = "EthernetSettings";

    private static final String USE_ETHERNET_SETTINGS = "ethernet";
	
    public static final String IS_ETHERNET_OPEN = Settings.IS_ETHERNET_OPEN;	
    private SwitchPreference mUseEthernet;
    private IntentFilter mIntentFilter;

    private boolean isEthernetEnabled() {
		return Settings.System.getInt(getActivity().getContentResolver(), IS_ETHERNET_OPEN,0) == 1 ? true : false;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.ethernet_settings);
		mUseEthernet = (SwitchPreference) findPreference(USE_ETHERNET_SETTINGS);
		mUseEthernet.setOnPreferenceChangeListener(this);
		if(isEthernetEnabled()) {
		   mUseEthernet.setChecked(true);
		} else {
		   mUseEthernet.setChecked(false);
		}
		File f = new File("sys/class/net/eth0/address");
		if (f.exists()) {
		   mUseEthernet.setEnabled(true);		
		} else {
		   mUseEthernet.setEnabled(false);
		}
    }
	
    @Override
    public void onResume() {
        super.onResume();
    }

     @Override
     public int getMetricsCategory(){return MetricsEvent.ETHERNET;}

     @Override
     public void onPause() {
        super.onPause();
    }	

    @Override
    public boolean onPreferenceChange(Preference preference, Object value) {
    	boolean result = true;
    	final String key = preference.getKey();
    	if (USE_ETHERNET_SETTINGS.equals(key)) {
    		Settings.System.putInt(getActivity().getContentResolver(), IS_ETHERNET_OPEN, 
    			((Boolean) value) ? 1 : 0);
    	}

    	return result;
    }

}

vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\ethernet\EthernetStaticIP.java

java 复制代码
	package com.android.settings.ethernet;
	
	import android.app.Dialog;
	import android.content.DialogInterface;
	import android.content.Intent;
	import android.content.ContentResolver;
	import android.os.Bundle;
	import android.support.v7.preference.Preference;
	import android.preference.PreferenceActivity;
	import android.support.v7.preference.PreferenceScreen;
	import android.support.v7.preference.CheckBoxPreference;
	import android.support.v7.preference.EditTextPreference;
	import android.support.v14.preference.SwitchPreference;
	
	import android.provider.Settings;
	import android.provider.Settings.Secure;
	import android.util.Log;
	import android.view.ContextMenu;
	import android.view.Menu;
	import android.view.MenuInflater;
	import android.view.MenuItem;
	import android.view.View;
	import android.view.ContextMenu.ContextMenuInfo;
	import android.widget.AdapterView;
	import android.widget.Toast;
	import android.widget.AdapterView.AdapterContextMenuInfo;
	
	import android.text.TextUtils;
	import java.util.Set;
	import java.util.WeakHashMap;
	import java.util.Formatter;
	import java.net.InetAddress;
	
	import android.net.EthernetManager;
	import android.net.StaticIpConfiguration;
	import android.net.LinkAddress;
	import android.net.IpConfiguration;
	import android.net.IpConfiguration.IpAssignment;
	import android.net.IpConfiguration.ProxySettings;
	import android.net.NetworkInfo.DetailedState;
	import android.content.BroadcastReceiver;
	import android.content.IntentFilter;
	import android.content.Context;
	import android.net.NetworkInfo;
	
	import android.view.KeyEvent;
	import android.view.Menu;
	import android.view.MenuItem;
	import android.app.AlertDialog;
	
	import com.android.settings.SettingsPreferenceFragment;
	import com.android.settings.R;
	import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
	
	public class EthernetStaticIP  extends SettingsPreferenceFragment 
	implements Preference.OnPreferenceChangeListener {
	    private static final String TAG = "EthernetStaticIP";
	    public static final boolean DEBUG = false;
	    private static void LOG(String msg) {
	        if ( DEBUG ) {
	            Log.d(TAG, msg);
	        }
	    }
	    
		/*-------------------------------------------------------*/
	    
	    private static final String KEY_USE_STATIC_IP = "use_static_ip";
	
	    private static final String KEY_IP_ADDRESS = "ip_address";
	    private static final String KEY_GATEWAY = "gateway";
	    private static final String KEY_NETMASK = "netmask";
	    private static final String KEY_DNS1 = "dns1";
	    private static final String KEY_DNS2 = "dns2";
	    public static final String ETHERNET_USE_STATIC_IP = Settings.IS_ETHERNET_STATUC_OPEN;
	    
	    private static final int MENU_ITEM_SAVE = Menu.FIRST;
	    private static final int MENU_ITEM_CANCEL = Menu.FIRST + 1;
	    
	    private String[] mSettingNames = {
	        Settings.ETHERNET_STATIC_IP, 
	        Settings.ETHERNET_STATIC_GATEWAY,
	        Settings.ETHERNET_STATIC_NETMASK,
	        Settings.ETHERNET_STATIC_DNS1, 
	        Settings.ETHERNET_STATIC_DNS2
	    };
	    
	    
	    private String[] mPreferenceKeys = {
	        KEY_IP_ADDRESS,
	        KEY_GATEWAY,
	        KEY_NETMASK,
	        KEY_DNS1,
	        KEY_DNS2,
	    };
	    
		/*-------------------------------------------------------*/
	    
	    private SwitchPreference mUseStaticIpSwitch;
	    private StaticIpConfiguration mStaticIpConfiguration;
	    private IpConfiguration mIpConfiguration;
	    private EthernetManager mEthernetManager;
	    
	    private boolean isOnPause = false;
	    private boolean chageState = false;
	    
	    public EthernetStaticIP() {
	    }
	    
	    @Override
	    public void onActivityCreated(Bundle savedInstanceState){
	        super.onActivityCreated(savedInstanceState);
			
	        addPreferencesFromResource(R.xml.ethernet_static_ip);
	
	        mUseStaticIpSwitch = (SwitchPreference)findPreference(KEY_USE_STATIC_IP);
	        mUseStaticIpSwitch.setOnPreferenceChangeListener(this);
	  
	        for ( int i = 0; i < mPreferenceKeys.length; i++ ) {
	            Preference preference = findPreference(mPreferenceKeys[i] );
	            preference.setOnPreferenceChangeListener(this);
	        }
	        setHasOptionsMenu(true);
	    }
	    
	    @Override
	    public void onResume() {
	        super.onResume();
	        if(!isOnPause) {
	            updateIpSettingsInfo();
	        }
	        isOnPause = false;
	    }
	    
	    @Override
	    public int getMetricsCategory(){return MetricsEvent.ETHERNET_STATIC;}    
	    
	    @Override
	    public void onPause() {
	        isOnPause = true;
	        super.onPause();
	    }
	    
	    @Override
	    public void onDestroy() {
	        super.onDestroy();
	    }
	
	    @Override
	    public void onSaveInstanceState(Bundle outState) {
	        super.onSaveInstanceState(outState);
	    }
	    
	    @Override
	    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
	        menu.add(Menu.NONE, MENU_ITEM_SAVE, 0, R.string.save_satic_ethernet)
	                .setEnabled(true)
	                .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
	        super.onCreateOptionsMenu(menu, inflater);
	    }
	
	    @Override
	    public boolean onOptionsItemSelected(MenuItem item) {
	
	        switch (item.getItemId()) {
	        
	            case MENU_ITEM_SAVE:
	            	saveIpSettingsInfo();
			if(isIpDataInUiComplete())
	            	   finish();
	                return true;
	                
	            case MENU_ITEM_CANCEL:
	                finish();
	                return true;
	        }
	        
	        return super.onOptionsItemSelected(item);
	    }        
	    
	    
	    private void updateIpSettingsInfo() {
	    	LOG("Static IP status updateIpSettingsInfo");
	        ContentResolver contentResolver = getContentResolver();
	        
	        mUseStaticIpSwitch.setChecked(Settings.System.getInt(contentResolver, ETHERNET_USE_STATIC_IP, 0) != 0);
	        
	        for (int i = 0; i < mSettingNames.length; i++) {
	            EditTextPreference preference = (EditTextPreference) findPreference(mPreferenceKeys[i]);
	            String settingValue = Settings.System.getString(contentResolver, mSettingNames[i]);
	            preference.setText(settingValue);
	            preference.setSummary(settingValue);
	        }
	    }
	        
	
	   
	    private void saveIpSettingsInfo() {
	        ContentResolver contentResolver = getContentResolver();
	  /*      
	        if(!chageState)   
	        	return;
	  */      
	        if(!isIpDataInUiComplete()) 
	        {     
	           Toast.makeText(getActivity(), R.string.eth_ip_settings_please_complete_settings, Toast.LENGTH_LONG).show();
	           return;
	        }
	
			mIpConfiguration = new IpConfiguration();
			mStaticIpConfiguration = new StaticIpConfiguration();
	        
	        for (int i = 0; i < mSettingNames.length; i++) {
	            
	            EditTextPreference preference = (EditTextPreference) findPreference(mPreferenceKeys[i]);
	            String text = preference.getText();
		    try {
		    	switch (mPreferenceKeys[i]) {
			   case KEY_IP_ADDRESS:
			      	mStaticIpConfiguration.ipAddress = new LinkAddress(InetAddress.getByName(text), 24);
			      break;
			   case KEY_GATEWAY:
		    		mStaticIpConfiguration.gateway = InetAddress.getByName(text);
			      break;
			   case KEY_NETMASK:
				mStaticIpConfiguration.domains = text;
			      break;
			   case KEY_DNS1:
		    		mStaticIpConfiguration.dnsServers.add(InetAddress.getByName(text));
			      break;
			   case KEY_DNS2:
		    		mStaticIpConfiguration.dnsServers.add(InetAddress.getByName(text));
			      break;	
		        }            
		    } catch (Exception e) {
	                e.printStackTrace();
	            }
	            if ( null == text || TextUtils.isEmpty(text) ) {
	               Settings.System.putString(contentResolver, mSettingNames[i], null);
	            }
	            else {
	               Settings.System.putString(contentResolver, mSettingNames[i], text);
	            }
	        }
			mIpConfiguration.ipAssignment = IpAssignment.STATIC;
			mIpConfiguration.proxySettings = ProxySettings.STATIC;
	        mIpConfiguration.staticIpConfiguration = mStaticIpConfiguration;	
			mEthernetManager = (EthernetManager) getSystemService(Context.ETHERNET_SERVICE);
			if (mUseStaticIpSwitch.isChecked())
	            mEthernetManager.setConfiguration(mIpConfiguration); 
	        Settings.System.putInt(contentResolver,ETHERNET_USE_STATIC_IP, mUseStaticIpSwitch.isChecked() ? 1 : 0);
	        
	        // disable ethernet
	        boolean enable = Secure.getInt(getContentResolver(), "isEnthernetOn", 1) == 1;
			LOG("notify Secure.ETHERNET_ON changed. enable = " + enable);
	        if(enable) {
	        	LOG("first disable");
	        	Secure.putInt(getContentResolver(), "isEnthernetOn", 0);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {}   
				LOG("second enable");
	        	Secure.putInt(getContentResolver(), "isEnthernetOn", 1);    	
	        }
	    }
	   
	    @Override
	    public boolean onPreferenceTreeClick(Preference preference) {
	        
	        boolean result = true;     
	        LOG("onPreferenceTreeClick()  chageState = " + chageState);
	        chageState = true;
	
	        return result;
	    }
	
		@Override
	    public boolean onPreferenceChange(Preference preference, Object newValue) {
	        boolean result = true;
	
	        String key = preference.getKey();
	        LOG("onPreferenceChange() : key = " + key);
	
	        if ( null == key ) {
	            return true;
	        }else if (key.equals(KEY_USE_STATIC_IP)) {
	
	
	        }else if ( key.equals(KEY_IP_ADDRESS) 
	                || key.equals(KEY_GATEWAY)
	                || key.equals(KEY_NETMASK)
	                || key.equals(KEY_DNS1)
	                || key.equals(KEY_DNS2) ) { 
	
	            String value = (String) newValue;       
	            
	            LOG("onPreferenceChange() : value = " + value);
	
	            
	            if ( TextUtils.isEmpty(value) ) {
	                
	                ( (EditTextPreference)preference).setText(value);
	                
	                preference.setSummary(value);
	                
	                result = true;
	            }
	            
	            else  if ( !isValidIpAddress(value) ) {
	                LOG("onPreferenceChange() : IP address user inputed is INVALID." );
	                
	                Toast.makeText(getActivity(), R.string.ethernet_ip_settings_invalid_ip, Toast.LENGTH_LONG).show();
	               
	                return false;
	            }
	            
	            else {
	                
	                ( (EditTextPreference)preference).setText(value);
	               
	                preference.setSummary(value);
	                
	                result = true;
	            }
	        }
	        return result;
	    }    
	    
	    private boolean isValidIpAddress(String value) {
	        
	        int start = 0;
	        int end = value.indexOf('.');
	        int numBlocks = 0;
	        
	        while (start < value.length()) {
	            
	            if ( -1 == end ) {
	                end = value.length();
	            }
	
	            try {
	                int block = Integer.parseInt(value.substring(start, end));
	                if ((block > 255) || (block < 0)) {
	                    Log.w(TAG, "isValidIpAddress() : invalid 'block', block = " + block);
	                    return false;
	                }
	            } catch (NumberFormatException e) {
	                Log.w(TAG, "isValidIpAddress() : e = " + e);
	                return false;
	            }
	            
	            numBlocks++;
	            
	            start = end + 1;
	            end = value.indexOf('.', start);
	        }
	        
	        return numBlocks == 4;
	    }
	    
	    private boolean isIpDataInUiComplete() {
	        ContentResolver contentResolver = getContentResolver();
	        for (int i = 0; i < (mPreferenceKeys.length - 1); i++) {
	            EditTextPreference preference = (EditTextPreference) findPreference(mPreferenceKeys[i]);
	            String text = preference.getText();
	            LOG("isIpDataInUiComplete() : text = " + text);
	            if ( null == text || TextUtils.isEmpty(text) ) {
	                return false;
	            }
	        }
	        return true;
	    }
	}

到这一步 Settings 的修改就完成了,就能实现上图的效果了,你可以mm push看效果了

如果你编译报错,大概是 Settings 中没有添加对应的变量,我的本来就有的,

没有的可参考下面的加一下

frameworks\base\core\java\android\provider\Settings.java

java 复制代码
// Intent actions for Settings
// ethernet
public static final String ETHERNET_STATIC_IP = "ethernet_static_ip";
public static final String ETHERNET_STATIC_GATEWAY = "ethernet_static_gateway";
public static final String ETHERNET_STATIC_NETMASK = "ethernet_static_netmask";
public static final String ETHERNET_STATIC_DNS1 = "ethernet_static_dns1";
public static final String ETHERNET_STATIC_DNS2 = "ethernet_static_dns2";
public static final String IS_ETHERNET_OPEN = "isEthernetOpen";
public static final String IS_ETHERNET_STATUC_OPEN = "isEthernetStaticOpen";

加完后你需要先 make update-api成功后,在重新 mm 编译应该就好了

二、framework 流程分析

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

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

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

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

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 中添加初始信息。

java 复制代码
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

java 复制代码
<!-- 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 可以看到你设备下的所有网口名称。

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

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

java 复制代码
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();
        }
    }

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

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

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

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 一下就可以。

java 复制代码
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)

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

相关推荐
_一条咸鱼_2 小时前
大厂Android面试秘籍:Activity 结果回调处理(八)
android·面试·android jetpack
_一条咸鱼_3 小时前
大厂Android面试秘籍:Activity 与 Fragment 交互(九)
android·面试·android jetpack
rainFFrain3 小时前
网络基础2
服务器·网络
小南家的青蛙4 小时前
Linux的网络配置的资料
linux·运维·网络
青山渺渺4 小时前
简单记录一下Android四大组件
android
兴达易控5 小时前
Profibus DP主站转ModbusTCP网关通讯秘籍
网络
海滩上的那乌克丽丽5 小时前
通过websocket给服务端发送订单催单提醒消息
网络·websocket·网络协议
每次的天空5 小时前
Android学习总结之OKHttp拦截器和缓存
android·学习·okhttp
aaajj6 小时前
【Android】ContentResolver的使用
android
时光少年6 小时前
Android ExoPlayer版本升级遇上系统的”瓜“
android·前端