Java tutorial
/* Radiobeacon - Openbmap wifi and cell logger Copyright (C) 2013 wish7 This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.openbmap.services; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; 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.SharedPreferences; import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; import android.os.RemoteException; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.Toast; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.openbmap.Preferences; import org.openbmap.R; import org.openbmap.RadioBeacon; import org.openbmap.activities.TabHostActivity; import org.openbmap.db.DataHelper; import org.openbmap.db.models.Session; import org.openbmap.events.onStartGpx; import org.openbmap.events.onStartLocation; import org.openbmap.events.onStartTracking; import org.openbmap.events.onStartWireless; import org.openbmap.events.onStopTracking; import org.openbmap.services.positioning.GpxLoggerService; import org.openbmap.services.positioning.PositioningService; import org.openbmap.services.positioning.PositioningService.ProviderType; import org.openbmap.services.wireless.WirelessLoggerService; import java.util.ArrayList; /** * MasterBrainService is the service coordinator, starting other sub-services as required * It's started as soon as Radiobeacon app starts and runs in the application context * It listens to StartTrackingEvent and StopTrackingEvent on the message bus as well as system's * low battery events */ public class MasterBrainService extends Service { private static final String TAG = MasterBrainService.class.getSimpleName(); /** For showing and hiding our notification. */ NotificationManager mNotificationManager; /** Keeps track of all current registered clients. */ ArrayList<Messenger> mClients = new ArrayList<>(); /** * System notification id. */ private static final int NOTIFICATION_ID = 1235; /** * Selected navigation provider, default GPS */ private ProviderType mSelectedProvider = ProviderType.GPS; /** * Unique powerlock id */ private static final String WAKELOCK_NAME = "org.openbmap.wakelock"; /** * Keeps the SharedPreferences. */ private SharedPreferences mPrefs = null; private DataHelper mDataHelper; private PositioningService mPositioningService; private WirelessLoggerService mWirelessService; private GpxLoggerService mGpxService; private boolean mPositioningBound; private boolean mWirelessBound; private boolean mGpxBound; private PowerManager.WakeLock mWakeLock; /** * Current session */ private int mSession = RadioBeacon.SESSION_NOT_TRACKING; private int mShutdownReason; /** * Handler of incoming messages from clients. */ class UpstreamHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case RadioBeacon.MSG_REGISTER_CLIENT: mClients.add(msg.replyTo); break; case RadioBeacon.MSG_UNREGISTER_CLIENT: mClients.remove(msg.replyTo); break; default: super.handleMessage(msg); } } } private ServiceConnection mPositioningConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { AbstractService.LocalBinder binder = (AbstractService.LocalBinder) service; mPositioningService = (PositioningService) binder.getService(); mPositioningBound = true; Log.d(TAG, "REQ StartPositioningEvent"); EventBus.getDefault().post(new onStartLocation()); } @Override public void onServiceDisconnected(ComponentName arg0) { mPositioningBound = false; } }; private ServiceConnection mWirelessConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { AbstractService.LocalBinder binder = (AbstractService.LocalBinder) service; mWirelessService = (WirelessLoggerService) binder.getService(); mWirelessBound = true; Log.d(TAG, "REQ StartWirelessEvent"); EventBus.getDefault().post(new onStartWireless(mSession)); } @Override public void onServiceDisconnected(ComponentName arg0) { mWirelessBound = false; } }; private ServiceConnection mGpxConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { AbstractService.LocalBinder binder = (AbstractService.LocalBinder) service; mGpxService = (GpxLoggerService) binder.getService(); mGpxBound = true; Log.d(TAG, "REQ StartGpxEvent"); EventBus.getDefault().post(new onStartGpx(mSession)); } @Override public void onServiceDisconnected(ComponentName arg0) { mGpxBound = false; } }; /** * Target we publish for clients to send messages to IncomingHandler. */ final Messenger mUpstreamMessenger = new Messenger(new UpstreamHandler()); @Override public void onCreate() { Log.d(TAG, "MasterBrainService created"); EventBus.getDefault().register(this); mDataHelper = new DataHelper(this); // get shared preferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } @Override public int onStartCommand(Intent intent, int flags, int startId) { registerReceiver(); // We want this service to continue running until it is explicitly stopped, so return sticky. return START_STICKY; } @Override public void onDestroy() { unregisterReceiver(); hideNotification(); EventBus.getDefault().unregister(this); unbindAll(); } /** * Registers broadcast receiver */ private void registerReceiver() { Log.i(TAG, "Registering broadcast receivers"); final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_LOW); registerReceiver(mReceiver, filter); } /** * Unregisters broadcast receiver */ private void unregisterReceiver() { try { Log.i(TAG, "Unregistering broadcast receivers"); unregisterReceiver(mReceiver); } catch (final IllegalArgumentException e) { // do nothing here {@see http://stackoverflow.com/questions/2682043/how-to-check-if-receiver-is-registered-in-android} return; } } /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ @Override public IBinder onBind(Intent intent) { return mUpstreamMessenger.getBinder(); } /** * Called when start tracking is requested on the message bus * @param event */ @Subscribe public void onEvent(onStartTracking event) { Log.d(TAG, "Received StartTracking event"); mSession = event.session; requirePowerLock(); startTracking(mSession); } /** * Called when stop tracking is requested on the message bus * @param event */ @Subscribe public void onEvent(onStopTracking event) { Log.d(TAG, "Received StopTrackingEvent event"); stopTracking(RadioBeacon.SHUTDOWN_REASON_NORMAL); mSession = RadioBeacon.SESSION_NOT_TRACKING; releasePowerLock(); } /** * Acquires wakelock to prevent CPU falling asleep */ private void requirePowerLock() { final PowerManager mgr = (PowerManager) getSystemService(Context.POWER_SERVICE); try { Log.i(TAG, "Acquiring wakelock " + WAKELOCK_NAME); mWakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_NAME); mWakeLock.setReferenceCounted(true); } catch (final Exception e) { Log.e(TAG, "Error acquiring wakelock " + WAKELOCK_NAME + e.toString(), e); } } /** * Releases wakelock, if held */ private void releasePowerLock() { if (mWakeLock != null && mWakeLock.isHeld()) { Log.i(TAG, "Releasing wakelock " + WAKELOCK_NAME); mWakeLock.release(); } mWakeLock = null; } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { /** * Handles start and stop service requests. */ @Override public void onReceive(final Context context, final Intent intent) { if (Intent.ACTION_BATTERY_LOW.equals(intent.getAction())) { Log.d(TAG, "ACTION_BATTERY_LOW received"); final boolean ignoreBattery = mPrefs.getBoolean(Preferences.KEY_IGNORE_BATTERY, Preferences.VAL_IGNORE_BATTERY); if (!ignoreBattery) { Toast.makeText(context, getString(R.string.battery_warning), Toast.LENGTH_LONG).show(); stopTracking(RadioBeacon.SHUTDOWN_REASON_LOW_POWER); } else { Log.i(TAG, "Battery low but ignoring due to settings"); } } else { Log.d(TAG, "Received intent " + intent.getAction() + " but ignored"); } } }; /** * Prepares database and sub-services to start tracking * @param session */ private void startTracking(final int session) { if (session != RadioBeacon.SESSION_NOT_TRACKING) { Log.d(TAG, "Preparing session " + session); mSession = session; resumeSession(session); } else { Log.d(TAG, "Preparing new session"); mSession = setupNewSession(); } bindAll(); showNotification(); } /** * Updates database and stops sub-services after stop request * @param reason */ private void stopTracking(int reason) { unbindAll(); updateDatabase(); for (int i = mClients.size() - 1; i >= 0; i--) { try { mClients.get(i).send(Message.obtain(null, RadioBeacon.MSG_SERVICE_SHUTDOWN, reason, 0)); } catch (RemoteException e) { // The client is dead. Remove it from the list; // we are going through the list from back to front // so this is safe to do inside the loop. mClients.remove(i); } } hideNotification(); } /** * Binds all sub-services */ private void bindAll() { Log.d(TAG, "Binding services"); Intent i1 = new Intent(this, PositioningService.class); bindService(i1, mPositioningConnection, Context.BIND_AUTO_CREATE); Intent i2 = new Intent(this, WirelessLoggerService.class); bindService(i2, mWirelessConnection, Context.BIND_AUTO_CREATE); Intent i3 = new Intent(this, GpxLoggerService.class); bindService(i3, mGpxConnection, Context.BIND_AUTO_CREATE); } /** * Unbinds all sub-services */ private void unbindAll() { // Unbind from the service if (mPositioningBound) { unbindService(mPositioningConnection); mPositioningBound = false; } if (mWirelessBound) { unbindService(mWirelessConnection); mWirelessBound = false; } if (mGpxBound) { unbindService(mGpxConnection); mGpxBound = false; } } /** * Opens new session */ private int setupNewSession() { // invalidate all active session mDataHelper.invalidateActiveSessions(); // Create a new session and activate it // Then start HostActivity. HostActivity onStart() and onResume() check active session // and starts services for active session final Session active = new Session(); active.setCreatedAt(System.currentTimeMillis()); active.setLastUpdated(System.currentTimeMillis()); active.setDescription("No description yet"); active.isActive(true); // id can only be set after session has been stored to database. final Uri result = mDataHelper.storeSession(active); final int id = Integer.valueOf(result.getLastPathSegment()); active.setId(id); return id; } /** * Resumes specific session * @param id */ private void resumeSession(final int id) { final Session resume = mDataHelper.loadSession(id); if (resume == null) { Log.e(TAG, "Error loading session " + id); return; } resume.isActive(true); mDataHelper.storeSession(resume, true); } /** * Updates number of cells and wifis and closes active session */ private void updateDatabase() { final Session active = mDataHelper.loadActiveSession(); if (active != null) { active.setWifisCount(mDataHelper.countWifis(active.getId())); active.setCellsCount(mDataHelper.countCells(active.getId())); active.setWaypointsCount(mDataHelper.countWaypoints(active.getId())); mDataHelper.storeSession(active, false); } mDataHelper.invalidateActiveSessions(); } /** * Shows Android notification while this service is running. */ private void showNotification() { PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, TabHostActivity.class), 0); // Set the icon, scrolling text and timestamp //final Notification notification = new Notification(R.drawable.icon_greyed_25x25, getString(R.string.notification_caption), // System.currentTimeMillis()); //notification.setLatestEventInfo(this, getString(R.string.app_name), getString(R.string.notification_caption), contentIntent); // Set the info for the views that show in the notification panel. if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { Notification.Builder builder = new Notification.Builder(this.getApplicationContext()); builder.setAutoCancel(false); builder.setContentTitle(getString(R.string.app_name)); builder.setContentText(getString(R.string.notification_caption)); builder.setSmallIcon(R.drawable.icon_greyed_25x25); builder.setContentIntent(intent); builder.setOngoing(true); mNotificationManager.notify(NOTIFICATION_ID, builder.build()); } else if (VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN) { NotificationCompat.Builder compat = new NotificationCompat.Builder(getApplicationContext()); compat.setAutoCancel(false); compat.setContentTitle(getString(R.string.app_name)); compat.setContentText(getString(R.string.notification_caption)); compat.setSmallIcon(R.drawable.icon_greyed_25x25); compat.setContentIntent(intent); compat.setOngoing(true); mNotificationManager.notify(NOTIFICATION_ID, compat.build()); } } /** * Hides Android notification */ private void hideNotification() { mNotificationManager.cancel(NOTIFICATION_ID); } }