Java tutorial
/*- * Copyright (C) 2009 Peter Baldwin * * 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 org.peterbaldwin.vlcremote.app; import org.apache.http.HttpResponse; import org.apache.http.ParseException; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import org.peterbaldwin.client.android.vlcremote.R; import org.peterbaldwin.vlcremote.preference.ProgressCategory; import org.peterbaldwin.vlcremote.receiver.PhoneStateChangedReceiver; import org.peterbaldwin.vlcremote.sweep.PortSweeper; import android.app.AlertDialog; import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.NetworkInfo; import android.net.Uri; import android.net.wifi.SupplicantState; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Looper; import android.os.SystemClock; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; import android.text.TextUtils; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.EditText; import android.widget.ListAdapter; import java.io.IOException; import java.net.HttpURLConnection; import java.text.MessageFormat; import java.util.ArrayList; @SuppressWarnings("deprecation") public final class PickServerActivity extends PreferenceActivity implements PortSweeper.Callback, DialogInterface.OnClickListener, OnPreferenceChangeListener { private static final String TAG = "PickServer"; private static final String PACKAGE_NAME = R.class.getPackage().getName(); private static final ComponentName PHONE_STATE_RECEIVER = new ComponentName(PACKAGE_NAME, PhoneStateChangedReceiver.class.getName()); public static final String EXTRA_PORT = "org.peterbaldwin.portsweep.intent.extra.PORT"; public static final String EXTRA_FILE = "org.peterbaldwin.portsweep.intent.extra.FILE"; public static final String EXTRA_WORKERS = "org.peterbaldwin.portsweep.intent.extra.WORKERS"; public static final String EXTRA_REMEMBERED = "org.peterbaldwin.portsweep.intent.extra.REMEMBERED"; private static final String KEY_WIFI = "wifi"; private static final String KEY_SERVERS = "servers"; private static final String KEY_ADD_SERVER = "add_server"; private static final String KEY_PAUSE_FOR_CALL = "pause_for_call"; public static final int DEFAULT_WORKERS = 16; private static final int DIALOG_ADD_SERVER = 1; private static final int MENU_SCAN = Menu.FIRST; private static final int CONTEXT_FORGET = Menu.FIRST; private static byte[] toByteArray(int i) { int i4 = (i >> 24) & 0xFF; int i3 = (i >> 16) & 0xFF; int i2 = (i >> 8) & 0xFF; int i1 = i & 0xFF; return new byte[] { (byte) i1, (byte) i2, (byte) i3, (byte) i4 }; } private PortSweeper mPortSweeper; private BroadcastReceiver mReceiver; private AlertDialog mDialogAddServer; private EditText mEditHostname; private EditText mEditPort; private String mFile; private int mPort; private int mWorkers; private long mCreateTime; private ArrayList<String> mRemembered; private CheckBoxPreference mPreferenceWiFi; private CheckBoxPreference mPreferencePauseForCall; private ProgressCategory mProgressCategory; private Preference mPreferenceAddServer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.server_settings); PreferenceScreen preferenceScreen = getPreferenceScreen(); mPreferenceWiFi = (CheckBoxPreference) preferenceScreen.findPreference(KEY_WIFI); mPreferencePauseForCall = (CheckBoxPreference) preferenceScreen.findPreference(KEY_PAUSE_FOR_CALL); mProgressCategory = (ProgressCategory) preferenceScreen.findPreference(KEY_SERVERS); mPreferenceAddServer = preferenceScreen.findPreference(KEY_ADD_SERVER); mPreferencePauseForCall.setOnPreferenceChangeListener(this); mPreferencePauseForCall.setChecked(getPauseForCall()); Intent intent = getIntent(); mPort = intent.getIntExtra(EXTRA_PORT, 0); if (mPort == 0) { throw new IllegalArgumentException("Port must be specified"); } mFile = intent.getStringExtra(EXTRA_FILE); if (mFile == null) { throw new IllegalArgumentException("File must be specified"); } mRemembered = intent.getStringArrayListExtra(EXTRA_REMEMBERED); if (mRemembered == null) { mRemembered = new ArrayList<String>(); } registerForContextMenu(getListView()); mWorkers = intent.getIntExtra(EXTRA_WORKERS, DEFAULT_WORKERS); mPortSweeper = (PortSweeper) getLastNonConfigurationInstance(); if (mPortSweeper == null) { mPortSweeper = createPortSweeper(); startSweep(); } mPortSweeper.setCallback(this); // Registering the receiver triggers a broadcast with the initial state. // To tell the difference between a broadcast triggered by registering a // receiver and a broadcast triggered by a true network event, note the // time and ignore all broadcasts for one second. mCreateTime = SystemClock.uptimeMillis(); mReceiver = new MyBroadcastReceiver(); // For robustness, update the connection status for all types of events. IntentFilter filter = new IntentFilter(); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); registerReceiver(mReceiver, filter); updateWifiInfo(); } boolean isInitialBroadcast() { return (SystemClock.uptimeMillis() - mCreateTime) < 1000; } @Override public Object onRetainNonConfigurationInstance() { Object nonConfigurationInstance = mPortSweeper; // TODO: Set callback to null without triggering NullPointerException in // the event that a callback happens while the Activity is recreating // itself. mPortSweeper = null; return nonConfigurationInstance; } private Preference createServerPreference(String server) { // Don't show port number in title if it's the default String title = server.endsWith(":8080") ? server.substring(0, server.lastIndexOf(':')) : server; Preference preference = new Preference(this); preference.setKey(server); preference.setTitle(title); preference.setPersistent(false); return preference; } private Preference createServerPreference(HttpResponse response) { String server = response.getFirstHeader(HTTP.TARGET_HOST).getValue(); Preference preference = createServerPreference(server); switch (response.getStatusLine().getStatusCode()) { case HttpURLConnection.HTTP_UNAUTHORIZED: preference.setSummary(getText(R.string.summary_password_protected)); break; case HttpURLConnection.HTTP_FORBIDDEN: preference.setSummary(getText(R.string.summary_forbidden)); preference.setEnabled(false); break; case HttpURLConnection.HTTP_OK: try { if (EntityUtils.toString(response.getEntity()).contains("--http-password")) { preference.setSummary(getText(R.string.summary_password_not_set)); preference.setEnabled(false); } } catch (ParseException e) { Log.e(TAG, "Failed to parse response", e); } catch (IOException e) { Log.e(TAG, "Failed to parse response", e); } break; } return preference; } @Override protected void onDestroy() { unregisterReceiver(mReceiver); mReceiver = null; if (mPortSweeper != null) { mPortSweeper.destory(); } super.onDestroy(); } private PortSweeper createPortSweeper() { PortSweeper.Callback callback = this; Looper looper = Looper.myLooper(); return new PortSweeper(mPort, mFile, mWorkers, callback, looper); } private WifiInfo getConnectionInfo() { Object service = getSystemService(WIFI_SERVICE); WifiManager manager = (WifiManager) service; WifiInfo info = manager.getConnectionInfo(); if (info != null) { SupplicantState state = info.getSupplicantState(); if (state.equals(SupplicantState.COMPLETED)) { return info; } } return null; } private byte[] getIpAddress() { WifiInfo info = getConnectionInfo(); if (info != null) { return toByteArray(info.getIpAddress()); } return null; } void startSweep() { byte[] ipAddress = getIpAddress(); if (ipAddress != null) { mPortSweeper.sweep(ipAddress); } } @Override protected void onStart() { super.onStart(); } @Override protected void onStop() { super.onStop(); } /** {@inheritDoc} */ public void onHostFound(HttpResponse response) { String server = response.getFirstHeader(HTTP.TARGET_HOST).getValue(); int responseCode = response.getStatusLine().getStatusCode(); switch (responseCode) { case HttpURLConnection.HTTP_OK: case HttpURLConnection.HTTP_FORBIDDEN: case HttpURLConnection.HTTP_UNAUTHORIZED: if (!mRemembered.contains(server)) { Preference preference = createServerPreference(response); mProgressCategory.addPreference(preference); } break; default: Log.d(TAG, "Unexpected response code: " + responseCode); break; } } @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_ADD_SERVER: AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.add_server); LayoutInflater inflater = getLayoutInflater(); View view = inflater.inflate(R.layout.add_server, null); mEditHostname = (EditText) view.findViewById(R.id.edit_hostname); mEditPort = (EditText) view.findViewById(R.id.edit_port); builder.setView(view); builder.setPositiveButton(R.string.ok, this); builder.setNegativeButton(R.string.cancel, this); mDialogAddServer = builder.create(); return mDialogAddServer; default: return super.onCreateDialog(id); } } /** {@inheritDoc} */ public void onClick(DialogInterface dialog, int which) { if (dialog == mDialogAddServer) { switch (which) { case DialogInterface.BUTTON_POSITIVE: String hostname = getHostname(); int port = getPort(); String server = hostname + ":" + port; pick(server); break; case DialogInterface.BUTTON_NEGATIVE: dialog.dismiss(); break; } } } private void pick(String server) { Intent data = new Intent(); Uri uri = Uri.parse("http://" + server); data.setData(uri); if (!mRemembered.contains(server)) { mRemembered.add(server); } data.putStringArrayListExtra(EXTRA_REMEMBERED, mRemembered); setResult(RESULT_OK, data); finish(); } private void forget(String server) { mRemembered.remove(server); int count = mProgressCategory.getPreferenceCount(); for (int i = 0; i < count; i++) { Preference preference = mProgressCategory.getPreference(i); if (server.equals(preference.getTitle().toString())) { mProgressCategory.removePreference(preference); break; } } // Send the updated list of remembered servers even if the activity is // canceled Intent data = new Intent(); data.putStringArrayListExtra(EXTRA_REMEMBERED, mRemembered); setResult(RESULT_CANCELED, data); } @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (preference == mPreferenceAddServer) { showDialog(DIALOG_ADD_SERVER); return true; } else if (preference == mPreferenceWiFi) { Intent intent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK); startActivity(intent); // Undo checkbox toggle updateWifiInfo(); return true; } else if (preference == mPreferencePauseForCall) { return super.onPreferenceTreeClick(preferenceScreen, preference); } else { String server = preference.getKey(); pick(server); return true; } } /** {@inheritDoc} */ public boolean onPreferenceChange(Preference preference, Object newValue) { if (preference == mPreferencePauseForCall) { setPauseForCall(Boolean.TRUE.equals(newValue)); return true; } else { return false; } } private Preference getPreferenceFromMenuInfo(ContextMenuInfo menuInfo) { if (menuInfo != null) { if (menuInfo instanceof AdapterContextMenuInfo) { AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo; PreferenceScreen screen = getPreferenceScreen(); ListAdapter root = screen.getRootAdapter(); Object item = root.getItem(adapterMenuInfo.position); return (Preference) item; } } return null; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); Preference preference = getPreferenceFromMenuInfo(menuInfo); if (preference != null) { String server = preference.getTitle().toString(); if (mRemembered.contains(server)) { menu.add(Menu.NONE, CONTEXT_FORGET, Menu.NONE, R.string.context_forget); } } } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case CONTEXT_FORGET: ContextMenuInfo menuInfo = item.getMenuInfo(); Preference preference = getPreferenceFromMenuInfo(menuInfo); if (preference != null) { String server = preference.getTitle().toString(); forget(server); } return true; default: return super.onContextItemSelected(item); } } /** {@inheritDoc} */ public void onProgress(int progress, int max) { if (progress == 0) { mProgressCategory.removeAll(); for (String server : mRemembered) { Preference preference = createServerPreference(server); preference.setSummary(R.string.summary_remembered); mProgressCategory.addPreference(preference); } } mProgressCategory.setProgress(progress != max); } private String getHostname() { return mEditHostname.getText().toString(); } private int getPort() { String value = String.valueOf(mEditPort.getText()); if (!TextUtils.isEmpty(value)) { try { return Integer.parseInt(value); } catch (NumberFormatException e) { Log.w(TAG, "Invalid port number: " + value); } } return mPort; } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuItem scan = menu.add(0, MENU_SCAN, 0, R.string.scan); scan.setIcon(R.drawable.ic_menu_scan_network); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_SCAN: startSweep(); return true; default: return super.onOptionsItemSelected(item); } } private void updateWifiInfo() { WifiInfo info = getConnectionInfo(); if (info != null) { mPreferenceWiFi.setChecked(true); String ssid = info.getSSID(); String template = getString(R.string.summary_wifi_connected); Object[] objects = { ssid != null ? ssid : "" }; CharSequence summary = MessageFormat.format(template, objects); mPreferenceWiFi.setSummary(summary); } else { mPreferenceWiFi.setChecked(false); mPreferenceWiFi.setSummary(R.string.summary_wifi_disconnected); } } private boolean getPauseForCall() { switch (getPackageManager().getComponentEnabledSetting(PHONE_STATE_RECEIVER)) { case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: return true; default: return false; } } private void setPauseForCall(boolean enabled) { getPackageManager().setComponentEnabledSetting(PHONE_STATE_RECEIVER, enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } private class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Bundle extras = intent.getExtras(); if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { NetworkInfo networkInfo = extras.getParcelable(WifiManager.EXTRA_NETWORK_INFO); NetworkInfo.State state = networkInfo.getState(); if (state == NetworkInfo.State.CONNECTED) { if (isInitialBroadcast()) { // Don't perform a sweep if the broadcast was triggered // as a result of a receiver being registered. } else { startSweep(); } } } updateWifiInfo(); } } }