Java tutorial
/* * Copyright 20132016 Michael von Glasow. * * This file is part of LSRN Tools. * * LSRN Tools 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. * * LSRN Tools 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 LSRN Tools. If not, see <http://www.gnu.org/licenses/>. */ package com.vonglasow.michael.satstat; import java.util.HashSet; import java.util.List; import java.util.Set; import com.vonglasow.michael.satstat.utils.PermissionHelper; import com.vonglasow.michael.satstat.utils.WifiCapabilities; import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManager.RunningServiceInfo; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.preference.PreferenceManager; import android.support.v4.content.ContextCompat; import android.util.Log; import android.widget.Toast; public class GpsEventReceiver extends BroadcastReceiver { public static final String TAG = GpsEventReceiver.class.getSimpleName(); /** * A dummy intent called when a location update is received. * <p> * Some devices will refresh AGPS data only when the GPS is accessed. Thus, * in order to force an AGPS update, we request location updates from the * GPS and immediately remove updates again. However, in order to request * location updates we need to supply either a LocationListener or a * PendingIntent to which the location updates will be delivered. This * Intent is used to create that PendingIntent. When this Intent is * received, it will be ignored. */ public static final String LOCATION_UPDATE_RECEIVED = "com.vonglasow.michael.satstat.LOCATION_UPDATE_RECEIVED"; private static Intent mAgpsIntent = new Intent(Const.AGPS_DATA_EXPIRED); private static Intent mLocationIntent = new Intent(LOCATION_UPDATE_RECEIVED); @Override public void onReceive(Context context, Intent intent) { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); // some logic to use the pre-1.7 setting KEY_PREF_UPDATE_WIFI as a // fallback if KEY_PREF_UPDATE_NETWORKS is not set Set<String> fallbackUpdateNetworks = new HashSet<String>(); if (sharedPref.getBoolean(Const.KEY_PREF_UPDATE_WIFI, false)) { fallbackUpdateNetworks.add(Const.KEY_PREF_UPDATE_NETWORKS_WIFI); } Set<String> updateNetworks = sharedPref.getStringSet(Const.KEY_PREF_UPDATE_NETWORKS, fallbackUpdateNetworks); if (intent.getAction().equals(Const.GPS_ENABLED_CHANGE) || intent.getAction().equals(Const.GPS_ENABLED_CHANGE)) { //FIXME: why are we checking for the same intent twice? Should on of them be GPS_FIX_CHANGE? // an application has connected to GPS or disconnected from it, check if notification needs updating boolean notifyFix = sharedPref.getBoolean(Const.KEY_PREF_NOTIFY_FIX, false); boolean notifySearch = sharedPref.getBoolean(Const.KEY_PREF_NOTIFY_SEARCH, false); if (notifyFix || notifySearch) { boolean isRunning = false; ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (PasvLocListenerService.class.getName().equals(service.service.getClassName())) { isRunning = true; } } if (!isRunning) { Intent startServiceIntent = new Intent(context, PasvLocListenerService.class); startServiceIntent.setAction(intent.getAction()); startServiceIntent.putExtras(intent.getExtras()); context.startService(startServiceIntent); } } } else if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) && updateNetworks.contains(Const.KEY_PREF_UPDATE_NETWORKS_WIFI)) { // change in WiFi connectivity, check if we are connected and need to refresh AGPS //FIXME: KEY_PREF_UPDATE_WIFI as fallback only NetworkInfo netinfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); if (netinfo == null) return; if (!netinfo.isConnected()) return; //Toast.makeText(context, "WiFi is connected", Toast.LENGTH_SHORT).show(); Log.i(this.getClass().getSimpleName(), "WiFi is connected"); refreshAgps(context, true, false); } else if ((intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) || (intent.getAction().equals(Const.AGPS_DATA_EXPIRED))) { // change in network connectivity or AGPS expiration timer fired boolean isAgpsExpired = false; if (intent.getAction().equals(Const.AGPS_DATA_EXPIRED)) { Log.i(this.getClass().getSimpleName(), "AGPS data expired, checking available networks"); isAgpsExpired = true; } NetworkInfo netinfo = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)) .getActiveNetworkInfo(); if (netinfo == null) return; if (!netinfo.isConnected()) return; String type; if ((netinfo.getType() < ConnectivityManager.TYPE_MOBILE_MMS) || (netinfo.getType() > ConnectivityManager.TYPE_MOBILE_HIPRI)) { type = Integer.toString(netinfo.getType()); } else { // specific mobile data connections will be treated as TYPE_MOBILE type = Const.KEY_PREF_UPDATE_NETWORKS_MOBILE; } if (!updateNetworks.contains(type)) return; if (!isAgpsExpired) Log.i(this.getClass().getSimpleName(), "Network of type " + netinfo.getTypeName() + " is connected"); // Enforce the update interval if we were called by a network event // but not if we were called by a timer, because in that case the // check has already been done. (I am somewhat paranoid and don't // count on alarms not going off a few milliseconds too early.) refreshAgps(context, !isAgpsExpired, false); } } /** * Refreshes AGPS data if necessary. * * This method requests a refresh of the AGPS data. It optionally does so * only after checking when the AGPS data was last refreshed and * determining if it is stale by adding the refresh interval specified in * the user preferences and comparing the result against the current time. * If the result is less than current time, AGPS data is considered stale * and a refresh is requested. * * @param context A {@link Context} to be passed to {@link LocationManager}. * @param enforceInterval If true, prevents updates when the interval has * not yet expired. If false, updates are permitted at any time. This is to * prevent race conditions if alarms fire off too early. * @param wantFeedback Whether to display a toast informing the user about * the success of the operation. */ public static void refreshAgps(Context context, boolean enforceInterval, boolean wantFeedback) { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); long last = sharedPref.getLong(Const.KEY_PREF_UPDATE_LAST, 0); long freqDays = Long.parseLong(sharedPref.getString(Const.KEY_PREF_UPDATE_FREQ, "0")); long now = System.currentTimeMillis(); if (enforceInterval && (last + freqDays * Const.MILLIS_PER_DAY > now)) return; //Log.d(GpsEventReceiver.class.getSimpleName(), String.format("refreshAgps, enforceInterval: %b, wantFeedback: %b", enforceInterval, wantFeedback)); if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) new AgpsUpdateTask(wantFeedback).execute(context, mAgpsIntent, sharedPref, freqDays * Const.MILLIS_PER_DAY); else { Log.i(TAG, "Requesting permissions to update AGPS data"); PermissionHelper.requestPermissions((SatStatApplication) (context.getApplicationContext()), new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, Const.PERM_REQUEST_LOCATION_NOTIFICATION, context.getString(R.string.notify_perm_title), context.getString(R.string.notify_perm_body), R.drawable.ic_security); } } private static class AgpsUpdateTask extends AsyncTask<Object, Void, Integer> { Context mContext; boolean mWantFeedback = false; public AgpsUpdateTask(boolean wantFeedback) { super(); mWantFeedback = wantFeedback; } /** * @param args[0] A {@link Context} for connecting to the various system services * @param args[1] The {@link Intent} to raise when the next update is due * @param args[2] A {@link SharedPreferences} instance in which the timestamp of the update will be stored * @param args[3] The update frequency, of type {@link Long}, in milliseconds */ @Override protected Integer doInBackground(Object... args) { mContext = (Context) args[0]; Intent agpsIntent = (Intent) args[1]; SharedPreferences sharedPref = (SharedPreferences) args[2]; long freqMillis = (Long) args[3]; int nc = WifiCapabilities.getNetworkConnectivity(); if (nc == WifiCapabilities.NETWORK_CAPTIVE_PORTAL) { // portale cattivo che non ci permette di scaricare i dati AGPS Log.i(GpsEventReceiver.class.getSimpleName(), "Captive portal detected, cannot update AGPS data"); return nc; } else if (nc == WifiCapabilities.NETWORK_ERROR) { Log.i(GpsEventReceiver.class.getSimpleName(), "No network available, cannot update AGPS data"); return nc; } AlarmManager alm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, agpsIntent, PendingIntent.FLAG_UPDATE_CURRENT); alm.cancel(pi); LocationManager locman = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); List<String> allProviders = locman.getAllProviders(); PendingIntent tempIntent = PendingIntent.getBroadcast(mContext, 0, mLocationIntent, PendingIntent.FLAG_UPDATE_CURRENT); Log.i(GpsEventReceiver.class.getSimpleName(), "Requesting AGPS data update"); try { if (allProviders.contains(LocationManager.GPS_PROVIDER)) locman.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, tempIntent); locman.sendExtraCommand("gps", "force_xtra_injection", null); locman.sendExtraCommand("gps", "force_time_injection", null); locman.removeUpdates(tempIntent); SharedPreferences.Editor spEditor = sharedPref.edit(); spEditor.putLong(Const.KEY_PREF_UPDATE_LAST, System.currentTimeMillis()); spEditor.commit(); } catch (SecurityException e) { Log.w(GpsEventReceiver.class.getSimpleName(), "Permissions not granted, cannot update AGPS data"); } if (freqMillis > 0) { // if an update interval is set, prepare an alarm to trigger a new // update when it elapses (if no interval is set, do nothing as we // cannot determine a point in time for re-running the update) long next = System.currentTimeMillis() + freqMillis; alm.set(AlarmManager.RTC, next, pi); Log.i(GpsEventReceiver.class.getSimpleName(), String.format( "Next update due %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS (after %2$d ms)", next, freqMillis)); } return nc; } @Override protected void onPostExecute(Integer result) { if ((mContext == null) || !mWantFeedback) return; String message = ""; switch (result) { case WifiCapabilities.NETWORK_AVAILABLE: message = mContext.getString(R.string.status_agps); break; case WifiCapabilities.NETWORK_CAPTIVE_PORTAL: message = mContext.getString(R.string.status_agps_captive); break; case WifiCapabilities.NETWORK_ERROR: message = mContext.getString(R.string.status_agps_error); break; } Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); } } }