package net.wigle.wigleandroid; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.location.LocationManager; import android.location.LocationProvider; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnErrorListener; import android.net.Uri; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.WifiLock; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.os.PowerManager; import android.provider.Settings; import android.support.annotation.NonNull; 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.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import net.wigle.wigleandroid.background.ObservationUploader; import net.wigle.wigleandroid.listener.BatteryLevelReceiver; import net.wigle.wigleandroid.listener.GPSListener; import net.wigle.wigleandroid.listener.PhoneState; import net.wigle.wigleandroid.listener.WifiReceiver; import net.wigle.wigleandroid.model.ConcurrentLinkedHashMap; import net.wigle.wigleandroid.model.Network; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Method; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import java.util.prefs.Preferences; import static android.location.LocationManager.GPS_PROVIDER; public final class MainActivity extends AppCompatActivity { //*** state that is retained *** public static class State { DatabaseHelper dbHelper; ServiceConnection serviceConnection; AtomicBoolean finishing; AtomicBoolean transferring; MediaPlayer soundPop; MediaPlayer soundNewPop; WifiLock wifiLock; GPSListener gpsListener; WifiReceiver wifiReceiver; NumberFormat numberFormat0; NumberFormat numberFormat1; NumberFormat numberFormat8; TTS tts; boolean inEmulator; PhoneState phoneState; ObservationUploader observationUploader; NetworkListAdapter listAdapter; String previousStatus; int currentTab; private final Fragment[] fragList = new Fragment[11]; private boolean screenLocked = false; private PowerManager.WakeLock wakeLock; private int logPointer = 0; private String[] logs = new String[20]; } private State state; // *** end of state that is retained *** static final Locale ORIG_LOCALE = Locale.getDefault(); // form auth public static final String TOKEN_URL = "https://api.wigle.net/api/v2/activate"; // no auth public static final String SITE_STATS_URL = "https://api.wigle.net/api/v2/stats/site"; public static final String RANK_STATS_URL = "https://api.wigle.net/api/v2/stats/standings"; public static final String NEWS_URL = "https://api.wigle.net/api/v2/news/latest"; // api token auth public static final String UPLOADS_STATS_URL = "https://api.wigle.net/api/v2/file/transactions"; public static final String USER_STATS_URL = "https://api.wigle.net/api/v2/stats/user"; public static final String OBSERVED_URL = "https://api.wigle.net/api/v2/network/mine"; public static final String FILE_POST_URL = "https://api.wigle.net/api/v2/file/upload"; private static final String LOG_TAG = "wigle"; public static final String ENCODING = "ISO-8859-1"; private static final int PERMISSIONS_REQUEST = 1; static final String ERROR_STACK_FILENAME = "errorstack"; static final String ERROR_REPORT_DO_EMAIL = "doEmail"; static final String ERROR_REPORT_DIALOG = "doDialog"; public static final long DEFAULT_SPEECH_PERIOD = 60L; public static final long DEFAULT_RESET_WIFI_PERIOD = 90000L; public static final long LOCATION_UPDATE_INTERVAL = 1000L; public static final long SCAN_STILL_DEFAULT = 3000L; public static final long SCAN_DEFAULT = 2000L; public static final long SCAN_FAST_DEFAULT = 1000L; public static final long DEFAULT_BATTERY_KILL_PERCENT = 2L; private static MainActivity mainActivity; private static ListFragment listActivity; private BatteryLevelReceiver batteryLevelReceiver; private boolean playServiceShown = false; private DrawerLayout mDrawerLayout; private ListView mDrawerList; private ActionBarDrawerToggle mDrawerToggle; private static final String STATE_FRAGMENT_TAG = "StateFragmentTag"; public static final String LIST_FRAGMENT_TAG = "ListFragmentTag"; public static final int LIST_TAB_POS = 0; public static final int MAP_TAB_POS = 1; public static final int DASH_TAB_POS = 2; public static final int DATA_TAB_POS = 3; public static final int NEWS_TAB_POS = 4; public static final int RANK_STATS_TAB_POS = 5; public static final int USER_STATS_TAB_POS = 6; public static final int UPLOADS_TAB_POS = 7; public static final int SETTINGS_TAB_POS = 8; public static final int EXIT_TAB_POS = 9; public static final int SITE_STATS_TAB_POS = 10; @SuppressWarnings("deprecation") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); info("MAIN onCreate. state: " + state); // set language setLocale(this); setContentView(R.layout.main); mainActivity = this; // set language setLocale(this); setupPermissions(); setupMenuDrawer(); // do some of our own error handling, write a file with the stack final UncaughtExceptionHandler origHandler = Thread.getDefaultUncaughtExceptionHandler(); if (!(origHandler instanceof WigleUncaughtExceptionHandler)) { Thread.setDefaultUncaughtExceptionHandler( new WigleUncaughtExceptionHandler(getApplicationContext(), origHandler)); } // test the error reporting // if( true ){ throw new RuntimeException( "weee" ); } final FragmentManager fm = getSupportFragmentManager(); // force the retained fragments to live fm.executePendingTransactions(); StateFragment stateFragment = (StateFragment) fm.findFragmentByTag(STATE_FRAGMENT_TAG); if (stateFragment != null && stateFragment.getState() != null) { info("MAIN: using retained stateFragment state"); // pry an orientation change, which calls destroy, but we get this from retained fragment state = stateFragment.getState(); // tell those that need it that we have a new context state.gpsListener.setMainActivity(this); state.wifiReceiver.setMainActivity(this); if (state.observationUploader != null) { state.observationUploader.setContext(this); } } else { info("MAIN: creating new state"); state = new State(); state.finishing = new AtomicBoolean(false); state.transferring = new AtomicBoolean(false); // set it up for retain stateFragment = new StateFragment(); stateFragment.setState(state); fm.beginTransaction().add(stateFragment, STATE_FRAGMENT_TAG).commit(); // new run, reset final SharedPreferences prefs = getSharedPreferences(ListFragment.SHARED_PREFS, 0); final float prevRun = prefs.getFloat(ListFragment.PREF_DISTANCE_RUN, 0f); Editor edit = prefs.edit(); edit.putFloat(ListFragment.PREF_DISTANCE_RUN, 0f); edit.putFloat(ListFragment.PREF_DISTANCE_PREV_RUN, prevRun); edit.apply(); } PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); if (state.wakeLock == null) { state.wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "DoNotDimScreen"); if (state.wakeLock.isHeld()) { state.wakeLock.release(); } } final String id = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); // DO NOT turn these into |=, they will cause older dalvik verifiers to freak out state.inEmulator = id == null; state.inEmulator = state.inEmulator || "sdk".equals(android.os.Build.PRODUCT); state.inEmulator = state.inEmulator || "google_sdk".equals(android.os.Build.PRODUCT); info("id: '" + id + "' inEmulator: " + state.inEmulator + " product: " + android.os.Build.PRODUCT); info("android release: '" + Build.VERSION.RELEASE); if (state.numberFormat0 == null) { state.numberFormat0 = NumberFormat.getNumberInstance(Locale.US); if (state.numberFormat0 instanceof DecimalFormat) { state.numberFormat0.setMaximumFractionDigits(0); } } if (state.numberFormat1 == null) { state.numberFormat1 = NumberFormat.getNumberInstance(Locale.US); if (state.numberFormat1 instanceof DecimalFormat) { state.numberFormat1.setMaximumFractionDigits(1); } } if (state.numberFormat8 == null) { state.numberFormat8 = NumberFormat.getNumberInstance(Locale.US); if (state.numberFormat8 instanceof DecimalFormat) { state.numberFormat8.setMaximumFractionDigits(8); } } info("setupService"); setupService(); info("setupDatabase"); setupDatabase(); info("setupBattery"); setupBattery(); info("setupSound"); setupSound(); info("setupWifi"); setupWifi(); info("setupLocation"); // must be after setupWifi setupLocation(); info("setup tabs"); if (savedInstanceState == null) { setupFragments(); } // rksh 20160202 - api/authuser secure preferences storage checkInitKeystore(); // show the list by default selectFragment(state.currentTab); info("onCreate setup complete"); } /** * migration method for viable APIs to switch to encrypted AUTH_TOKENs */ private void checkInitKeystore() { final SharedPreferences prefs = getApplicationContext(). getSharedPreferences(ListFragment.SHARED_PREFS, 0); if (TokenAccess.checkMigrateKeystoreVersion(prefs, this)) { // successful migration should remove the password value if (!prefs.getString(ListFragment.PREF_PASSWORD, "").isEmpty()) { final Editor editor = prefs.edit(); editor.remove(ListFragment.PREF_PASSWORD); editor.apply(); } } else { MainActivity.info("Not able to upgrade key storage."); } } private void setupPermissions() { if (Build.VERSION.SDK_INT >= 23) { final List<String> permissionsNeeded = new ArrayList<>(); final List<String> permissionsList = new ArrayList<>(); if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION)) { permissionsNeeded.add(mainActivity.getString(R.string.gps_permission)); } if (!addPermission(permissionsList, Manifest.permission.ACCESS_COARSE_LOCATION)) { permissionsNeeded.add(mainActivity.getString(R.string.cell_permission)); } addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE); if (!permissionsList.isEmpty()) { // The permission is NOT already granted. // Check if the user has been asked about this permission already and denied // it. If so, we want to give more explanation about why the permission is needed. // 20170324 rksh: disabled due to // https://stackoverflow.com/questions/35453759/android-screen-overlay-detected-message-if-user-is-trying-to-grant-a-permissio /*String message = mainActivity.getString(R.string.please_allow); for (int i = 0; i < permissionsNeeded.size(); i++) { if (i > 0) message += ", "; message += permissionsNeeded.get(i); } if (permissionsList.contains(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { message = mainActivity.getString(R.string.allow_storage); } // Show our own UI to explain to the user why we need to read the contacts // before actually requesting the permission and showing the default UI Toast.makeText(mainActivity, message, Toast.LENGTH_LONG).show();*/ MainActivity.info("no permission for " + permissionsNeeded); // Fire off an async request to actually get the permission // This will show the standard permission request dialog UI requestPermissions(permissionsList.toArray(new String[permissionsList.size()]), PERMISSIONS_REQUEST); } } } private boolean addPermission(List<String> permissionsList, String permission) { if (Build.VERSION.SDK_INT >= 23) { if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { permissionsList.add(permission); // Check for Rationale Option if (!shouldShowRequestPermissionRationale(permission)) return false; } } else { return false; } return true; } @Override public void onRequestPermissionsResult(final int requestCode, @NonNull final String permissions[], @NonNull final int[] grantResults) { switch (requestCode) { case PERMISSIONS_REQUEST: { info("location grant response permissions: " + Arrays.toString(permissions) + " grantResults: " + Arrays.toString(grantResults)); boolean restart = false; for (int i = 0; i < permissions.length; i++) { final String permission = permissions[i]; if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission) && grantResults[i] == PackageManager.PERMISSION_GRANTED) { restart = true; } } if (restart) { // restart the app now that we can talk to the database Toast.makeText(mainActivity, R.string.restart, Toast.LENGTH_LONG).show(); Intent i = getBaseContext().getPackageManager() .getLaunchIntentForPackage(getBaseContext().getPackageName()); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); finish(); startActivity(i); } return; } default: warn("Unhandled onRequestPermissionsResult code: " + requestCode); } } private void setupMenuDrawer() { // set up drawer menu final String[] menuTitles = new String[]{ getString(R.string.tab_list), getString(R.string.tab_map), getString(R.string.tab_dash), getString(R.string.tab_data), getString(R.string.tab_news), getString(R.string.tab_rank), getString(R.string.tab_stats), getString(R.string.tab_uploads), getString(R.string.menu_settings), getString(R.string.menu_exit), }; final int[] menuIcons = new int[]{ android.R.drawable.ic_menu_sort_by_size, android.R.drawable.ic_menu_mapmode, android.R.drawable.ic_menu_directions, android.R.drawable.ic_menu_save, android.R.drawable.ic_menu_agenda, android.R.drawable.ic_menu_sort_alphabetically, android.R.drawable.ic_menu_today, android.R.drawable.ic_menu_upload, android.R.drawable.ic_menu_preferences, android.R.drawable.ic_delete, }; mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerList = (ListView) findViewById(R.id.left_drawer); // Set the adapter for the list view mDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, R.id.drawer_list_text, menuTitles) { @Override public View getView(int position, View convertView, ViewGroup parent) { final LayoutInflater inflater = LayoutInflater.from(MainActivity.this); View view; if (convertView == null) { view = inflater.inflate(R.layout.drawer_list_item, parent, false); } else { view = convertView; } final TextView text = (TextView) view.findViewById(R.id.drawer_list_text); text.setText(menuTitles[position]); final ImageView image = (ImageView) view.findViewById(R.id.drawer_list_icon); image.setImageResource(menuIcons[position]); return view; } }); // Set the list's click listener mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerToggle = new ActionBarDrawerToggle( this, /* host Activity */ mDrawerLayout, /* DrawerLayout object */ R.string.drawer_open, /* "open drawer" description */ R.string.drawer_close /* "close drawer" description */ ) { /** Called when a drawer has settled in a completely closed state. */ public void onDrawerClosed(View view) { super.onDrawerClosed(view); } /** Called when a drawer has settled in a completely open state. */ public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); final ActionBar actionBar = getSupportActionBar(); if (actionBar != null) actionBar.setTitle("Menu"); } }; // Set the drawer toggle as the DrawerListener mDrawerLayout.setDrawerListener(mDrawerToggle); final ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeButtonEnabled(true); } // end drawer setup } private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { selectFragment(position); } } /** * Swaps fragments in the main content view */ public void selectFragment(int position) { if (position == EXIT_TAB_POS) { finish(); return; } final String[] fragmentTitles = new String[]{ getString(R.string.list_app_name), getString(R.string.mapping_app_name), getString(R.string.dashboard_app_name), getString(R.string.data_activity_name), getString(R.string.news_app_name), getString(R.string.rank_stats_app_name), getString(R.string.user_stats_app_name), getString(R.string.uploads_app_name), getString(R.string.settings_app_name), getString(R.string.menu_exit), getString(R.string.site_stats_app_name), }; final Fragment frag = state.fragList[position]; // Insert the fragment by replacing any existing fragment FragmentManager fragmentManager = getSupportFragmentManager(); try { fragmentManager.beginTransaction() .replace(R.id.tabcontent, frag) .commit(); } catch (final NullPointerException | IllegalStateException ex) { final String message = "exception in fragment switch: " + ex; error(message, ex); Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } // Highlight the selected item, update the title, and close the drawer mDrawerList.setItemChecked(position, true); mDrawerLayout.closeDrawer(mDrawerList); state.currentTab = position; setTitle(fragmentTitles[position]); } @Override public void setTitle(CharSequence title) { final ActionBar actionBar = getSupportActionBar(); if (actionBar != null) actionBar.setTitle(title); } private void setupFragments() { info("Creating ListFragment"); listActivity = new ListFragment(); Bundle bundle = new Bundle(); listActivity.setArguments(bundle); state.fragList[LIST_TAB_POS] = listActivity; info("Creating MappingFragment"); final MappingFragment map = new MappingFragment(); bundle = new Bundle(); map.setArguments(bundle); state.fragList[MAP_TAB_POS] = map; info("Creating DashboardFragment"); final DashboardFragment dash = new DashboardFragment(); bundle = new Bundle(); dash.setArguments(bundle); state.fragList[DASH_TAB_POS] = dash; info("Creating DataFragment"); final DataFragment data = new DataFragment(); bundle = new Bundle(); data.setArguments(bundle); state.fragList[DATA_TAB_POS] = data; info("Creating UserStatsFragment"); final UserStatsFragment userStats = new UserStatsFragment(); bundle = new Bundle(); userStats.setArguments(bundle); state.fragList[USER_STATS_TAB_POS] = userStats; info("Creating SiteStatsFragment"); final SiteStatsFragment siteStats = new SiteStatsFragment(); bundle = new Bundle(); siteStats.setArguments(bundle); state.fragList[SITE_STATS_TAB_POS] = siteStats; info("Creating NewsFragment"); final NewsFragment newsStats = new NewsFragment(); bundle = new Bundle(); newsStats.setArguments(bundle); state.fragList[NEWS_TAB_POS] = newsStats; info("Creating RankStatsFragment"); final RankStatsFragment rankStats = new RankStatsFragment(); bundle = new Bundle(); rankStats.setArguments(bundle); state.fragList[RANK_STATS_TAB_POS] = rankStats; info("Creating UploadsFragment"); final UploadsFragment uploads = new UploadsFragment(); bundle = new Bundle(); uploads.setArguments(bundle); state.fragList[UPLOADS_TAB_POS] = uploads; info("Creating SettingsFragment"); final SettingsFragment settings = new SettingsFragment(); bundle = new Bundle(); settings.setArguments(bundle); state.fragList[SETTINGS_TAB_POS] = settings; } private void handleIntent() { // Get the intent that started this activity final Intent intent = getIntent(); // Figure out what to do based on the intent type MainActivity.info("ShareActivity intent type: " + intent.getAction()); switch (intent.getAction()) { case Intent.ACTION_INSERT: MainActivity.getMainActivity().handleScanChange(true); break; case Intent.ACTION_DELETE: MainActivity.getMainActivity().handleScanChange(false); break; case Intent.ACTION_SYNC: MainActivity.getMainActivity().doUpload(); break; default: MainActivity.info("Unhandled intent action: " + intent.getAction()); } } @Override public boolean onMenuOpened(int featureId, Menu menu) { if (featureId == Window.FEATURE_ACTION_BAR && menu != null) { if (menu.getClass().getSimpleName().equals("MenuBuilder")) { try { Method m = menu.getClass().getDeclaredMethod( "setOptionalIconsVisible", Boolean.TYPE); m.setAccessible(true); m.invoke(menu, true); } catch (NoSuchMethodException ex) { error("onMenuOpened no such method: " + ex, ex); } catch (Exception ex) { error("onMenuOpened ex: " + ex, ex); } } } return super.onMenuOpened(featureId, menu); } // be careful with this public static MainActivity getMainActivity() { return mainActivity; } static void setLockScreen(Fragment fragment, boolean lockScreen) { final MainActivity main = getMainActivity(fragment); if (main != null) { main.setLockScreen(lockScreen); } } static boolean isScreenLocked(Fragment fragment) { final MainActivity main = getMainActivity(fragment); return main != null && main.getState().screenLocked; } @SuppressLint("Wakelock") private void setLockScreen(boolean lockScreen) { state.screenLocked = lockScreen; if (lockScreen) { if (!state.wakeLock.isHeld()) { MainActivity.info("acquire wake lock"); state.wakeLock.acquire(); } } else if (state.wakeLock.isHeld()) { MainActivity.info("release wake lock"); state.wakeLock.release(); } } // Fragment-style dialog public static class ConfirmationDialog extends DialogFragment { public static ConfirmationDialog newInstance(final String message, final int tabPos, final int dialogId) { final ConfirmationDialog frag = new ConfirmationDialog(); Bundle args = new Bundle(); args.putString("message", message); args.putInt("tabPos", tabPos); args.putInt("dialogId", dialogId); frag.setArguments(args); return frag; } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setCancelable(true); builder.setTitle("Confirmation"); builder.setMessage(getArguments().getString("message")); final int tabPos = getArguments().getInt("tabPos"); final int dialogId = getArguments().getInt("dialogId"); final AlertDialog ad = builder.create(); // ok ad.setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { try { dialog.dismiss(); final Activity activity = getActivity(); if (activity == null) { info("activity is null in dialog. tabPos: " + tabPos + " dialogId: " + dialogId); } else if (activity instanceof MainActivity) { final MainActivity mainActivity = (MainActivity) activity; if (mainActivity.getState() != null) { final Fragment fragment = mainActivity.getState().fragList[tabPos]; ((DialogListener) fragment).handleDialog(dialogId); } } else { ((DialogListener) activity).handleDialog(dialogId); } } catch (Exception ex) { // guess it wasn't there anyways MainActivity.info("exception handling fragment alert dialog: " + ex, ex); } } }); // cancel ad.setButton(DialogInterface.BUTTON_NEGATIVE, activity.getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { try { dialog.dismiss(); } catch (Exception ex) { // guess it wasn't there anyways MainActivity.info("exception dismissing fragment alert dialog: " + ex, ex); } } }); return ad; } } static void createConfirmation(final FragmentActivity activity, final String message, final int tabPos, final int dialogId) { try { final FragmentManager fm = activity.getSupportFragmentManager(); final ConfirmationDialog dialog = ConfirmationDialog.newInstance(message, tabPos, dialogId); final String tag = tabPos + "-" + dialogId + "-" + activity.getClass().getSimpleName(); info("tag: " + tag + " fm: " + fm); dialog.show(fm, tag); } catch (WindowManager.BadTokenException ex) { MainActivity.info("exception showing dialog, view probably changed: " + ex, ex); } catch (final IllegalStateException ex) { final String errorMessage = "Exception trying to show dialog: " + ex; MainActivity.error(errorMessage, ex); Toast.makeText(activity, errorMessage, Toast.LENGTH_LONG).show(); } } private void setupDatabase() { // could be set by nonconfig retain if (state.dbHelper == null) { state.dbHelper = new DatabaseHelper(getApplicationContext()); //state.dbHelper.checkDB(); state.dbHelper.start(); ListFragment.lameStatic.dbHelper = state.dbHelper; } } public static CheckBox prefSetCheckBox(final Context context, final View view, final int id, final String pref, final boolean def) { final SharedPreferences prefs = context.getSharedPreferences(ListFragment.SHARED_PREFS, 0); final CheckBox checkbox = (CheckBox) view.findViewById(id); checkbox.setChecked(prefs.getBoolean(pref, def)); return checkbox; } private static CheckBox prefSetCheckBox(final SharedPreferences prefs, final View view, final int id, final String pref, final boolean def) { final CheckBox checkbox = (CheckBox) view.findViewById(id); if (checkbox == null) { error("No checkbox for id: " + id); } else { checkbox.setChecked(prefs.getBoolean(pref, def)); } return checkbox; } public static CheckBox prefBackedCheckBox(final Fragment fragment, final View view, final int id, final String pref, final boolean def) { final SharedPreferences prefs = fragment.getActivity().getSharedPreferences(ListFragment.SHARED_PREFS, 0); final Editor editor = prefs.edit(); final CheckBox checkbox = prefSetCheckBox(prefs, view, id, pref, def); checkbox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { editor.putBoolean(pref, isChecked); editor.apply(); } }); return checkbox; } public static State getStaticState() { final MainActivity mainActivity = getMainActivity(); return mainActivity == null ? null : mainActivity.getState(); } public State getState() { return state; } static MainActivity getMainActivity(Fragment fragment) { final Activity activity = fragment.getActivity(); if (activity instanceof MainActivity) { return (MainActivity) activity; } else { info("not main activity: " + activity); } return null; } /** * safely get the canonical path, as this call throws exceptions on some devices */ public static String safeFilePath(final File file) { String retval = null; try { retval = file.getCanonicalPath(); } catch (Exception ex) { MainActivity.error("Failed to get filepath", ex); } if (retval == null) { retval = file.getAbsolutePath(); } return retval; } public static String getSDPath() { return MainActivity.safeFilePath(Environment.getExternalStorageDirectory()) + "/wiglewifi/"; } public static FileOutputStream createFile(final Context context, final String filename) throws IOException { final String filepath = getSDPath(); final File path = new File(filepath); final boolean hasSD = MainActivity.hasSD(); if (hasSD) { //noinspection ResultOfMethodCallIgnored path.mkdirs(); final String openString = filepath + filename; MainActivity.info("openString: " + openString); final File file = new File(openString); if (!file.exists()) { if (!file.createNewFile()) { throw new IOException("Could not create file: " + openString); } } return new FileOutputStream(file); } //noinspection deprecation return context.openFileOutput(filename, Context.MODE_WORLD_READABLE); } @Override public void onDestroy() { MainActivity.info("MAIN: destroy."); super.onDestroy(); try { info("unregister batteryLevelReceiver"); unregisterReceiver(batteryLevelReceiver); } catch (final IllegalArgumentException ex) { info("batteryLevelReceiver not registered: " + ex); } try { info("unregister wifiReceiver"); unregisterReceiver(state.wifiReceiver); } catch (final IllegalArgumentException ex) { info("wifiReceiver not registered: " + ex); } } @Override public void onSaveInstanceState(final Bundle outState) { info("MAIN: onSaveInstanceState"); super.onSaveInstanceState(outState); } @Override public void onPause() { MainActivity.info("MAIN: pause."); super.onPause(); // deal with wake lock if (state.wakeLock.isHeld()) { MainActivity.info("release wake lock"); state.wakeLock.release(); } } @Override public void onResume() { MainActivity.info("MAIN: resume."); super.onResume(); // deal with wake lock if (!state.wakeLock.isHeld() && state.screenLocked) { MainActivity.info("acquire wake lock"); state.wakeLock.acquire(); } final int serviceAvailable = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getApplicationContext()); info("GooglePlayServicesAvailable: " + serviceAvailable); if (serviceAvailable != ConnectionResult.SUCCESS && !playServiceShown) { error("service not available! " + serviceAvailable); final Dialog dialog = GooglePlayServicesUtil.getErrorDialog(serviceAvailable, this, 0); dialog.show(); playServiceShown = true; } } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. mDrawerToggle.syncState(); } @Override public void onPostResume() { MainActivity.info("MAIN: post resume."); super.onPostResume(); } @Override public void onConfigurationChanged(final Configuration newConfig) { MainActivity.info("MAIN: config changed"); setLocale(this, newConfig); super.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig); } @SuppressLint("ApplySharedPref") @Override public void onStart() { MainActivity.info("MAIN: start."); final SharedPreferences prefs = getSharedPreferences(ListFragment.SHARED_PREFS, 0); if (prefs.getBoolean(ListFragment.PREF_BLOWED_UP, false)) { prefs.edit().putBoolean(ListFragment.PREF_BLOWED_UP, false).commit(); // activate the email intent final Intent intent = new Intent(this, ErrorReportActivity.class); intent.putExtra( MainActivity.ERROR_REPORT_DO_EMAIL, true ); startActivity(intent); } super.onStart(); } @Override public void onStop() { MainActivity.info("MAIN: stop."); super.onStop(); } @Override public void onRestart() { MainActivity.info("MAIN: restart."); super.onRestart(); } public static Throwable getBaseThrowable(final Throwable throwable) { Throwable retval = throwable; while (retval.getCause() != null) { retval = retval.getCause(); } return retval; } public static String getBaseErrorMessage(Throwable throwable, final boolean withNewLine) { throwable = MainActivity.getBaseThrowable(throwable); final String newline = withNewLine ? "\n" : " "; return throwable.getClass().getSimpleName() + ":" + newline + throwable.getMessage(); } public static void setLocale(final Activity activity) { final Context context = activity.getBaseContext(); final Configuration config = context.getResources().getConfiguration(); setLocale(context, config); } public static void setLocale(final Context context, final Configuration config) { final SharedPreferences prefs = context.getSharedPreferences(ListFragment.SHARED_PREFS, 0); final String lang = prefs.getString(ListFragment.PREF_LANGUAGE, ""); final String current = config.locale.getLanguage(); MainActivity.info("current lang: " + current + " new lang: " + lang); Locale newLocale = null; if (!"".equals(lang) && !current.equals(lang)) { newLocale = new Locale(lang); } else if ("".equals(lang) && ORIG_LOCALE != null && !current.equals(ORIG_LOCALE.getLanguage())) { newLocale = ORIG_LOCALE; } if (newLocale != null) { Locale.setDefault(newLocale); config.locale = newLocale; MainActivity.info("setting locale: " + newLocale); context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics()); } } /** * create a mediaplayer for a given raw resource id. * * @param soundId the R.raw. id for a given sound * @return the mediaplayer for soundId or null if it could not be created. */ private MediaPlayer createMediaPlayer(final int soundId) { final MediaPlayer sound = createMp(getApplicationContext(), soundId); if (sound == null) { info("sound null from media player"); return null; } // try to figure out why sounds stops after a while sound.setOnErrorListener(new OnErrorListener() { @Override public boolean onError(final MediaPlayer mp, final int what, final int extra) { String whatString; switch (what) { case MediaPlayer.MEDIA_ERROR_UNKNOWN: whatString = "error unknown"; break; case MediaPlayer.MEDIA_ERROR_SERVER_DIED: whatString = "server died"; break; default: whatString = "not defined"; } info("media player error \"" + whatString + "\" what: " + what + " extra: " + extra + " mp: " + mp); return false; } }); return sound; } /** * externalize the file from a given resource id (if it dosen't already exist), write to our dir if there is one. * * @param context the context to use * @param resid the resource id * @param name the file name to write out * @return the uri of a file containing resid's resource */ @SuppressWarnings("deprecation") private static Uri resToFile(final Context context, final int resid, final String name) throws IOException { // throw it in our bag of fun. String openString = name; final boolean hasSD = hasSD(); if (hasSD) { final String filepath = MainActivity.safeFilePath(Environment.getExternalStorageDirectory()) + "/wiglewifi/"; final File path = new File(filepath); //noinspection ResultOfMethodCallIgnored path.mkdirs(); openString = filepath + name; } final File f = new File(openString); // see if it exists already if (!f.exists()) { info("causing " + MainActivity.safeFilePath(f) + " to be made"); // make it happen: if (!f.createNewFile()) { throw new IOException("Could not create file: " + openString); } InputStream is = null; FileOutputStream fos = null; try { is = context.getResources().openRawResource(resid); if (hasSD) { fos = new FileOutputStream(f); } else { // XXX: should this be using openString instead? baroo? fos = context.openFileOutput(name, Context.MODE_WORLD_READABLE); } final byte[] buff = new byte[1024]; int rv; while ((rv = is.read(buff)) > -1) { fos.write(buff, 0, rv); } } finally { if (fos != null) { fos.close(); } if (is != null) { is.close(); } } } return Uri.fromFile(f); } /** * create a media player (trying several paths if available) * * @param context the context to use * @param resid the resource to use * @return the media player for resid (or null if it wasn't creatable) */ private static MediaPlayer createMp(final Context context, final int resid) { try { MediaPlayer mp = MediaPlayer.create(context, resid); // this can fail for many reasons, but android 1.6 on archos5 definitely hates creating from resource if (mp == null) { Uri sounduri; // XXX: find a better way? baroo. if (resid == R.raw.pop) { sounduri = resToFile(context, resid, "pop.wav"); } else if (resid == R.raw.newpop) { sounduri = resToFile(context, resid, "newpop.wav"); } else { info("unknown raw sound id:" + resid); return null; } mp = MediaPlayer.create(context, sounduri); // may still end up null } return mp; } catch (IOException ex) { error("ioe create failed: " + ex, ex); // fall through } catch (IllegalArgumentException ex) { error("iae create failed: " + ex, ex); // fall through } catch (SecurityException ex) { error("se create failed: " + ex, ex); // fall through } catch (Resources.NotFoundException ex) { error("rnfe create failed(" + resid + "): " + ex, ex); } return null; } public boolean isMuted() { //noinspection SimplifiableIfStatement if (state.phoneState != null && state.phoneState.isPhoneActive()) { // always be quiet when the phone is active return true; } return getSharedPreferences(ListFragment.SHARED_PREFS, 0) .getBoolean(ListFragment.PREF_MUTED, true); } public static void sleep(final long sleep) { try { Thread.sleep(sleep); } catch (final InterruptedException ex) { // no worries } } public static void info(final String value) { Log.i(LOG_TAG, Thread.currentThread().getName() + "] " + value); saveLog(value); } public static void warn(final String value) { Log.w(LOG_TAG, Thread.currentThread().getName() + "] " + value); saveLog(value); } public static void error(final String value) { Log.e(LOG_TAG, Thread.currentThread().getName() + "] " + value); saveLog(value); } public static void info(final String value, final Throwable t) { Log.i(LOG_TAG, Thread.currentThread().getName() + "] " + value, t); saveLog(value); } public static void warn(final String value, final Throwable t) { Log.w(LOG_TAG, Thread.currentThread().getName() + "] " + value, t); saveLog(value); } public static void error(final String value, final Throwable t) { Log.e(LOG_TAG, Thread.currentThread().getName() + "] " + value, t); saveLog(value); } private static void saveLog(final String value) { final State state = getStaticState(); if (state == null) return; final int pointer = state.logPointer++ % state.logs.length; state.logs[pointer] = Thread.currentThread().getName() + "] " + value; if (pointer > 10000 * state.logs.length) { state.logPointer -= 100 * state.logs.length; } } /** * get the network LRU cache * * @return network cache */ public static ConcurrentLinkedHashMap<String, Network> getNetworkCache() { return ListFragment.lameStatic.networkCache; } public static void addNetworkToMap(final Network network) { if (getStaticState().currentTab == MAP_TAB_POS) { // Map is visible, give it the new network final State state = mainActivity.getState(); final MappingFragment f = (MappingFragment) state.fragList[MAP_TAB_POS]; if (f != null) { f.addNetwork(network); } } } public static void updateNetworkOnMap(final Network network) { if (getStaticState().currentTab == MAP_TAB_POS) { // Map is visible, give it the new network final State state = mainActivity.getState(); final MappingFragment f = (MappingFragment) state.fragList[MAP_TAB_POS]; if (f != null) { f.updateNetwork(network); } } } public static void reclusterMap() { if (getStaticState().currentTab == MAP_TAB_POS) { // Map is visible, give it the new network final State state = mainActivity.getState(); final MappingFragment f = (MappingFragment) state.fragList[MAP_TAB_POS]; if (f != null) { f.reCluster(); } } } public static void writeError(final Thread thread, final Throwable throwable, final Context context) { writeError(thread, throwable, context, null); } public static void writeError(final Thread thread, final Throwable throwable, final Context context, final String detail) { try { final String error = "Thread: " + thread + " throwable: " + throwable; error(error, throwable); if (hasSD()) { File file = new File(MainActivity.safeFilePath(Environment.getExternalStorageDirectory()) + "/wiglewifi/"); //noinspection ResultOfMethodCallIgnored file.mkdirs(); file = new File(MainActivity.safeFilePath(Environment.getExternalStorageDirectory()) + "/wiglewifi/" + ERROR_STACK_FILENAME + "_" + System.currentTimeMillis() + ".txt"); error("Writing stackfile to: " + MainActivity.safeFilePath(file) + "/" + file.getName()); if (!file.exists()) { if (!file.createNewFile()) { throw new IOException("Cannot create file: " + file); } } final FileOutputStream fos = new FileOutputStream(file); try { final String baseErrorMessage = MainActivity.getBaseErrorMessage(throwable, false); StringBuilder builder = new StringBuilder("WigleWifi error log - "); final DateFormat format = SimpleDateFormat.getDateTimeInstance(); builder.append(format.format(new Date())).append("\n"); final PackageManager pm = context.getPackageManager(); final PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); builder.append("versionName: ").append(pi.versionName).append("\n"); builder.append("baseError: ").append(baseErrorMessage).append("\n\n"); if (detail != null) { builder.append("detail: ").append(detail).append("\n"); } builder.append("packageName: ").append(pi.packageName).append("\n"); builder.append("MODEL: ").append(android.os.Build.MODEL).append("\n"); builder.append("RELEASE: ").append(android.os.Build.VERSION.RELEASE).append("\n"); builder.append("BOARD: ").append(android.os.Build.BOARD).append("\n"); builder.append("BRAND: ").append(android.os.Build.BRAND).append("\n"); // android 1.6 android.os.Build.CPU_ABI; builder.append("DEVICE: ").append(android.os.Build.DEVICE).append("\n"); builder.append("DISPLAY: ").append(android.os.Build.DISPLAY).append("\n"); builder.append("FINGERPRINT: ").append(android.os.Build.FINGERPRINT).append("\n"); builder.append("HOST: ").append(android.os.Build.HOST).append("\n"); builder.append("ID: ").append(android.os.Build.ID).append("\n"); // android 1.6: android.os.Build.MANUFACTURER; builder.append("PRODUCT: ").append(android.os.Build.PRODUCT).append("\n"); builder.append("TAGS: ").append(android.os.Build.TAGS).append("\n"); builder.append("TIME: ").append(android.os.Build.TIME).append("\n"); builder.append("TYPE: ").append(android.os.Build.TYPE).append("\n"); builder.append("USER: ").append(android.os.Build.USER).append("\n"); // write to file fos.write(builder.toString().getBytes(ENCODING)); } catch (Throwable er) { // ohwell error("error getting data for error: " + er, er); } fos.write((error + "\n\n").getBytes(ENCODING)); throwable.printStackTrace(new PrintStream(fos)); try { fos.write((error + "\n\n").getBytes(ENCODING)); final State state = getStaticState(); final String[] logs = state.logs; int pointer = state.logPointer; final int maxPointer = pointer + logs.length; for (int i = pointer; i < maxPointer; i++) { final String log = logs[i % logs.length]; if (log != null) { fos.write(log.getBytes(ENCODING)); fos.write("\n".getBytes(ENCODING)); } } } catch (Throwable er) { // ohwell error("error getting logs for error: " + er, er); } fos.close(); } } catch (final Exception ex) { error("error logging error: " + ex, ex); ex.printStackTrace(); } } public static boolean hasSD() { File sdCard = new File(MainActivity.safeFilePath(Environment.getExternalStorageDirectory()) + "/"); MainActivity.info("exists: " + sdCard.exists() + " dir: " + sdCard.isDirectory() + " read: " + sdCard.canRead() + " write: " + sdCard.canWrite() + " path: " + sdCard.getAbsolutePath()); return sdCard.exists() && sdCard.isDirectory() && sdCard.canRead() && sdCard.canWrite(); } private void setupSound() { // could have been retained if (state.soundPop == null) { state.soundPop = createMediaPlayer(R.raw.pop); } if (state.soundNewPop == null) { state.soundNewPop = createMediaPlayer(R.raw.newpop); } // make volume change "media" setVolumeControlStream(AudioManager.STREAM_MUSIC); try { if (TTS.hasTTS()) { // don't reuse an old one, has to be on *this* context if (state.tts != null) { state.tts.shutdown(); } // this has to have the parent activity, for whatever wacky reasons state.tts = new TTS(this); } } catch (Exception ex) { error("exception setting TTS: " + ex, ex); } TelephonyManager tele = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); if (tele != null && state.phoneState == null) { state.phoneState = new PhoneState(); final int signal_strengths = 256; try { tele.listen(state.phoneState, PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_CALL_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS | signal_strengths); } catch (SecurityException ex) { info("cannot get call state, will play audio over any telephone calls: " + ex); } } } public boolean inEmulator() { return state.inEmulator; } public BatteryLevelReceiver getBatteryLevelReceiver() { return batteryLevelReceiver; } public GPSListener getGPSListener() { return state.gpsListener; } public PhoneState getPhoneState() { return state.phoneState; } @Override public boolean isFinishing() { return state.finishing.get(); } public boolean isTransferring() { return state.transferring.get(); } public boolean isScanning() { return isScanning(this); } public static boolean isScanning(final Context context) { final SharedPreferences prefs = context.getSharedPreferences(ListFragment.SHARED_PREFS, 0); return prefs.getBoolean(ListFragment.PREF_SCAN_RUNNING, true); } public void playNewNetSound() { try { if (state.soundNewPop != null && !state.soundNewPop.isPlaying()) { // play sound on something new state.soundNewPop.start(); } else { MainActivity.info("soundNewPop is playing or null"); } } catch (IllegalStateException ex) { // ohwell, likely already playing MainActivity.info("exception trying to play sound: " + ex); } } public void playRunNetSound() { try { if (state.soundPop != null && !state.soundPop.isPlaying()) { // play sound on something new state.soundPop.start(); } else { MainActivity.info("soundPop is playing or null"); } } catch (IllegalStateException ex) { // ohwell, likely already playing MainActivity.info("exception trying to play sound: " + ex); } } private void setupWifi() { // warn about turning off network notification @SuppressWarnings("deprecation") final String notifOn = Settings.Secure.getString(getContentResolver(), Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON); if (notifOn != null && "1".equals(notifOn) && state.wifiReceiver == null) { Toast.makeText(this, getString(R.string.best_results), Toast.LENGTH_LONG).show(); } final WifiManager wifiManager = (WifiManager) this.getApplicationContext(). getSystemService(Context.WIFI_SERVICE); final SharedPreferences prefs = getSharedPreferences(ListFragment.SHARED_PREFS, 0); final Editor edit = prefs.edit(); // keep track of for later boolean turnedWifiOn = false; if (!wifiManager.isWifiEnabled()) { // tell user, cuz this takes a little while Toast.makeText(this, getString(R.string.turn_on_wifi), Toast.LENGTH_LONG).show(); // save so we can turn it back off when we exit edit.putBoolean(ListFragment.PREF_WIFI_WAS_OFF, true); // just turn it on, but not in emulator cuz it crashes it if (!state.inEmulator) { MainActivity.info("turning on wifi"); wifiManager.setWifiEnabled(true); MainActivity.info("wifi on"); turnedWifiOn = true; } } else { edit.putBoolean(ListFragment.PREF_WIFI_WAS_OFF, false); } edit.apply(); if (state.wifiReceiver == null) { MainActivity.info("new wifiReceiver"); // wifi scan listener // this receiver is the main workhorse of the entire app state.wifiReceiver = new WifiReceiver(this, state.dbHelper); state.wifiReceiver.setupWifiTimer(turnedWifiOn); } // register wifi receiver setupWifiReceiverIntent(); if (state.wifiLock == null) { MainActivity.info("lock wifi radio on"); // lock the radio on state.wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY, ListFragment.WIFI_LOCK_NAME); state.wifiLock.acquire(); } } private void setupWifiReceiverIntent() { // register MainActivity.info("register BroadcastReceiver"); final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); registerReceiver(state.wifiReceiver, intentFilter); } /** * Computes the battery level by registering a receiver to the intent triggered * by a battery status/level change. */ private void setupBattery() { if (batteryLevelReceiver == null) { batteryLevelReceiver = new BatteryLevelReceiver(); IntentFilter batteryLevelFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); registerReceiver(batteryLevelReceiver, batteryLevelFilter); } } public void setTransferring() { state.transferring.set(true); } public void scheduleScan() { state.wifiReceiver.scheduleScan(); } public void speak(final String string) { if (!MainActivity.getMainActivity().isMuted() && state.tts != null) { state.tts.speak(string); } } public void interruptSpeak() { if (state.tts != null) { state.tts.stop(); } } private void setupService() { // could be set by nonconfig retain if (state.serviceConnection == null) { final Intent serviceIntent = new Intent(getApplicationContext(), WigleService.class); final ComponentName compName = startService(serviceIntent); if (compName == null) { MainActivity.error("startService() failed!"); } else { MainActivity.info("service started ok: " + compName); } state.serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(final ComponentName name, final IBinder iBinder) { MainActivity.info(name + " service connected"); } @Override public void onServiceDisconnected(final ComponentName name) { MainActivity.info(name + " service disconnected"); } }; int flags = 0; // have to use the app context to bind to the service, cuz we're in tabs // http://code.google.com/p/android/issues/detail?id=2483#c2 final boolean bound = getApplicationContext().bindService(serviceIntent, state.serviceConnection, flags); MainActivity.info("service bound: " + bound); } } private void setupLocation() { final LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); try { // check if there is a gps final LocationProvider locProvider = locationManager.getProvider(GPS_PROVIDER); if (locProvider == null) { Toast.makeText(this, getString(R.string.no_gps_device), Toast.LENGTH_LONG).show(); } else if (!locationManager.isProviderEnabled(GPS_PROVIDER)) { // gps exists, but isn't on Toast.makeText(this, getString(R.string.turn_on_gps), Toast.LENGTH_LONG).show(); final Intent myIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); try { startActivity(myIntent); } catch (Exception ex) { error("exception trying to start location activity: " + ex, ex); } } } catch (final SecurityException ex) { info("Security exception in setupLocation: " + ex); return; } if (state.gpsListener == null) { // force a listener to be created final SharedPreferences prefs = getSharedPreferences(ListFragment.SHARED_PREFS, 0); internalHandleScanChange(prefs.getBoolean(ListFragment.PREF_SCAN_RUNNING, true)); } } public void handleScanChange(final boolean isScanning) { final boolean oldIsScanning = isScanning(); if (isScanning == oldIsScanning) { info("main handleScanChange: no difference, returning"); } final Editor edit = getSharedPreferences(ListFragment.SHARED_PREFS, 0).edit(); edit.putBoolean(ListFragment.PREF_SCAN_RUNNING, isScanning); edit.apply(); internalHandleScanChange(isScanning); } private void internalHandleScanChange(final boolean isScanning) { info("main internalHandleScanChange: isScanning now: " + isScanning); if (isScanning) { if (listActivity != null) { listActivity.setStatusUI(getString(R.string.list_scanning_on)); } if (state.wifiReceiver != null) { state.wifiReceiver.updateLastScanResponseTime(); } // turn on location updates this.setLocationUpdates(getLocationSetPeriod(), 0f); if (!state.wifiLock.isHeld()) { state.wifiLock.acquire(); } } else { if (listActivity != null) { listActivity.setStatusUI(getString(R.string.list_scanning_off)); } // turn off location updates this.setLocationUpdates(0L, 0f); state.gpsListener.handleScanStop(); if (state.wifiLock.isHeld()) { try { state.wifiLock.release(); } catch (SecurityException ex) { // a case where we have a leftover lock from another run? MainActivity.info("exception releasing wifilock: " + ex); } } } } /** * resets the gps listener to the requested update time and distance. * an updateIntervalMillis of <= 0 will not register for updates. */ public void setLocationUpdates(final long updateIntervalMillis, final float updateMeters) { try { internalSetLocationUpdates(updateIntervalMillis, updateMeters); } catch (final SecurityException ex) { error("Security exception in setLocationUpdates: " + ex, ex); } } private void internalSetLocationUpdates(final long updateIntervalMillis, final float updateMeters) throws SecurityException { final LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); if (state.gpsListener != null) { // remove any old requests locationManager.removeUpdates(state.gpsListener); locationManager.removeGpsStatusListener(state.gpsListener); } // create a new listener to try and get around the gps stopping bug state.gpsListener = new GPSListener(this); state.gpsListener.setMapListener(MappingFragment.STATIC_LOCATION_LISTENER); try { locationManager.addGpsStatusListener(state.gpsListener); } catch (final SecurityException ex) { info("Security exception adding status listener: " + ex, ex); } final SharedPreferences prefs = getSharedPreferences(ListFragment.SHARED_PREFS, 0); final boolean useNetworkLoc = prefs.getBoolean(ListFragment.PREF_USE_NETWORK_LOC, false); final List<String> providers = locationManager.getAllProviders(); if (providers != null) { for (String provider : providers) { MainActivity.info("available provider: " + provider + " updateIntervalMillis: " + updateIntervalMillis); if (!useNetworkLoc && LocationManager.NETWORK_PROVIDER.equals(provider)) { // skip! continue; } if (!"passive".equals(provider) && updateIntervalMillis > 0L) { MainActivity.info("using provider: " + provider); try { locationManager.requestLocationUpdates(provider, updateIntervalMillis, updateMeters, state.gpsListener); } catch (final SecurityException ex) { info("Security exception adding status listener: " + ex, ex); } } } if (updateIntervalMillis <= 0L) { info("removing location listener: " + state.gpsListener); try { locationManager.removeUpdates(state.gpsListener); } catch (final SecurityException ex) { info("Security exception removing status listener: " + ex, ex); } } } } public long getLocationSetPeriod() { final SharedPreferences prefs = getSharedPreferences(ListFragment.SHARED_PREFS, 0); long setPeriod = prefs.getLong(ListFragment.GPS_SCAN_PERIOD, MainActivity.LOCATION_UPDATE_INTERVAL); if (setPeriod == 0) { if (state.wifiReceiver == null) { setPeriod = MainActivity.LOCATION_UPDATE_INTERVAL; } else { setPeriod = Math.max(state.wifiReceiver.getScanPeriod(), MainActivity.LOCATION_UPDATE_INTERVAL); } } return setPeriod; } public void setLocationUpdates() { final long setPeriod = getLocationSetPeriod(); setLocationUpdates(setPeriod, 0f); } /** * TransferListener interface */ public void transferComplete() { state.transferring.set(false); MainActivity.info("transfer complete"); // start a scan to get the ball rolling again if this is non-stop mode scheduleScan(); state.observationUploader = null; } public void setLocationUI() { // tell list about new location if (listActivity != null) { listActivity.setLocationUI(this); } } public void setNetCountUI() { // tell list if (listActivity != null) { listActivity.setNetCountUI(getState()); } } public void setStatusUI(String status) { if (status == null) { status = state.previousStatus; } if (status != null) { // keep around a previous, for orientation changes state.previousStatus = status; if (listActivity != null) { // tell list listActivity.setStatusUI(status); } } } @Override public boolean onCreateOptionsMenu(final Menu menu) { info("MAIN: onCreateOptionsMenu."); return true; } @Override public boolean onOptionsItemSelected(final MenuItem item) { // Pass the event to ActionBarDrawerToggle, if it returns // true, then it has handled the app icon touch event return mDrawerToggle.onOptionsItemSelected(item); } //@Override @Override public void finish() { info("MAIN: finish. networks: " + state.wifiReceiver.getRunNetworkCount()); final boolean wasFinishing = state.finishing.getAndSet(true); if (wasFinishing) { info("MAIN: finish called twice!"); } // interrupt this just in case final ObservationUploader observationUploader = state.observationUploader; if (observationUploader != null) { observationUploader.setInterrupted(); } if (state.gpsListener != null) { // save our location for later runs state.gpsListener.saveLocation(); } // close the db. not in destroy, because it'll still write after that. state.dbHelper.close(); final LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); if (state.gpsListener != null) { locationManager.removeGpsStatusListener(state.gpsListener); try { locationManager.removeUpdates(state.gpsListener); } catch (final SecurityException ex) { error("SecurityException on finish: " + ex, ex); } } // stop the service, so when we die it's both stopped and unbound and will die final Intent serviceIntent = new Intent(this, WigleService.class); stopService(serviceIntent); try { // have to use the app context to bind to the service, cuz we're in tabs getApplicationContext().unbindService(state.serviceConnection); } catch (final IllegalArgumentException ex) { MainActivity.info("serviceConnection not registered: " + ex, ex); } // release the lock before turning wifi off if (state.wifiLock != null && state.wifiLock.isHeld()) { try { state.wifiLock.release(); } catch (Exception ex) { MainActivity.error("exception releasing wifi lock: " + ex, ex); } } final SharedPreferences prefs = this.getSharedPreferences(ListFragment.SHARED_PREFS, 0); final boolean wifiWasOff = prefs.getBoolean(ListFragment.PREF_WIFI_WAS_OFF, false); // don't call on emulator, it crashes it if (wifiWasOff && !state.inEmulator) { // tell user, cuz this takes a little while Toast.makeText(this, getString(R.string.turning_wifi_off), Toast.LENGTH_SHORT).show(); // well turn it of now that we're done final WifiManager wifiManager = (WifiManager) getApplicationContext() .getSystemService(Context.WIFI_SERVICE); MainActivity.info("turning back off wifi"); try { wifiManager.setWifiEnabled(false); } catch (Exception ex) { MainActivity.error("exception turning wifi back off: " + ex, ex); } } TelephonyManager tele = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); if (tele != null && state.phoneState != null) { tele.listen(state.phoneState, PhoneStateListener.LISTEN_NONE); } if (state.tts != null) { if (!isMuted()) { // give time for the above "done" to be said sleep(250); } state.tts.shutdown(); } // clean up. if (state.soundPop != null) { state.soundPop.release(); } if (state.soundNewPop != null) { state.soundNewPop.release(); } super.finish(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { MainActivity.info("onKeyDown: not quitting app on back"); selectFragment(0); return true; } // we may want this, but devices with menu button don't get the 3 dots, so we'd have to force on the 3 dots // pry not worth it. leaving in case we do want it in the future // else if (keyCode == KeyEvent.KEYCODE_MENU) { // if (!mDrawerLayout.isDrawerOpen(mDrawerList)) { // mDrawerLayout.openDrawer(mDrawerList); // } else if (mDrawerLayout.isDrawerOpen(mDrawerList)) { // mDrawerLayout.closeDrawer(mDrawerList); // } // return true; // } return super.onKeyDown(keyCode, event); } public void doUpload() { selectFragment(LIST_TAB_POS); listActivity.makeUploadDialog(this); } }