Java tutorial
/* According to apache license This is fork of christocracy cordova-plugin-background-geolocation plugin https://github.com/christocracy/cordova-plugin-background-geolocation Differences to original version: 1. new methods isLocationEnabled, mMessageReciever, handleMessage */ package com.tenforwardconsulting.cordova; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.app.Application; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.location.LocationManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import com.marianhello.bgloc.Config; import com.marianhello.bgloc.LocationService; import com.marianhello.bgloc.ResourceResolver; import com.marianhello.bgloc.data.BackgroundLocation; import com.marianhello.bgloc.data.ConfigurationDAO; import com.marianhello.bgloc.data.DAOFactory; import com.marianhello.bgloc.data.LocationDAO; import com.marianhello.cordova.JSONErrorFactory; import com.marianhello.cordova.PermissionHelper; import com.marianhello.logging.FileLogReader; import com.marianhello.logging.LogEntry; import com.marianhello.logging.LogReader; import com.marianhello.logging.LoggerManager; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class BackgroundGeolocationPlugin extends CordovaPlugin { public static final String ACTION_START = "start"; public static final String ACTION_STOP = "stop"; public static final String ACTION_CONFIGURE = "configure"; public static final String ACTION_SWITCH_MODE = "switchMode"; public static final String ACTION_LOCATION_ENABLED_CHECK = "isLocationEnabled"; public static final String ACTION_SHOW_LOCATION_SETTINGS = "showLocationSettings"; public static final String ACTION_SHOW_APP_SETTINGS = "showAppSettings"; public static final String ACTION_ADD_MODE_CHANGED_LISTENER = "watchLocationMode"; public static final String ACTION_REMOVE_MODE_CHANGED_LISTENER = "stopWatchingLocationMode"; public static final String ACTION_ADD_STATIONARY_LISTENER = "addStationaryRegionListener"; public static final String ACTION_GET_STATIONARY = "getStationaryLocation"; public static final String ACTION_GET_ALL_LOCATIONS = "getLocations"; public static final String ACTION_GET_VALID_LOCATIONS = "getValidLocations"; public static final String ACTION_DELETE_LOCATION = "deleteLocation"; public static final String ACTION_DELETE_ALL_LOCATIONS = "deleteAllLocations"; public static final String ACTION_GET_CONFIG = "getConfig"; public static final String ACTION_GET_LOG_ENTRIES = "getLogEntries"; public static final int START_REQ_CODE = 0; public static final int PERMISSION_DENIED_ERROR_CODE = 2; public static final String[] permissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION }; /** Messenger for communicating with the service. */ private Messenger mService = null; /** Flag indicating whether we have called bind on the service. */ private Boolean isBound = false; private Boolean isServiceRunning = false; private Config config; private CallbackContext callbackContext; private ArrayList<CallbackContext> stationaryContexts = new ArrayList<CallbackContext>(); private CallbackContext actionStartCallbackContext; private CallbackContext locationModeChangeCallbackContext; private ExecutorService executorService; private BackgroundLocation stationaryLocation; private org.slf4j.Logger log; /** * Handler of incoming messages from service. */ class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case LocationService.MSG_LOCATION_UPDATE: try { log.debug("Sending location to webview"); Bundle bundle = msg.getData(); bundle.setClassLoader(LocationService.class.getClassLoader()); JSONObject location = ((BackgroundLocation) bundle.getParcelable("location")).toJSONObject(); PluginResult result = new PluginResult(PluginResult.Status.OK, location); result.setKeepCallback(true); callbackContext.sendPluginResult(result); } catch (JSONException e) { log.warn("Error converting message to json"); PluginResult result = new PluginResult(PluginResult.Status.JSON_EXCEPTION); result.setKeepCallback(true); callbackContext.sendPluginResult(result); } break; case LocationService.MSG_ON_STATIONARY: if (!stationaryContexts.isEmpty()) { PluginResult result; try { log.debug("Sending stationary location to webview"); Bundle bundle = msg.getData(); bundle.setClassLoader(LocationService.class.getClassLoader()); stationaryLocation = (BackgroundLocation) bundle.getParcelable("location"); JSONObject location = stationaryLocation.toJSONObject(); result = new PluginResult(PluginResult.Status.OK, location); result.setKeepCallback(true); } catch (JSONException e) { log.warn("Error converting message to json"); result = new PluginResult(PluginResult.Status.JSON_EXCEPTION); result.setKeepCallback(true); } for (CallbackContext ctx : stationaryContexts) { ctx.sendPluginResult(result); } } break; case LocationService.MSG_ERROR: try { log.debug("Sending error to webview"); Bundle bundle = msg.getData(); bundle.setClassLoader(LocationService.class.getClassLoader()); JSONObject error = new JSONObject(bundle.getString("error")); PluginResult result = new PluginResult(PluginResult.Status.ERROR, error); result.setKeepCallback(true); callbackContext.sendPluginResult(result); } catch (JSONException e) { log.warn("Error converting message to json"); PluginResult result = new PluginResult(PluginResult.Status.JSON_EXCEPTION); result.setKeepCallback(true); callbackContext.sendPluginResult(result); } break; default: super.handleMessage(msg); } } } final Messenger mMessenger = new Messenger(new IncomingHandler()); /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the object we can use to // interact with the service. We are communicating with the // service using a Messenger, so here we get a client-side // representation of that from the raw IBinder object. mService = new Messenger(service); isBound = true; // We want to monitor the service for as long as we are // connected to it. try { Message msg = Message.obtain(null, LocationService.MSG_REGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { // In this case the service has crashed before we could even // do anything with it; we can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. mService = null; isBound = false; } }; private BroadcastReceiver locationModeChangeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { log.debug("Received MODE_CHANGED_ACTION action"); if (locationModeChangeCallbackContext != null) { PluginResult result; try { int isLocationEnabled = BackgroundGeolocationPlugin.isLocationEnabled(context) ? 1 : 0; result = new PluginResult(PluginResult.Status.OK, isLocationEnabled); result.setKeepCallback(true); } catch (SettingNotFoundException e) { result = new PluginResult(PluginResult.Status.ERROR, "Location setting error occured"); } locationModeChangeCallbackContext.sendPluginResult(result); } } }; @Override protected void pluginInitialize() { super.pluginInitialize(); log = LoggerManager.getLogger(BackgroundGeolocationPlugin.class); LoggerManager.enableRollingFileLogging(); log.info("initializing plugin"); final ResourceResolver res = ResourceResolver.newInstance(getApplication()); final String authority = res.getStringResource(Config.CONTENT_AUTHORITY_RESOURCE); executorService = Executors.newSingleThreadExecutor(); } public boolean execute(String action, final JSONArray data, final CallbackContext callbackContext) { Activity activity = getActivity(); Context context = getContext(); if (ACTION_START.equals(action)) { if (config == null) { log.warn("Attempt to start unconfigured service"); callbackContext.error("Plugin not configured. Please call configure method first."); return true; } if (!hasPermissions()) { log.info("Requesting permissions from user"); actionStartCallbackContext = callbackContext; PermissionHelper.requestPermissions(this, START_REQ_CODE, permissions); return true; } executorService.execute(new Runnable() { public void run() { startAndBindBackgroundService(); callbackContext.success(); } }); return true; } else if (ACTION_STOP.equals(action)) { executorService.execute(new Runnable() { public void run() { doUnbindService(); stopBackgroundService(); callbackContext.success(); } }); return true; } else if (ACTION_SWITCH_MODE.equals(action)) { Message msg = Message.obtain(null, LocationService.MSG_SWITCH_MODE); try { msg.arg1 = data.getInt(0); mService.send(msg); } catch (Exception e) { log.error("Switch mode failed: {}", e.getMessage()); } return true; } else if (ACTION_CONFIGURE.equals(action)) { this.callbackContext = callbackContext; executorService.execute(new Runnable() { public void run() { try { config = Config.fromJSONObject(data.getJSONObject(0)); persistConfiguration(config); // callbackContext.success(); //we cannot do this } catch (JSONException e) { log.error("Configuration error: {}", e.getMessage()); callbackContext.error("Configuration error: " + e.getMessage()); } catch (NullPointerException e) { log.error("Configuration error: {}", e.getMessage()); callbackContext.error("Configuration error: " + e.getMessage()); } } }); return true; } else if (ACTION_LOCATION_ENABLED_CHECK.equals(action)) { log.debug("Location services enabled check"); try { int isLocationEnabled = BackgroundGeolocationPlugin.isLocationEnabled(context) ? 1 : 0; callbackContext.success(isLocationEnabled); } catch (SettingNotFoundException e) { log.error("Location service checked failed: {}", e.getMessage()); callbackContext.error("Location setting error occured"); } return true; } else if (ACTION_SHOW_LOCATION_SETTINGS.equals(action)) { showLocationSettings(); // TODO: call success/fail callback return true; } else if (ACTION_SHOW_APP_SETTINGS.equals(action)) { showAppSettings(); // TODO: call success/fail callback return true; } else if (ACTION_ADD_MODE_CHANGED_LISTENER.equals(action)) { registerLocationModeChangeReceiver(callbackContext); // TODO: call success/fail callback return true; } else if (ACTION_REMOVE_MODE_CHANGED_LISTENER.equals(action)) { unregisterLocationModeChangeReceiver(); // TODO: call success/fail callback return true; } else if (ACTION_ADD_STATIONARY_LISTENER.equals(action)) { stationaryContexts.add(callbackContext); return true; } else if (ACTION_GET_STATIONARY.equals(action)) { try { if (stationaryLocation != null) { callbackContext.success(stationaryLocation.toJSONObject()); } else { callbackContext.success(); } } catch (JSONException e) { log.error("Getting stationary location failed: {}", e.getMessage()); callbackContext.error("Getting stationary location failed"); } return true; } else if (ACTION_GET_ALL_LOCATIONS.equals(action)) { cordova.getThreadPool().execute(new Runnable() { public void run() { try { callbackContext.success(getAllLocations()); } catch (JSONException e) { log.error("Getting all locations failed: {}", e.getMessage()); callbackContext.error("Converting locations to JSON failed."); } } }); return true; } else if (ACTION_GET_VALID_LOCATIONS.equals(action)) { cordova.getThreadPool().execute(new Runnable() { public void run() { try { callbackContext.success(getValidLocations()); } catch (JSONException e) { log.error("Getting valid locations failed: {}", e.getMessage()); callbackContext.error("Converting locations to JSON failed."); } } }); return true; } else if (ACTION_DELETE_LOCATION.equals(action)) { cordova.getThreadPool().execute(new Runnable() { public void run() { try { Long locationId = data.getLong(0); deleteLocation(locationId); callbackContext.success(); } catch (JSONException e) { log.error("Delete location failed: {}", e.getMessage()); callbackContext.error("Deleting location failed: " + e.getMessage()); } } }); return true; } else if (ACTION_DELETE_ALL_LOCATIONS.equals(action)) { cordova.getThreadPool().execute(new Runnable() { public void run() { deleteAllLocations(); callbackContext.success(); } }); return true; } else if (ACTION_GET_CONFIG.equals(action)) { cordova.getThreadPool().execute(new Runnable() { public void run() { try { callbackContext.success(retrieveConfiguration()); } catch (JSONException e) { log.error("Error getting config: {}", e.getMessage()); callbackContext.error("Error getting config: " + e.getMessage()); } } }); return true; } else if (ACTION_GET_LOG_ENTRIES.equals(action)) { cordova.getThreadPool().execute(new Runnable() { public void run() { try { callbackContext.success(getLogs(data.getInt(0))); } catch (Exception e) { callbackContext.error("Getting logs failed: " + e.getMessage()); } } }); return true; } return false; } /** * Called when the system is about to start resuming a previous activity. * * @param multitasking Flag indicating if multitasking is turned on for app */ public void onPause(boolean multitasking) { log.info("App will be paused multitasking={}", multitasking); } /** * Called when the activity will start interacting with the user. * * @param multitasking Flag indicating if multitasking is turned on for app */ public void onResume(boolean multitasking) { log.info("App will be resumed multitasking={}", multitasking); } /** * Called when the activity is becoming visible to the user. */ public void onStart() { log.info("App is visible"); } /** * Called when the activity is no longer visible to the user. */ public void onStop() { log.info("App is no longer visible"); } /** * The final call you receive before your activity is destroyed. * Checks to see if it should turn off */ @Override public void onDestroy() { log.info("Destroying plugin"); unregisterLocationModeChangeReceiver(); // Unbind from the service doUnbindService(); if (config.getStopOnTerminate()) { stopBackgroundService(); } super.onDestroy(); } protected Activity getActivity() { return this.cordova.getActivity(); } protected Application getApplication() { return getActivity().getApplication(); } protected Context getContext() { return getActivity().getApplicationContext(); } protected void runOnUiThread(Runnable action) { getActivity().runOnUiThread(action); } protected void startAndBindBackgroundService() { startBackgroundService(); doBindService(); } protected void startBackgroundService() { if (!isServiceRunning) { log.info("Starting bg service"); Activity activity = getActivity(); Intent locationServiceIntent = new Intent(activity, LocationService.class); locationServiceIntent.putExtra("config", config); locationServiceIntent.addFlags(Intent.FLAG_FROM_BACKGROUND); // start service to keep service running even if no clients are bound to it activity.startService(locationServiceIntent); isServiceRunning = true; } } protected void stopBackgroundService() { if (isServiceRunning) { log.info("Stopping bg service"); Activity activity = getActivity(); activity.stopService(new Intent(activity, LocationService.class)); isServiceRunning = false; } } void doBindService() { // Establish a connection with the service. We use an explicit // class name because there is no reason to be able to let other // applications replace our component. if (!isBound) { log.info("Binding to bg service"); Activity activity = getActivity(); Intent locationServiceIntent = new Intent(activity, LocationService.class); locationServiceIntent.putExtra("config", config); activity.bindService(locationServiceIntent, mConnection, Context.BIND_IMPORTANT); } } void doUnbindService() { if (isBound) { log.info("Unbinding from bg service"); // If we have received the service, and hence registered with // it, then now is the time to unregister. if (mService != null) { try { Message msg = Message.obtain(null, LocationService.MSG_UNREGISTER_CLIENT); msg.replyTo = mMessenger; mService.send(msg); } catch (RemoteException e) { // There is nothing special we need to do if the service // has crashed. } // Detach our existing connection. Activity activity = getActivity(); activity.unbindService(mConnection); isBound = false; } } } @TargetApi(Build.VERSION_CODES.KITKAT) private Intent registerLocationModeChangeReceiver(CallbackContext callbackContext) { if (locationModeChangeCallbackContext != null) { unregisterLocationModeChangeReceiver(); } locationModeChangeCallbackContext = callbackContext; return getContext().registerReceiver(locationModeChangeReceiver, new IntentFilter(LocationManager.MODE_CHANGED_ACTION)); } private void unregisterLocationModeChangeReceiver() { if (locationModeChangeCallbackContext == null) { return; } getContext().unregisterReceiver(locationModeChangeReceiver); locationModeChangeCallbackContext = null; } public void showLocationSettings() { Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); cordova.getActivity().startActivity(settingsIntent); } public void showAppSettings() { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.parse("package:" + getContext().getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); getContext().startActivity(intent); } public static boolean isLocationEnabled(Context context) throws SettingNotFoundException { int locationMode = 0; String locationProviders; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE); return locationMode != Settings.Secure.LOCATION_MODE_OFF; } else { locationProviders = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED); return !TextUtils.isEmpty(locationProviders); } } public JSONArray getAllLocations() throws JSONException { JSONArray jsonLocationsArray = new JSONArray(); LocationDAO dao = DAOFactory.createLocationDAO(getContext()); Collection<BackgroundLocation> locations = dao.getAllLocations(); for (BackgroundLocation location : locations) { jsonLocationsArray.put(location.toJSONObject()); } return jsonLocationsArray; } public JSONArray getValidLocations() throws JSONException { JSONArray jsonLocationsArray = new JSONArray(); LocationDAO dao = DAOFactory.createLocationDAO(getContext()); Collection<BackgroundLocation> locations = dao.getValidLocations(); for (BackgroundLocation location : locations) { jsonLocationsArray.put(location.toJSONObjectWithId()); } return jsonLocationsArray; } public void deleteLocation(Long locationId) { LocationDAO dao = DAOFactory.createLocationDAO(getContext()); dao.deleteLocation(locationId); } public void deleteAllLocations() { LocationDAO dao = DAOFactory.createLocationDAO(getContext()); dao.deleteAllLocations(); } public void persistConfiguration(Config config) throws NullPointerException { ConfigurationDAO dao = DAOFactory.createConfigurationDAO(getContext()); dao.persistConfiguration(config); } public JSONObject retrieveConfiguration() throws JSONException { ConfigurationDAO dao = DAOFactory.createConfigurationDAO(getContext()); Config config = dao.retrieveConfiguration(); if (config != null) { return config.toJSONObject(); } return null; } public JSONArray getLogs(Integer limit) throws Exception { JSONArray jsonLogsArray = new JSONArray(); LogReader logReader = new FileLogReader(); Collection<LogEntry> logEntries = logReader.getEntries(limit); for (LogEntry logEntry : logEntries) { jsonLogsArray.put(logEntry.toJSONObject()); } return jsonLogsArray; } public boolean hasPermissions() { for (String p : permissions) { if (!PermissionHelper.hasPermission(this, p)) { return false; } } return true; } public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { for (int r : grantResults) { if (r == PackageManager.PERMISSION_DENIED) { log.info("Permission Denied!"); actionStartCallbackContext.error( JSONErrorFactory.getJSONError(PERMISSION_DENIED_ERROR_CODE, "Permission denied by user")); actionStartCallbackContext = null; return; } } switch (requestCode) { case START_REQ_CODE: startAndBindBackgroundService(); actionStartCallbackContext.success(); actionStartCallbackContext = null; break; } } }