// -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- // vim:ts=2:sw=2:tw=80:et package net.wigle.wigleandroid; import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.res.Configuration; import android.location.Location; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.view.MenuItemCompat; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; 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.Button; import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; import net.wigle.wigleandroid.MainActivity.State; import net.wigle.wigleandroid.background.ApiListener; import net.wigle.wigleandroid.background.FileUploaderTask; import net.wigle.wigleandroid.background.ObservationUploader; import net.wigle.wigleandroid.background.TransferListener; import net.wigle.wigleandroid.listener.WifiReceiver; import net.wigle.wigleandroid.model.ConcurrentLinkedHashMap; import net.wigle.wigleandroid.model.Network; import net.wigle.wigleandroid.model.OUI; import net.wigle.wigleandroid.model.QueryArgs; import org.json.JSONObject; import java.text.NumberFormat; import java.util.Set; public final class ListFragment extends Fragment implements ApiListener, DialogListener { private static final int MENU_WAKELOCK = 12; private static final int MENU_SORT = 13; private static final int MENU_SCAN = 14; private static final int MENU_FILTER = 15; private static final int MENU_MUTE = 16; private static final int MENU_MAP = 17; private static final int SORT_DIALOG = 100; private static final int UPLOAD_DIALOG = 101; private static final int SSID_FILTER = 102; public static final float MIN_DISTANCE_ACCURACY = 32f; // preferences public static final String SHARED_PREFS = "WiglePrefs"; public static final String PREF_USERNAME = "username"; public static final String PREF_PASSWORD = "password"; public static final String PREF_AUTHNAME = "authname"; public static final String PREF_TOKEN = "token"; public static final String PREF_SHOW_CURRENT = "showCurrent"; public static final String PREF_BE_ANONYMOUS = "beAnonymous"; public static final String PREF_DONATE = "donate"; public static final String PREF_DB_MARKER = "dbMarker"; public static final String PREF_MAX_DB = "maxDbMarker"; public static final String PREF_NETS_UPLOADED = "netsUploaded"; public static final String PREF_SCAN_PERIOD_STILL = "scanPeriodStill"; public static final String PREF_SCAN_PERIOD = "scanPeriod"; public static final String PREF_SCAN_PERIOD_FAST = "scanPeriodFast"; public static final String GPS_SCAN_PERIOD = "gpsPeriod"; public static final String PREF_FOUND_SOUND = "foundSound"; public static final String PREF_FOUND_NEW_SOUND = "foundNewSound"; public static final String PREF_LANGUAGE = "speechLanguage"; public static final String PREF_RESET_WIFI_PERIOD = "resetWifiPeriod"; public static final String PREF_BATTERY_KILL_PERCENT = "batteryKillPercent"; public static final String PREF_MUTED = "muted"; public static final String PREF_WIFI_WAS_OFF = "wifiWasOff"; public static final String PREF_DISTANCE_RUN = "distRun"; public static final String PREF_DISTANCE_TOTAL = "distTotal"; public static final String PREF_DISTANCE_PREV_RUN = "distPrevRun"; public static final String PREF_MAP_ONLY_NEWDB = "mapOnlyNewDB"; public static final String PREF_PREV_LAT = "prevLat"; public static final String PREF_PREV_LON = "prevLon"; public static final String PREF_PREV_ZOOM = "prevZoom2"; public static final String PREF_LIST_SORT = "listSort"; public static final String PREF_SCAN_RUNNING = "scanRunning"; public static final String PREF_METRIC = "metric"; public static final String PREF_MAP_LABEL = "mapLabel"; public static final String PREF_MAP_CLUSTER = "mapCluster"; public static final String PREF_MAP_TRAFFIC = "mapTraffic"; public static final String PREF_CIRCLE_SIZE_MAP = "circleSizeMap"; public static final String PREF_USE_NETWORK_LOC = "useNetworkLoc"; public static final String PREF_DISABLE_TOAST = "disableToast"; // bool public static final String PREF_MAP_TYPE = "mapType"; public static final String PREF_BLOWED_UP = "blowedUp"; // what to speak on announcements public static final String PREF_SPEECH_PERIOD = "speechPeriod"; public static final String PREF_SPEECH_GPS = "speechGPS"; public static final String PREF_SPEAK_RUN = "speakRun"; public static final String PREF_SPEAK_NEW_WIFI = "speakNew"; public static final String PREF_SPEAK_NEW_CELL = "speakNewCell"; public static final String PREF_SPEAK_QUEUE = "speakQueue"; public static final String PREF_SPEAK_MILES = "speakMiles"; public static final String PREF_SPEAK_TIME = "speakTime"; public static final String PREF_SPEAK_BATTERY = "speakBattery"; public static final String PREF_SPEAK_SSID = "speakSsid"; public static final String PREF_SPEAK_WIFI_RESTART = "speakWifiRestart"; // map ssid filter public static final String PREF_MAPF_REGEX = "mapfRegex"; public static final String PREF_MAPF_INVERT = "mapfInvert"; public static final String PREF_MAPF_OPEN = "mapfOpen"; public static final String PREF_MAPF_WEP = "mapfWep"; public static final String PREF_MAPF_WPA = "mapfWpa"; public static final String PREF_MAPF_CELL = "mapfCell"; public static final String PREF_MAPF_ENABLED = "mapfEnabled"; public static final String FILTER_PREF_PREFIX = "LA"; // rank stats data public static final String PREF_RANK = "rank"; public static final String PREF_MONTH_RANK = "monthRank"; public static final String NETWORK_EXTRA_BSSID = "extraBssid"; public static final String NETWORK_EXTRA_IS_DB_RESULT = "extraIsDbResult"; public static final String ANONYMOUS = "anonymous"; public static final String WIFI_LOCK_NAME = "wigleWifiLock"; /** cross-activity communication */ public static class LameStatic { public Location location; public int runNets; public long newNets; public long newWifi; public long newCells; public int currNets; public int preQueueSize; public long dbNets; public long dbLocs; public DatabaseHelper dbHelper; public Set<String> runNetworks; public QueryArgs queryArgs; public ConcurrentLinkedHashMap<String,Network> networkCache; public OUI oui; } public static final LameStatic lameStatic = new LameStatic(); static { final long maxMemory = Runtime.getRuntime().maxMemory(); int cacheSize = 128; if (maxMemory > 200000000L) { cacheSize = 1024; } else if (maxMemory > 100000000L) { cacheSize = 512; } MainActivity.info("Heap: maxMemory: " + maxMemory + " cacheSize: " + cacheSize); lameStatic.networkCache = new ConcurrentLinkedHashMap<>(cacheSize); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.list, container, false); final State state = MainActivity.getStaticState(); MainActivity.info("setupUploadButton"); setupUploadButton(view); MainActivity.info("setupList"); setupList(view); MainActivity.info("setNetCountUI"); setNetCountUI(state, view); MainActivity.info("setStatusUI"); setStatusUI(view, null); MainActivity.info("setupLocation"); setupLocation(view); return view; } /** Called when the activity is first created. */ @Override public void onCreate( final Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); setHasOptionsMenu(true); } public void setNetCountUI( final State state ) { setNetCountUI(state, getView()); } private void setNetCountUI( final State state, final View view ) { if (view == null) { return; } TextView tv = (TextView) view.findViewById( R.id.stats_run ); tv.setText( getString(R.string.run) + ": " + state.wifiReceiver.getRunNetworkCount() ); tv = (TextView) view.findViewById( R.id.stats_new ); tv.setText( getString(R.string.new_word) + ": " + state.dbHelper.getNewNetworkCount() ); tv = (TextView) view.findViewById( R.id.stats_dbnets ); tv.setText(getString(R.string.db) + ": " + state.dbHelper.getNetworkCount()); } public void setStatusUI( String status ) { setStatusUI(getView(), status); } public void setStatusUI( final View view, final String status ) { if ( status != null && view != null ) { final TextView tv = (TextView) view.findViewById( R.id.status ); tv.setText( status ); } } @Override public void onPause() { MainActivity.info("LIST: paused."); super.onPause(); } @Override public void onResume() { MainActivity.info( "LIST: resumed."); super.onResume(); getActivity().setTitle(R.string.list_app_name); } @Override public void onStart() { MainActivity.info("LIST: start."); super.onStart(); } @Override public void onStop() { MainActivity.info( "LIST: stop."); super.onStop(); } @Override public void onDestroyView() { MainActivity.info( "LIST: onDestroyView."); super.onDestroyView(); } @Override public void onDestroy() { MainActivity.info( "LIST: destroy."); super.onDestroy(); } @Override public void onDetach() { MainActivity.info( "LIST: onDetach."); super.onDetach(); } /* Creates the menu items */ @Override public void onCreateOptionsMenu (final Menu menu, final MenuInflater inflater) { MenuItem item = menu.add(0, MENU_MAP, 0, getString(R.string.tab_map)); item.setIcon( android.R.drawable.ic_menu_mapmode ); MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); item = menu.add(0, MENU_FILTER, 0, getString(R.string.menu_ssid_filter)); item.setIcon(android.R.drawable.ic_menu_search); MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); item = menu.add(0, MENU_SORT, 0, getString(R.string.menu_sort)); item.setIcon( android.R.drawable.ic_menu_sort_alphabetically ); MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); MainActivity main = MainActivity.getMainActivity(this); final String scan = (main == null || main.isScanning()) ? getString(R.string.off) : getString(R.string.on); item = menu.add(0, MENU_SCAN, 0, getString(R.string.scan) + " " + scan); item.setIcon((main == null || main.isScanning()) ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play); final String wake = MainActivity.isScreenLocked(this) ? getString(R.string.menu_screen_sleep) : getString(R.string.menu_screen_wake); item = menu.add(0, MENU_WAKELOCK, 0, wake); item.setIcon( android.R.drawable.ic_menu_gallery ); final SharedPreferences prefs = getActivity().getSharedPreferences(SHARED_PREFS, 0); boolean muted = prefs.getBoolean(PREF_MUTED, true); item = menu.add(0, MENU_MUTE, 0, muted ? getString(R.string.play) : getString(R.string.mute)); item.setIcon( muted ? android.R.drawable.ic_media_play : android.R.drawable.ic_media_pause); // item = menu.add(0, MENU_SETTINGS, 0, getString(R.string.menu_settings)); // item.setIcon( android.R.drawable.ic_menu_preferences ); // item = menu.add(0, MENU_EXIT, 0, getString(R.string.menu_exit)); // item.setIcon( android.R.drawable.ic_menu_close_clear_cancel ); super.onCreateOptionsMenu(menu, inflater); } /* Handles item selections */ @Override public boolean onOptionsItemSelected( final MenuItem item ) { final MainActivity main = MainActivity.getMainActivity(this); switch ( item.getItemId() ) { case MENU_WAKELOCK: { boolean screenLocked = ! MainActivity.isScreenLocked( this ); MainActivity.setLockScreen( this, screenLocked ); final String wake = screenLocked ? getString(R.string.menu_screen_sleep) : getString(R.string.menu_screen_wake); item.setTitle( wake ); return true; } case MENU_SORT: { MainActivity.info("sort dialog"); onCreateDialog( SORT_DIALOG ); return true; } case MENU_SCAN: { if (main != null) { final boolean scanning = !main.isScanning(); main.handleScanChange(scanning); String name = getString(R.string.scan) + " " + (scanning ? getString(R.string.off) : getString(R.string.on)); item.setTitle(name); item.setIcon(scanning ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play); handleScanChange(main, getView()); } return true; } case MENU_FILTER: onCreateDialog(SSID_FILTER); return true; case MENU_MAP: // call over to finish if (main != null) main.selectFragment(MainActivity.MAP_TAB_POS); return true; case MENU_MUTE: final SharedPreferences prefs = getActivity().getSharedPreferences(SHARED_PREFS, 0); boolean muted = prefs.getBoolean(PREF_MUTED, true); muted = ! muted; Editor editor = prefs.edit(); editor.putBoolean(PREF_MUTED, muted); editor.apply(); item.setTitle(muted ? getString(R.string.play) : getString(R.string.mute)); item.setIcon(muted ? android.R.drawable.ic_media_play : android.R.drawable.ic_media_pause); return true; } return false; } private void handleScanChange(final MainActivity main, final View view ) { final boolean isScanning = main == null || main.isScanning(); MainActivity.info("list handleScanChange: isScanning now: " + isScanning ); if ( isScanning ) { setStatusUI(view, getString(R.string.list_scanning_on)); } else { setStatusUI(view, getString(R.string.list_scanning_off)); } } public void onCreateDialog( int which ) { DialogFragment dialogFragment = null; switch ( which ) { case SSID_FILTER: dialogFragment = MappingFragment.createSsidFilterDialog(FILTER_PREF_PREFIX); break; case SORT_DIALOG: dialogFragment = new SortDialog(); break; default: MainActivity.error( "unhandled dialog: " + which ); } if (dialogFragment != null) { final FragmentManager fm = getActivity().getSupportFragmentManager(); dialogFragment.show(fm, MainActivity.LIST_FRAGMENT_TAG); } } public static class SortDialog extends DialogFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final Dialog dialog = getDialog(); View view = inflater.inflate(R.layout.listdialog, container); dialog.setTitle(getString(R.string.sort_title)); TextView text = (TextView) view.findViewById( R.id.text ); text.setText( getString(R.string.sort_spin_label) ); final SharedPreferences prefs = getActivity().getSharedPreferences( SHARED_PREFS, 0 ); final Editor editor = prefs.edit(); Spinner spinner = (Spinner) view.findViewById( R.id.sort_spinner ); ArrayAdapter<String> adapter = new ArrayAdapter<>( getActivity(), android.R.layout.simple_spinner_item); final int[] listSorts = new int[]{ WifiReceiver.CHANNEL_COMPARE, WifiReceiver.CRYPTO_COMPARE, WifiReceiver.FIND_TIME_COMPARE, WifiReceiver.SIGNAL_COMPARE, WifiReceiver.SSID_COMPARE }; final String[] listSortName = new String[]{ getString(R.string.channel),getString(R.string.crypto), getString(R.string.found_time),getString(R.string.signal),getString(R.string.ssid) }; int listSort = prefs.getInt( PREF_LIST_SORT, WifiReceiver.SIGNAL_COMPARE ); int periodIndex = 0; for ( int i = 0; i < listSorts.length; i++ ) { adapter.add( listSortName[i] ); if ( listSort == listSorts[i] ) { periodIndex = i; } } adapter.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item ); spinner.setAdapter( adapter ); spinner.setSelection( periodIndex ); spinner.setOnItemSelectedListener( new OnItemSelectedListener() { @Override public void onItemSelected( final AdapterView<?> parent, final View v, final int position, final long id ) { // set pref final int listSort = listSorts[position]; MainActivity.info( PREF_LIST_SORT + " setting list sort: " + listSort ); editor.putInt( PREF_LIST_SORT, listSort ); editor.apply(); } @Override public void onNothingSelected( final AdapterView<?> arg0 ) {} }); Button ok = (Button) view.findViewById( R.id.listdialog_button ); ok.setOnClickListener( new OnClickListener() { @Override public void onClick( final View buttonView ) { try { dialog.dismiss(); } catch ( Exception ex ) { // guess it wasn't there anyways MainActivity.info( "exception dismissing sort dialog: " + ex ); } } } ); return view; } } // why is this even here? this is stupid. via: // http://stackoverflow.com/questions/456211/activity-restart-on-rotation-android @Override public void onConfigurationChanged( final Configuration newConfig ) { final MainActivity main = MainActivity.getMainActivity(this); final State state = MainActivity.getStaticState(); MainActivity.info( "LIST: on config change" ); MainActivity.setLocale( this.getActivity(), newConfig); super.onConfigurationChanged( newConfig ); // getActivity().setContentView( R.layout.list ); // have to redo linkages/listeners setupUploadButton(getView()); setNetCountUI( state, getView() ); setLocationUI(main, getView()); setStatusUI(getView(), state.previousStatus); } private void setupList( final View view ) { State state = MainActivity.getStaticState(); if (state.listAdapter == null) { state.listAdapter = new NetworkListAdapter( getActivity().getApplicationContext(), R.layout.row ); } // always set our current list adapter state.wifiReceiver.setListAdapter(state.listAdapter); final ListView listView = (ListView) view.findViewById( R.id.ListView01 ); setupListAdapter(listView, getActivity(), state.listAdapter, false); } public static void setupListAdapter( final ListView listView, final FragmentActivity activity, final NetworkListAdapter listAdapter, final boolean isDbResult) { listView.setAdapter(listAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, final int position, final long id) { final Network network = (Network) parent.getItemAtPosition(position); MainActivity.getNetworkCache().put(network.getBssid(), network); final Intent intent = new Intent(activity, NetworkActivity.class); intent.putExtra(NETWORK_EXTRA_BSSID, network.getBssid()); intent.putExtra(NETWORK_EXTRA_IS_DB_RESULT, isDbResult); activity.startActivity(intent); } }); } private void setupLocation( final View view ) { // set on UI if we already have one final MainActivity main = MainActivity.getMainActivity(this); setLocationUI(main, view); handleScanChange(main, view); } public void setLocationUI( final MainActivity main ) { setLocationUI( main, getView() ); } private void setLocationUI( final MainActivity main, final View view ) { final State state = main.getState(); if ( state.gpsListener == null ) { return; } if ( view == null ) { return; } try { TextView tv = (TextView) view.findViewById( R.id.LocationTextView06 ); tv.setText( getString(R.string.list_short_sats) + " " + state.gpsListener.getSatCount() ); final Location location = state.gpsListener.getLocation(); tv = (TextView) view.findViewById( R.id.LocationTextView01 ); String latText; if ( location == null ) { if ( main.isScanning() ) { latText = getString(R.string.list_waiting_gps); } else { latText = getString(R.string.list_scanning_off); } } else { latText = state.numberFormat8.format( location.getLatitude() ); } tv.setText( getString(R.string.list_short_lat) + " " + latText ); tv = (TextView) view.findViewById( R.id.LocationTextView02 ); tv.setText( getString(R.string.list_short_lon) + " " + (location == null ? "" : state.numberFormat8.format( location.getLongitude() ) ) ); tv = (TextView) view.findViewById( R.id.LocationTextView03 ); tv.setText( getString(R.string.list_speed) + " " + (location == null ? "" : metersPerSecondToSpeedString(state.numberFormat1, getActivity(), location.getSpeed()) ) ); TextView tv4 = (TextView) view.findViewById( R.id.LocationTextView04 ); TextView tv5 = (TextView) view.findViewById( R.id.LocationTextView05 ); if ( location == null ) { tv4.setText( "" ); tv5.setText( "" ); } else { String distString = DashboardFragment.metersToString( state.numberFormat0, getActivity(), location.getAccuracy(), true); tv4.setText( "+/- " + distString ); distString = DashboardFragment.metersToString( state.numberFormat0, getActivity(), (float) location.getAltitude(), true ); tv5.setText( getString(R.string.list_short_alt) + " " + distString ); } } catch ( IncompatibleClassChangeError ex ) { // yeah, saw this in the wild, who knows. MainActivity.error( "wierd ex: " + ex, ex); } } public static String metersPerSecondToSpeedString( final NumberFormat numberFormat, final Context context, final float metersPerSecond ) { final SharedPreferences prefs = context.getSharedPreferences( ListFragment.SHARED_PREFS, 0 ); final boolean metric = prefs.getBoolean( ListFragment.PREF_METRIC, false ); String retval; if ( metric ) { retval = numberFormat.format( metersPerSecond * 3.6 ) + " " + context.getString(R.string.kmph); } else { retval = numberFormat.format( metersPerSecond * 2.23693629f ) + " " + context.getString(R.string.mph); } return retval; } private void setupUploadButton( final View view ) { final Button button = (Button) view.findViewById( R.id.upload_button ); button.setOnClickListener( new OnClickListener() { @Override public void onClick( final View view ) { final MainActivity main = MainActivity.getMainActivity( ListFragment.this ); if (main == null) {return;} makeUploadDialog(main); } }); } public void makeUploadDialog(final MainActivity main) { final SharedPreferences prefs = main.getSharedPreferences( ListFragment.SHARED_PREFS, 0 ); final boolean beAnonymous = prefs.getBoolean(ListFragment.PREF_BE_ANONYMOUS, false); final String username = beAnonymous? "anonymous": prefs.getString( ListFragment.PREF_USERNAME, "anonymous" ); final String text = getString(R.string.list_upload) + "\n" + getString(R.string.username) + ": " + username; MainActivity.createConfirmation( ListFragment.this.getActivity(), text, MainActivity.LIST_TAB_POS, UPLOAD_DIALOG); } @Override public void handleDialog(final int dialogId) { switch (dialogId) { case UPLOAD_DIALOG: final State state = MainActivity.getStaticState(); uploadFile( state.dbHelper ); break; default: MainActivity.warn("ListFragment unhandled dialogId: " + dialogId); } } public void uploadFile( final DatabaseHelper dbHelper ){ MainActivity.info( "upload file" ); final MainActivity main = MainActivity.getMainActivity(this); if (main == null) { return; } final State state = main.getState(); main.setTransferring(); // actually need this Activity context, for dialogs // writeEntireDb and writeRun are both false, so PREF_DB_MARKER is used state.observationUploader = new ObservationUploader(main, ListFragment.lameStatic.dbHelper, this, false, false, false); try { state.observationUploader.startDownload(this); } catch (WiGLEAuthException waex) { MainActivity.warn("Authentication failure on run upload"); } } /** * ApiListener interface */ @Override public void requestComplete(final JSONObject json, final boolean isCache) throws WiGLEAuthException { final MainActivity main = MainActivity.getMainActivity( this ); if (main == null) { MainActivity.warn("No main for requestComplete"); } else { main.transferComplete(); } } }