Java tutorial
/* * Copyright (C) 2011-2012 Felix Bechstein * * This file is part of WifiBarcode. * * 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 de.ub0r.android.wifibarcode; import com.actionbarsherlock.app.SherlockActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.app.AlertDialog.Builder; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.net.Uri; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; import java.util.List; import de.ub0r.android.lib.ChangelogHelper; import de.ub0r.android.lib.Market; import de.ub0r.android.lib.Utils; import de.ub0r.android.logg0r.Log; /** * Main {@link SherlockActivity} showing wifi configuration and barcodes. * * @author flx */ public final class WifiBarcodeActivity extends SherlockActivity implements OnClickListener { /** * Tag for log output. */ private static final String TAG = "WifiBarcodeActivity"; /** * Extra: barcode's bitmap. */ static final String EXTRA_BARCODE = "barcode"; /** * Extra: barcode's title. */ static final String EXTRA_TITLE = "title"; /** * Cache barcodes. */ private BarcodeCache barcodes; /** * Local {@link Spinner}s. */ private Spinner mSpConfigs, mSpNetType; /** * Local {@link EditText}s. */ private EditText mEtSsid, mEtPassword; /** * BarCode's background color. */ private String bCBackgroundColor = "FFFFFF"; /** * BarCode's size. */ private String bCSize = "200x200"; /** * Extra: Got root? */ private static final String EXTRA_GOT_ROOT = "got_root"; /** * False if runAsRoot failed. */ private boolean gotRoot = true; private boolean mFirstLoad = true; /** * Cache barcodes. */ private static class BarcodeCache extends HashMap<String, Bitmap> { /** * Serial Version UID. */ private static final long serialVersionUID = -1563072588317040645L; /** * Cache dir. */ private final String cacheDir; /** * Default constructor. * * @param context {@link Context} */ public BarcodeCache(final Context context) { super(); //noinspection ConstantConditions cacheDir = context.getCacheDir().getAbsolutePath() + "/barcodes/"; File f = new File(cacheDir); if (!f.isDirectory()) { //noinspection ResultOfMethodCallIgnored f.mkdir(); } } @Override public Bitmap get(final Object key) { Log.d(TAG, "cache.get(", key, ")"); Bitmap b = super.get(key); if (b == null) { Log.v(TAG, "cache/miss"); try { // get bitmap from file system String url = (String) key; File f = getFile(url); if (f.exists()) { Log.i(TAG, "load barcode from file system.."); b = BitmapFactory.decodeStream(new FileInputStream(f)); if (b != null) { // save barcode to memory cache super.put(url, b); } } } catch (IOException e) { Log.e(TAG, "io error", e); } } return b; } /** * Load a bitmap from input stream and save it to file. * * @param key url * @param is {@link InputStream} * @return {@link Bitmap} */ public Bitmap put(final String key, final InputStream is) { Log.d(TAG, "cache.put(", key, ")"); if (key == null || is == null) { return null; } // final Bitmap b = BitmapFactory.decodeStream(is); // super.put(key, b); // put bitmap to file system File f = getFile(key); Log.i(TAG, "file ", f.getAbsolutePath(), " does not exist, write it.."); try { // f.createNewFile(); FileOutputStream os = new FileOutputStream(f); byte[] buffer = new byte[Utils.K]; int l; while ((l = is.read(buffer)) > 0) { Log.d(TAG, "write bytes: ", l); os.write(buffer, 0, l); } os.close(); Log.d(TAG, "done"); } catch (IOException e) { Log.e(TAG, "error writing file: ", f.getAbsolutePath(), e); } Bitmap b = BitmapFactory.decodeFile(f.getAbsolutePath()); super.put(key, b); return b; } /** * Get {@link File} object for a barcode's url. * * @param url url * @return {@link File} */ private File getFile(final String url) { final String ret = cacheDir + url.replaceAll(".*chart\\?cht=", "").replaceAll("%", "_") .replaceAll("\\|", "_").replaceAll("&", "_"); Log.d(TAG, "getFile(", url, "): ", ret); return new File(ret); } } /** * Show wifi configuration as {@link ArrayAdapter}. */ private static class WifiAdapter extends ArrayAdapter<WifiConfiguration> { /** * Passwords. */ private final HashMap<WifiConfiguration, String> passwords = new HashMap<>(); /** * Default constructor. * * @param context {@link Context} * @param textViewResourceId Resource for item views. */ public WifiAdapter(final Context context, final int textViewResourceId) { super(context, textViewResourceId); } @Override public View getView(final int position, final View convertView, final ViewGroup parent) { View v = super.getView(position, convertView, parent); assert v != null; ((TextView) v.findViewById(android.R.id.text1)) .setText(this.getItem(position).SSID.replaceAll("\"", "")); return v; } @Override public View getDropDownView(final int position, final View convertView, final ViewGroup parent) { View v = super.getDropDownView(position, convertView, parent); assert v != null; ((TextView) v.findViewById(android.R.id.text1)) .setText(this.getItem(position).SSID.replaceAll("\"", "")); return v; } @Override public void clear() { super.clear(); passwords.clear(); } /** * Add a {@link WifiConfiguration} with password. * * @param object {@link WifiConfiguration} * @param password password */ public void add(final WifiConfiguration object, final String password) { add(object); passwords.put(object, password); } /** * Get password for {@link WifiConfiguration}. * * @param position position * @return password */ public String getPassword(final int position) { WifiConfiguration wc = getItem(position); if (wc == null) { return null; } return passwords.get(wc); } } /** * Load barcodes in background. */ private class BarcodeLoader extends AsyncTask<String, Void, Exception> { @Override protected Exception doInBackground(final String... url) { DefaultHttpClient httpClient = new DefaultHttpClient(); for (String u : url) { try { Log.d(TAG, "load barcode: ", u); HttpResponse repsonse = httpClient.execute(new HttpGet(u)); if (repsonse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { WifiBarcodeActivity.this.barcodes.put(u, repsonse.getEntity().getContent()); } } catch (IOException e) { Log.e(TAG, "error fetching barcode", e); return e; } } return null; } @Override protected void onPostExecute(final Exception result) { WifiBarcodeActivity.this.showBarcode(true); if (result != null) { Toast.makeText(WifiBarcodeActivity.this, result.toString(), Toast.LENGTH_LONG).show(); Toast.makeText(WifiBarcodeActivity.this, R.string.error_get_barcode, Toast.LENGTH_LONG).show(); } } } /** * Encloses the incoming string inside double quotes, if it isn't already quoted. * * @param string : the input string * @return a quoted string, of the form "input". If the input string is null, it returns null as * well. */ private static String convertToQuotedString(final String string) { if (string == null) { return null; } if (TextUtils.isEmpty(string)) { return ""; } int lastPos = string.length() - 1; if (lastPos < 0 || (string.charAt(0) == '"' && string.charAt(lastPos) == '"')) { return string; } return '\"' + string + '\"'; } /** * Run command as root. * * @param command command * @return true, if command was successfully executed */ private static boolean runAsRoot(final String command) { Log.i(TAG, "running command as root: ", command); try { Runtime r = Runtime.getRuntime(); Process p = r.exec("su"); DataOutputStream d = new DataOutputStream(p.getOutputStream()); d.writeBytes(command); d.writeBytes("\nexit\n"); d.flush(); int retval = p.waitFor(); Log.i(TAG, "done"); return (retval == 0); } catch (Exception e) { Log.e(TAG, "runAsRoot", e); return false; } } /** * {@inheritDoc} */ @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ChangelogHelper.showChangelog(this, getString(R.string.changelog_), getString(R.string.app_name), R.array.updates, -1); if (savedInstanceState != null) { gotRoot = savedInstanceState.getBoolean(EXTRA_GOT_ROOT, true); mFirstLoad = savedInstanceState.getBoolean("mFirstLoad", true); } else { if (!this.getCacheFile().delete()) { Log.e(TAG, "error deleting file: ", getCacheFile().getAbsolutePath()); } } barcodes = new BarcodeCache(this); WifiAdapter adapter = new WifiAdapter(this, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); findViewById(R.id.add).setOnClickListener(this); findViewById(R.id.barcode).setOnClickListener(this); mEtSsid = (EditText) findViewById(R.id.ssid); mEtPassword = (EditText) findViewById(R.id.password); mSpConfigs = (Spinner) findViewById(R.id.configurations); mSpNetType = (Spinner) findViewById(R.id.networktype); mSpConfigs.setAdapter(adapter); mSpConfigs.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) { if (position == 0) { WifiBarcodeActivity.this.mEtSsid.setText(null); WifiBarcodeActivity.this.mEtSsid.setEnabled(true); WifiBarcodeActivity.this.mSpNetType.setEnabled(true); WifiBarcodeActivity.this.mSpNetType.setSelection(0); WifiBarcodeActivity.this.mEtPassword.setText(null); WifiBarcodeActivity.this.mEtPassword.setEnabled(true); } else { WifiAdapter a = (WifiAdapter) WifiBarcodeActivity.this.mSpConfigs.getAdapter(); WifiConfiguration wc = a.getItem(position); WifiBarcodeActivity.this.mEtSsid.setText(wc.SSID.replaceAll("\"", "")); WifiBarcodeActivity.this.mEtSsid.setEnabled(false); int i = 0; if (wc.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.SHARED)) { i = 1; } else if (wc.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) { i = 2; } WifiBarcodeActivity.this.mSpNetType.setSelection(i); WifiBarcodeActivity.this.mSpNetType.setEnabled(false); String p = a.getPassword(position); WifiBarcodeActivity.this.mEtPassword.setText(p); WifiBarcodeActivity.this.mEtPassword.setEnabled(i != 0 && TextUtils.isEmpty(p)); } WifiBarcodeActivity.this.showBarcode(true); WifiBarcodeActivity.this.findViewById(R.id.add).setVisibility(View.GONE); } @Override public void onNothingSelected(final AdapterView<?> parent) { // nothing to do } }); mSpNetType.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) { //noinspection ConstantConditions String p = WifiBarcodeActivity.this.mEtPassword.getText().toString(); WifiBarcodeActivity.this.mEtPassword.setEnabled( position != 0 && (WifiBarcodeActivity.this.mSpConfigs.getSelectedItemPosition() == 0 || TextUtils.isEmpty(p))); } @Override public void onNothingSelected(final AdapterView<?> parent) { // nothing to do } }); final int c = getResources().getColor(android.R.color.background_light); bCBackgroundColor = Integer.toHexString(Color.red(c)) + Integer.toHexString(Color.green(c)) + Integer.toHexString(Color.blue(c)); bCSize = getString(R.string.barcode_size); } /** * {@inheritDoc} */ @Override protected void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_GOT_ROOT, gotRoot); outState.putBoolean("mFirstLoad", mFirstLoad); } /** * {@inheritDoc} */ @Override public boolean onCreateOptionsMenu(final Menu menu) { getSupportMenuInflater().inflate(R.menu.menu, menu); return true; } /** * {@inheritDoc} */ @Override public boolean onOptionsItemSelected(final MenuItem item) { Log.d(TAG, "onOptionsItemSelected(", item.getItemId(), ")"); switch (item.getItemId()) { case R.id.item_generate: showBarcode(false); return true; case R.id.item_wifi_config: startActivity(new Intent("android.settings.WIFI_SETTINGS")); return true; case R.id.item_scan: try { Intent intent = new Intent("com.google.zxing.client.android.SCAN"); // intent.setPackage("com.google.zxing.client.android"); intent.putExtra("SCAN_MODE", "QR_CODE_MODE"); startActivityForResult(intent, 0); } catch (ActivityNotFoundException e) { Log.e(TAG, "failed launching scanner", e); Builder b = new Builder(this); b.setTitle(R.string.install_barcode_scanner_); b.setMessage(R.string.install_barcode_scanner_hint); b.setNegativeButton(android.R.string.cancel, null); b.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { Market.installApp(WifiBarcodeActivity.this, "com.google.zxing" + ".client.android", "http://code.google.com/p" + "/zxing/downloads/list"); } }); b.show(); } return true; case R.id.item_about: startActivity(new Intent(this, About.class)); return true; case R.id.item_more_apps: Market.searchMoreApps(this); return true; default: return false; } } /** * {@inheritDoc} */ @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { if (requestCode == 0) { if (resultCode == RESULT_OK) { final String contents = intent.getStringExtra("SCAN_RESULT"); Log.d(TAG, "got qr code: ", contents); parseResult(contents); } } } /** * {@inheritDoc} */ @Override public void onClick(final View v) { switch (v.getId()) { case R.id.add: addWifi(); break; case R.id.barcode: final String url = getUrl(); final Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url), this, ViewerActivity.class); i.putExtra(EXTRA_BARCODE, barcodes.get(url)); //noinspection ConstantConditions i.putExtra(EXTRA_TITLE, mEtSsid.getText().toString()); startActivity(i); break; default: break; } } /** * {@inheritDoc} */ @Override protected void onResume() { super.onResume(); loadWifiConfigurations(); } /** * Load wifi configurations. */ private void loadWifiConfigurations() { WifiAdapter adapter = (WifiAdapter) mSpConfigs.getAdapter(); WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE); List<WifiConfiguration> wcs = wm.getConfiguredNetworks(); String currentSSID = wm.getConnectionInfo().getSSID(); Log.d(TAG, "currentSSID=", currentSSID); WifiConfiguration custom = new WifiConfiguration(); custom.SSID = getString(R.string.custom); adapter.clear(); adapter.add(custom); flushWifiPasswords(); Log.d(TAG, "#wcs=", wcs == null ? "null" : wcs.size()); if (wcs != null) { int selected = -1; for (WifiConfiguration wc : wcs) { adapter.add(wc, getWifiPassword(wc)); Log.d(TAG, "wc.SSID=", wc.SSID); if (mFirstLoad && currentSSID != null && currentSSID.equals(wc.SSID)) { selected = adapter.getCount() - 1; Log.d(TAG, "selected=", selected); } } if (selected > 0) { // mFirstLoad == true mSpConfigs.setSelection(selected); } mFirstLoad = false; } } /** * Add wifi configuration. */ private void addWifi() { WifiConfiguration wc = new WifiConfiguration(); wc.allowedAuthAlgorithms.clear(); wc.allowedGroupCiphers.clear(); wc.allowedKeyManagement.clear(); wc.allowedPairwiseCiphers.clear(); wc.allowedProtocols.clear(); //noinspection ConstantConditions wc.SSID = convertToQuotedString(mEtSsid.getText().toString()); wc.hiddenSSID = true; //noinspection ConstantConditions String password = mEtPassword.getText().toString(); switch (mSpNetType.getSelectedItemPosition()) { case 1: // WEP wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); wc.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); wc.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED); int length = password.length(); // WEP-40, WEP-104, and 256-bit WEP (WEP-232?) if ((length == 10 || length == 26 || length == 58) && password.matches("[0-9A-Fa-f]*")) { wc.wepKeys[0] = password; } else { wc.wepKeys[0] = '"' + password + '"'; } break; case 2: // WPA wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); if (password.matches("[0-9A-Fa-f]{64}")) { wc.preSharedKey = password; } else { wc.preSharedKey = '"' + password + '"'; } break; default: // OPEN wc.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); break; } WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE); int netId = wm.addNetwork(wc); int msg; final boolean ret = wm.saveConfiguration(); if (ret) { wm.enableNetwork(netId, false); msg = R.string.wifi_added; } else { msg = R.string.wifi_failed; } Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); } /** * Parse result from QR Code. * * @param result content from qr code */ private void parseResult(final String result) { Log.d(TAG, "parseResult(", result, ")"); if (result == null || !result.startsWith("WIFI:")) { Log.e(TAG, "error parsing result: ", result); Toast.makeText(this, R.string.error_read_barcode, Toast.LENGTH_LONG).show(); return; } String[] c = result.substring("WIFI:".length()).split(";", 3); for (String line : c) { if (line.startsWith("S:")) { mEtSsid.setText(line.substring(2)); } else if (line.startsWith("T:NOPASS")) { mSpNetType.setSelection(0); } else if (line.startsWith("T:WEP")) { mSpNetType.setSelection(1); } else if (line.startsWith("T:WPA")) { mSpNetType.setSelection(2); } else if (line.startsWith("P:")) { mEtPassword.setText(line.substring(2).replaceAll(";?;$", "")); } } mSpConfigs.setSelection(0); findViewById(R.id.add).setVisibility(View.VISIBLE); } /** * Flush wifi password cache. */ private void flushWifiPasswords() { //noinspection ConstantConditions final String targetFile = getCacheDir().getAbsolutePath() + "/wpa_supplicant.conf"; File f = new File(targetFile); if (f.exists()) { //noinspection ResultOfMethodCallIgnored f.delete(); } } /** * @return file holding cached wpa_supplicant.conf. */ private File getCacheFile() { return new File(getCacheDir(), "wpa_supplicant.conf"); } /** * Get WiFi password. * * @param wc {@link WifiConfiguration} * @return password */ private String getWifiPassword(final WifiConfiguration wc) { Log.d(TAG, "getWifiPassword(", wc, ")"); File f = getCacheFile(); if (!f.exists()) { if (gotRoot) { final String command = "cat /data/misc/wifi/wpa_supplicant.conf" + " > " + f.getAbsolutePath() + "\n chmod 666 " + f.getAbsolutePath(); if (!runAsRoot(command)) { Toast.makeText(this, R.string.error_need_root, Toast.LENGTH_LONG).show(); gotRoot = false; return null; } } else { Log.w(TAG, "gotRoot=false"); return null; } } f = new File(f.getAbsolutePath()); if (!f.exists()) { Toast.makeText(this, R.string.error_read_file, Toast.LENGTH_LONG).show(); return null; } try { BufferedReader br = new BufferedReader(new FileReader(f)); String l; StringBuffer sb = new StringBuffer(); while ((l = br.readLine()) != null) { if (l.startsWith("network=") || l.equals("}")) { String config = sb.toString(); // parse it if (config.contains("ssid=" + wc.SSID)) { Log.d(TAG, "wifi config:"); Log.d(TAG, config); int i = config.indexOf("wep_key0="); int len; if (i < 0) { i = config.indexOf("psk="); len = "psk=".length(); } else { len = "wep_key0=".length(); } if (i < 0) { return null; } return config.substring(i + len + 1, config.indexOf("\n", i) - 1); } sb = new StringBuffer(); } sb.append(l).append("\n"); } br.close(); } catch (IOException e) { Log.e(TAG, "error reading file", e); Toast.makeText(this, R.string.error_read_file, Toast.LENGTH_LONG).show(); return null; } return null; } /** * Get current BarCode's URL. * * @return URL */ private String getUrl() { String url = "http://chart.apis.google.com/" + "chart?cht=qr&chs=" + bCSize + "&chld=2&chf=bg,s," + bCBackgroundColor + "&chl="; int type = mSpNetType.getSelectedItemPosition(); String[] types = getResources().getStringArray(R.array.networktypes); StringBuilder sb = new StringBuilder(); sb.append("WIFI:T:"); sb.append(types[type]); sb.append(";S:"); sb.append(mEtSsid.getText()); sb.append(";P:"); if (type == 0) { sb.append("nopass"); } else { sb.append(mEtPassword.getText()); } sb.append(";;"); try { url += URLEncoder.encode(sb.toString(), "UTF-8"); } catch (UnsupportedEncodingException e) { Log.e(TAG, "error url encoding barcode"); } return url; } /** * Show barcode. * * @param cacheOnly load only from cache */ private void showBarcode(final boolean cacheOnly) { final String url = getUrl(); if (!cacheOnly && !this.barcodes.containsKey(url)) { Log.i(TAG, "barcode not available, load it..."); BarcodeLoader loader = new BarcodeLoader(); loader.execute(url); } ImageView iv = (ImageView) findViewById(R.id.barcode); Bitmap bc = barcodes.get(url); if (bc != null) { iv.setImageBitmap(bc); iv.setVisibility(View.VISIBLE); findViewById(R.id.c2e).setVisibility(View.VISIBLE); findViewById(R.id.progress).setVisibility(View.GONE); } else { iv.setVisibility(View.GONE); findViewById(R.id.c2e).setVisibility(View.GONE); if (!cacheOnly) { findViewById(R.id.progress).setVisibility(View.VISIBLE); } } if (cacheOnly) { findViewById(R.id.progress).setVisibility(View.GONE); } } }