/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.mozstumbler.client; import android.app.Notification; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; import android.os.IBinder; import android.os.Looper; import android.support.v4.content.LocalBroadcastManager; import org.mozilla.mozstumbler.R; import org.mozilla.mozstumbler.client.util.NotificationUtil; import org.mozilla.mozstumbler.service.AppGlobals; import org.mozilla.mozstumbler.service.stumblerthread.Reporter; import org.mozilla.mozstumbler.service.stumblerthread.StumblerService; import org.mozilla.mozstumbler.service.utils.BatteryCheckReceiver; import org.mozilla.mozstumbler.service.utils.BatteryCheckReceiver.BatteryCheckCallback; import org.mozilla.mozstumbler.svclocator.ServiceLocator; import org.mozilla.mozstumbler.svclocator.services.log.ILogger; import org.mozilla.mozstumbler.svclocator.services.log.LoggerUtil; // Used as a bound service (with foreground priority) in Mozilla Stumbler, a.k.a. active scanning mode. // -- In accordance with Android service docs -and experimental findings- this puts the service as low // as possible on the Android process kill list. // -- Binding functions are commented in this class as being unused in the stand-alone service mode. public class ClientStumblerService extends StumblerService { private static final String LOG_TAG = LoggerUtil.makeLogTag(StumblerService.class); private static final ILogger Log = (ILogger) ServiceLocator.getInstance().getService(ILogger.class); public static enum RequestChangeScannerState { START, STOP; public static final String NAMESPACE = "org.mozilla.mozstumbler.clientstumblerservice.state"; @Override public String toString() { return NAMESPACE+ this.name(); } public static RequestChangeScannerState fromString(String name) { try { return RequestChangeScannerState.valueOf(name.substring(RequestChangeScannerState.NAMESPACE.length())); } catch (IllegalArgumentException iae) { return null; } } } private static final String START_FOREGROUND_SCANNING = RequestChangeScannerState.START.toString(); private static final String STOP_FOREGROUND_SCANNING = RequestChangeScannerState.STOP.toString(); private final IBinder mBinder = new StumblerBinder(); private final BatteryCheckCallback mBatteryCheckCallback = new BatteryCheckCallback() { private boolean waitForBatteryOkBeforeSendingNotification; @Override public void batteryCheckCallback(BatteryCheckReceiver receiver) { int minBattery = ClientPrefs.getInstance(ClientStumblerService.this).getMinBatteryPercent(); boolean isLow = receiver.isBatteryNotChargingAndLessThan(minBattery); if (isLow && !waitForBatteryOkBeforeSendingNotification) { waitForBatteryOkBeforeSendingNotification = true; LocalBroadcastManager.getInstance(ClientStumblerService.this). sendBroadcast(new Intent(MainApp.ACTION_LOW_BATTERY)); } else if (receiver.isBatteryNotChargingAndGreaterThan(minBattery)) { waitForBatteryOkBeforeSendingNotification = false; } } }; private BatteryCheckReceiver mBatteryChecker; private final BroadcastReceiver startStopScanReceiver = new BroadcastReceiver() { // This captures state change from the ScanManager @Override public void onReceive(Context context, Intent intent) { if (intent == null) { return; } RequestChangeScannerState newRunState = RequestChangeScannerState.fromString(intent.getAction()); if (newRunState != null) { if (newRunState.equals(RequestChangeScannerState.START)) { startScanning(); } else if (newRunState.equals(RequestChangeScannerState.STOP)) { stopScanning(); } } }; }; // Service binding is not used in stand-alone passive mode. @Override public IBinder onBind(Intent intent) { if (AppGlobals.isDebug) { Log.d(LOG_TAG, "onBind"); } registerIntentFilters(); return mBinder; } private void registerIntentFilters() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(START_FOREGROUND_SCANNING); intentFilter.addAction(STOP_FOREGROUND_SCANNING); LocalBroadcastManager .getInstance(getApplicationContext()) .registerReceiver(startStopScanReceiver, intentFilter); } // Service binding is not used in stand-alone passive mode. @Override public boolean onUnbind(Intent intent) { if (AppGlobals.isDebug) { Log.d(LOG_TAG, "onUnbind"); } return true; } // Service binding is not used in stand-alone passive mode. @Override public void onRebind(Intent intent) { if (AppGlobals.isDebug) { Log.d(LOG_TAG, "onRebind"); } } public void stopScanning() { if (mScanManager.stopScanning()) { Intent flush = new Intent(Reporter.ACTION_FLUSH_TO_BUNDLE); LocalBroadcastManager.getInstance(this).sendBroadcastSync(flush); } if (mBatteryChecker != null) { mBatteryChecker.stop(); } stopForeground(true); } @Override public synchronized void startScanning() { foregroundNotification(); boolean passiveScanning = ClientPrefs.getInstance(ClientStumblerService.this).isScanningPassive(); setPassiveMode(passiveScanning); super.startScanning(); if (mBatteryChecker == null) { mBatteryChecker = new BatteryCheckReceiver(this, mBatteryCheckCallback); } mBatteryChecker.start(); } private void foregroundNotification() { NotificationUtil nm = new NotificationUtil(this.getApplicationContext()); Notification notification = nm.buildNotification(getString(R.string.stop_scanning)); startForeground(NotificationUtil.NOTIFICATION_ID, notification); } public static void startForegroundScanning(Context ctx) { Intent startIntent = new Intent(ctx, ClientStumblerService.class); startIntent.setAction(START_FOREGROUND_SCANNING); // TODO: change this eventually to use startService and adjust the BroadcastReceiver // code and move it into an onHandleIntent block LocalBroadcastManager.getInstance(ctx).sendBroadcastSync(startIntent); } public static void stopForegroundScanning(Context ctx) { Intent startIntent = new Intent(ctx, ClientStumblerService.class); startIntent.setAction(STOP_FOREGROUND_SCANNING); // TODO: change this eventually to use stopService LocalBroadcastManager.getInstance(ctx).sendBroadcastSync(startIntent); } // Service binding is not used in stand-alone passive mode. public final class StumblerBinder extends Binder { // Only to be used in the non-standalone, non-passive case (Mozilla Stumbler). In the passive standalone usage // of this class, everything, including initialization, is done on its dedicated thread // This function is written to enforce the contract of its usage, and will throw if called from the wrong thread public ClientStumblerService getServiceAndInitialize(Thread callingThread, long maxBytesOnDisk, int maxWeeksOld) { if (Looper.getMainLooper().getThread() != callingThread) { throw new RuntimeException("Only call from main thread"); } ClientDataStorageManager.createGlobalInstance(ClientStumblerService.this, ClientStumblerService.this, maxBytesOnDisk, maxWeeksOld); init(); return ClientStumblerService.this; } public ClientStumblerService getService() { return ClientStumblerService.this; } } }