/* * WiFiKeyShare. Share Wi-Fi passwords with QR codes or NFC tags. * Copyright (C) 2016 Bruno Parmentier <[email protected]> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package be.brunoparmentier.wifikeyshare.ui.activities; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; 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.widget.TextView; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import be.brunoparmentier.wifikeyshare.R; import be.brunoparmentier.wifikeyshare.adapters.WifiNetworkAdapter; import be.brunoparmentier.wifikeyshare.db.WifiKeysDataSource; import be.brunoparmentier.wifikeyshare.model.WifiAuthType; import be.brunoparmentier.wifikeyshare.model.WifiNetwork; import be.brunoparmentier.wifikeyshare.ui.AboutDialog; import be.brunoparmentier.wifikeyshare.ui.ContextMenuRecyclerView; import be.brunoparmentier.wifikeyshare.ui.DividerItemDecoration; import be.brunoparmentier.wifikeyshare.utils.WifiConfigStoreParser; import be.brunoparmentier.wifikeyshare.utils.WpaSupplicantParser; import eu.chainfire.libsuperuser.Shell; public class WifiListActivity extends AppCompatActivity { private static final String TAG = WifiListActivity.class.getSimpleName(); private static final String FILE_WIFI_SUPPLICANT = "/data/misc/wifi/wpa_supplicant.conf"; private static final String FILE_WIFI_CONFIG_STORE = "/data/misc/wifi/WifiConfigStore.xml"; private static final int PASSWORD_REQUEST = 1; private static final String KEY_NETWORK_ID = "network_id"; private static final String PREF_KEY_HAS_READ_NO_ROOT_DIALOG = "has_read_no_root_dialog"; private List<WifiNetwork> wifiNetworks; private WifiNetworkAdapter wifiNetworkAdapter; private ContextMenuRecyclerView rvWifiNetworks; private WifiManager wifiManager; private boolean isDeviceRooted = false; private int networkIdToUpdate = -1; // index of item to update in networks list private BroadcastReceiver wifiStateChangeBroadcastReceiver; private boolean waitingForWifiToTurnOn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_wifi_list); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); /* Enable Wi-Fi if disabled */ if (!wifiManager.isWifiEnabled()) { wifiManager.setWifiEnabled(true); waitingForWifiToTurnOn = true; initializeWifiStateChangeListener(); } setupWifiNetworksList(); } private void setupWifiNetworksList() { wifiNetworks = new ArrayList<>(); rvWifiNetworks = (ContextMenuRecyclerView) findViewById(R.id.rvWifiNetwork); wifiNetworkAdapter = new WifiNetworkAdapter(this, wifiNetworks); rvWifiNetworks.setAdapter(wifiNetworkAdapter); // Set layout manager to position the items rvWifiNetworks.setLayoutManager(new LinearLayoutManager(this)); // Separator RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration( this, DividerItemDecoration.VERTICAL_LIST); rvWifiNetworks.addItemDecoration(itemDecoration); rvWifiNetworks.setHasFixedSize(true); rvWifiNetworks.setItemAnimator(new DefaultItemAnimator()); registerForContextMenu(rvWifiNetworks); //rvWifiNetworks.setItemAnimator(new SlideInUpAnimator()); //rvWifiNetworks.getItemAnimator().setAddDuration(1000); /* FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { addWifiNetwork(); } }); */ if (!waitingForWifiToTurnOn) { (new WifiListTask()).execute(); } } void initializeWifiStateChangeListener() { wifiStateChangeBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) { final boolean isConnected = intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false); if (isConnected) { if (waitingForWifiToTurnOn) { (new WifiListTask()).execute(); } } } } }; } private void addWifiNetwork() { // TODO: Show dialog box to configure new Wi-Fi AP wifiNetworks.add(new WifiNetwork("Test1", WifiAuthType.WEP, "mykey", false)); wifiNetworkAdapter.notifyItemInserted(wifiNetworks.size() - 1); rvWifiNetworks.scrollToPosition(wifiNetworkAdapter.getItemCount() - 1); } @Override protected void onResume() { if (waitingForWifiToTurnOn) { IntentFilter wifiStateIntentFilter = new IntentFilter(); wifiStateIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); registerReceiver(wifiStateChangeBroadcastReceiver, wifiStateIntentFilter); } if (networkIdToUpdate > -1) { String key = WifiKeysDataSource.getInstance().getWifiKey( wifiNetworks.get(networkIdToUpdate).getSsid(), wifiNetworks.get(networkIdToUpdate).getAuthType()); if (key == null) { Log.d(TAG, "onResume: key is null"); } else { wifiNetworks.get(networkIdToUpdate).setKey(key); wifiNetworkAdapter.notifyItemChanged(networkIdToUpdate); } networkIdToUpdate = -1; } super.onResume(); } @Override protected void onPause() { if (waitingForWifiToTurnOn) { unregisterReceiver(wifiStateChangeBroadcastReceiver); } super.onPause(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_wifi_list, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_settings: Intent settingsIntent = new Intent(this, SettingsActivity.class); startActivity(settingsIntent); return true; case R.id.action_about: final AlertDialog aboutDialog = new AboutDialog(this); aboutDialog.show(); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); int itemPosition = ((ContextMenuRecyclerView.RecyclerContextMenuInfo) menuInfo).position; menu.setHeaderTitle(wifiNetworks.get(itemPosition).getSsid()); MenuInflater menuInflater = getMenuInflater(); menuInflater.inflate(R.menu.context_menu, menu); boolean canViewPasword = wifiNetworks.get(itemPosition).isPasswordProtected() && !wifiNetworks.get(itemPosition).getKey().isEmpty(); boolean canClearPassword = canViewPasword; MenuItem viewPasswordMenuItem = menu.findItem(R.id.context_menu_wifi_list_view_password); viewPasswordMenuItem.setEnabled(canViewPasword); MenuItem clearPasswordMenuItem = menu.findItem(R.id.context_menu_wifi_list_clear_password); clearPasswordMenuItem.setEnabled(canClearPassword); clearPasswordMenuItem.setVisible(!isDeviceRooted); } @Override public boolean onContextItemSelected(MenuItem item) { int itemPosition = ((ContextMenuRecyclerView.RecyclerContextMenuInfo) item.getMenuInfo()).position; switch (item.getItemId()) { case (R.id.context_menu_wifi_list_view_password): final AlertDialog viewPasswordDialog = new AlertDialog.Builder(this) .setTitle(getString(R.string.wifilist_dialog_view_password)) .setView(R.layout.dialog_view_password) .setPositiveButton(R.string.action_close, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }) .create(); viewPasswordDialog.show(); /* Set SSID, security and password values */ TextView ssidTextView = (TextView) viewPasswordDialog.findViewById(R.id.ssid_value); TextView authTypeTextView = (TextView) viewPasswordDialog.findViewById(R.id.auth_type_value); TextView passwordTextView = (TextView) viewPasswordDialog.findViewById(R.id.password_value); ssidTextView.setText(wifiNetworks.get(itemPosition).getSsid()); authTypeTextView.setText(wifiNetworks.get(itemPosition).getAuthType().toString()); passwordTextView.setText(wifiNetworks.get(itemPosition).getKey()); passwordTextView.setTextIsSelectable(true); return true; case (R.id.context_menu_wifi_list_clear_password): removeSavedWifiKey(itemPosition); return true; } return super.onContextItemSelected(item); } private void removeSavedWifiKey(int position) { /* Reset key in local Wi-Fi list */ wifiNetworks.get(position).setKey(""); /* Notify adapter that the Wi-Fi network has changed */ wifiNetworkAdapter.notifyItemChanged(position); /* Remove key from saved keys database */ WifiNetwork wifiNetwork = wifiNetworks.get(position); String ssid = wifiNetwork.getSsid(); WifiAuthType authType = wifiNetwork.getAuthType(); if (WifiKeysDataSource.getInstance().removeWifiKey(ssid, authType) == 0) { Log.e(TAG, "No key was removed from database"); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(TAG, "onActivityResult: requestCode=" + requestCode + ", resultCode=" + resultCode); if (requestCode == PASSWORD_REQUEST) { if (resultCode == RESULT_OK) { networkIdToUpdate = data.getIntExtra(KEY_NETWORK_ID, -1); } } } private class WifiListTask extends AsyncTask<Void, Void, List<WifiNetwork>> { @Override protected List<WifiNetwork> doInBackground(Void... params) { List<WifiNetwork> wifiManagerNetworks = new ArrayList<>(); List<WifiConfiguration> savedWifiConfigs = wifiManager.getConfiguredNetworks(); if (waitingForWifiToTurnOn) { wifiManager.setWifiEnabled(false); waitingForWifiToTurnOn = false; unregisterReceiver(wifiStateChangeBroadcastReceiver); } /* Populate WifiNetwork list from WifiManager */ if (savedWifiConfigs != null) { for (WifiConfiguration wifiConfig : savedWifiConfigs) { WifiNetwork newNetwork = WifiNetwork.fromWifiConfiguration(wifiConfig); if (!newNetwork.getSsid().isEmpty()) { wifiManagerNetworks.add(newNetwork); } } } /* Get passwords from wpa_supplicant if root is available */ if (Shell.SU.available()) { isDeviceRooted = true; String wifiFile; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { wifiFile = FILE_WIFI_SUPPLICANT; } else { wifiFile = FILE_WIFI_CONFIG_STORE; } List<String> result = Shell.SU.run("cat " + wifiFile); String strRes = ""; for (String line : result) { strRes += line + "\n"; //Log.d(TAG, line); } List<WifiNetwork> wifiNetworksFromRoot = new ArrayList<>(); try { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { wifiNetworksFromRoot = WpaSupplicantParser.parse(strRes); } else { wifiNetworksFromRoot = WifiConfigStoreParser.parse( new ByteArrayInputStream(strRes.getBytes(StandardCharsets.UTF_8))); } } catch (XmlPullParserException | IOException e) { e.printStackTrace(); } for (WifiNetwork wifiManagerNetwork : wifiManagerNetworks) { if (wifiManagerNetwork.getAuthType() != WifiAuthType.OPEN) { for (WifiNetwork wpaSupplicantNetwork : wifiNetworksFromRoot) { if (wifiManagerNetwork.getSsid().equals(wpaSupplicantNetwork.getSsid())) { wifiManagerNetwork.setKey(wpaSupplicantNetwork.getKey()); break; } } } } } Collections.sort(wifiManagerNetworks, new Comparator<WifiNetwork>() { @Override public int compare(WifiNetwork w1, WifiNetwork w2) { return w1.getSsid().toLowerCase().compareTo(w2.getSsid().toLowerCase()); } }); return wifiManagerNetworks; } @Override protected void onPostExecute(List<WifiNetwork> wifiManagerNetworks) { for (WifiNetwork wifiNetwork : wifiManagerNetworks) { /* TODO: EAP networks are not yet supported */ if (wifiNetwork.getAuthType() != WifiAuthType.WPA_EAP && wifiNetwork.getAuthType() != WifiAuthType.WPA2_EAP) { wifiNetworks.add(wifiNetwork); wifiNetworkAdapter.notifyItemInserted(wifiNetworkAdapter.getItemCount() - 1); } } if (!isDeviceRooted) { setSavedKeysToWifiNetworks(); boolean hasReadNoRootDialog = PreferenceManager .getDefaultSharedPreferences(WifiListActivity.this) .getBoolean(PREF_KEY_HAS_READ_NO_ROOT_DIALOG, false); if (!hasReadNoRootDialog) { new AlertDialog.Builder(WifiListActivity.this) .setTitle(getString(R.string.wifilist_dialog_noroot_title)) .setMessage(getString(R.string.wifilist_dialog_noroot_msg)) .setPositiveButton(getString(R.string.action_got_it), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { PreferenceManager.getDefaultSharedPreferences(WifiListActivity.this) .edit() .putBoolean(PREF_KEY_HAS_READ_NO_ROOT_DIALOG, true) .apply(); dialogInterface.dismiss(); } }) .setCancelable(false) .create() .show(); } } } } private void setSavedKeysToWifiNetworks() { List<WifiNetwork> wifiNetworksWithKey = WifiKeysDataSource.getInstance().getSavedWifiWithKeys(); for (int i = 0; i < wifiNetworks.size(); i++) { for (int j = 0; j < wifiNetworksWithKey.size(); j++) { if (wifiNetworks.get(i).getSsid().equals(wifiNetworksWithKey.get(j).getSsid()) && wifiNetworks.get(i).getAuthType() == wifiNetworksWithKey.get(j).getAuthType()) { if (wifiNetworks.get(i).needsPassword()) { wifiNetworks.get(i).setKey(wifiNetworksWithKey.get(j).getKey()); wifiNetworkAdapter.notifyItemChanged(i); } } } } } }