package net.wigle.wigleandroid;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
public final class WigleService extends Service {
private static final int NOTIFICATION_ID = 1;
private GuardThread guardThread;
private final AtomicBoolean done = new AtomicBoolean( false );
private Bitmap largeIcon = null;
// copied from javadoc
private static final Class<?>[] mSetForegroundSignature = new Class[] {
boolean.class};
@SuppressWarnings("rawtypes")
private static final Class[] mStartForegroundSignature = new Class[] {
int.class, Notification.class};
@SuppressWarnings("rawtypes")
private static final Class[] mStopForegroundSignature = new Class[] {
boolean.class};
private NotificationManager notificationManager;
private Method mSetForeground;
private Method mStartForeground;
private Method mStopForeground;
private final Object[] mSetForegroundArgs = new Object[1];
private final Object[] mStartForegroundArgs = new Object[2];
private final Object[] mStopForegroundArgs = new Object[1];
private class GuardThread extends Thread {
public GuardThread() {
}
@Override
public void run() {
Thread.currentThread().setName( "GuardThread-" + Thread.currentThread().getName() );
while ( ! done.get() ) {
MainActivity.sleep( 15000L );
setupNotification();
}
MainActivity.info("GuardThread done");
}
}
private void setDone() {
done.set( true );
guardThread.interrupt();
}
@Override
public IBinder onBind( final Intent intent ) {
MainActivity.info( "service: onbind. intent: " + intent );
return null;
}
@Override
public void onRebind( final Intent intent ) {
MainActivity.info( "service: onRebind. intent: " + intent );
super.onRebind( intent );
}
@Override
public boolean onUnbind( final Intent intent ) {
MainActivity.info( "service: onUnbind. intent: " + intent );
shutdownNotification();
stopSelf();
return super.onUnbind( intent );
}
@Override
public void onCreate() {
MainActivity.info( "service: onCreate" );
notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
try {
mStartForeground = getClass().getMethod("startForeground",
mStartForegroundSignature);
mStopForeground = getClass().getMethod("stopForeground",
mStopForegroundSignature);
} catch (NoSuchMethodException e) {
// Running on an older platform.
mStartForeground = mStopForeground = null;
}
try {
mSetForeground = getClass().getMethod("setForeground",
mSetForegroundSignature);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"OS doesn't have Service.startForeground OR Service.setForeground!");
}
setupNotification();
// don't use guard thread
guardThread = new GuardThread();
guardThread.start();
super.onCreate();
}
@Override
public void onDestroy() {
MainActivity.info( "service: onDestroy" );
// Make sure our notification is gone.
shutdownNotification();
setDone();
super.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
MainActivity.info( "service: onLowMemory" );
}
//This is the old onStart method that will be called on the pre-2.0
//platform. On 2.0 or later we override onStartCommand() so this
//method will not be called.
@SuppressWarnings("deprecation")
@Override
public void onStart( Intent intent, int startId ) {
MainActivity.info( "service: onStart" );
handleCommand( intent );
}
@Override
public int onStartCommand( Intent intent, int flags, int startId ) {
MainActivity.info( "service: onStartCommand" );
handleCommand( intent );
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return 1;
}
private void handleCommand( Intent intent ) {
MainActivity.info( "service: handleCommand: intent: " + intent );
}
private void shutdownNotification() {
stopForegroundCompat( NOTIFICATION_ID );
}
private void setupNotification() {
if ( ! done.get() ) {
final long when = System.currentTimeMillis();
final Context context = getApplicationContext();
final String title = context.getString(R.string.wigle_service);
final Intent notificationIntent = new Intent( this, MainActivity.class );
final PendingIntent contentIntent = PendingIntent.getActivity( this, 0, notificationIntent, 0 );
final long dbNets = ListFragment.lameStatic.dbNets;
String text = context.getString(R.string.list_waiting_gps);
if ( dbNets > 0 ) {
text = context.getString(R.string.run) + ": " + ListFragment.lameStatic.runNets
+ " "+ context.getString(R.string.new_word) + ": " + ListFragment.lameStatic.newNets
+ " "+ context.getString(R.string.db) + ": " + dbNets;
}
if (! MainActivity.isScanning(context)) {
text = context.getString(R.string.list_scanning_off) + " " + text;
}
if (largeIcon == null) {
largeIcon = BitmapFactory.decodeResource(getResources(), R.drawable.wiglewifi);
}
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setContentIntent(contentIntent);
builder.setNumber((int)ListFragment.lameStatic.newNets);
builder.setTicker(title);
builder.setContentTitle(title);
builder.setContentText(text);
builder.setWhen(when);
builder.setLargeIcon(largeIcon);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setSmallIcon(R.drawable.wiglewifi_small_white);
}
else {
builder.setSmallIcon(R.drawable.wiglewifi_small);
}
builder.setOngoing(true);
builder.setCategory("SERVICE");
builder.setPriority(NotificationCompat.PRIORITY_LOW);
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
final Uri uri = Uri.EMPTY;
final Intent pauseSharedIntent = new Intent(Intent.ACTION_DELETE, uri, this, ShareActivity.class );
final PendingIntent pauseIntent = PendingIntent.getActivity( this, 0, pauseSharedIntent, 0 );
builder.addAction(R.drawable.wiglewifi_small_black_white, "Pause", pauseIntent);
final Intent scanSharedIntent = new Intent(Intent.ACTION_INSERT, uri, this, ShareActivity.class );
final PendingIntent scanIntent = PendingIntent.getActivity( this, 0, scanSharedIntent, 0 );
builder.addAction(R.drawable.wiglewifi_small_black_white, "Scan", scanIntent);
// final Intent uploadSharedIntent = new Intent(Intent.ACTION_SYNC, uri, this, ShareActivity.class );
// final PendingIntent uploadIntent = PendingIntent.getActivity( this, 0, uploadSharedIntent, 0 );
// builder.addAction(R.drawable.wiglewifi_small_black_white, "Upload", uploadIntent);
final Notification notification = builder.build();
startForegroundCompat( NOTIFICATION_ID, notification );
}
}
void invokeMethod(Method method, Object[] args) {
//noinspection TryWithIdenticalCatches
try {
method.invoke(this, args);
} catch (InvocationTargetException e) {
// Should not happen.
MainActivity.warn("Unable to invoke method", e);
} catch (IllegalAccessException e) {
// Should not happen.
MainActivity.warn("Unable to invoke method", e);
}
}
/**
* This is a wrapper around the new startForeground method, using the older
* APIs if it is not available.
*/
private void startForegroundCompat(int id, Notification notification) {
// If we have the new startForeground API, then use it.
if (mStartForeground != null) {
mStartForegroundArgs[0] = id;
mStartForegroundArgs[1] = notification;
invokeMethod(mStartForeground, mStartForegroundArgs);
return;
}
// Fall back on the old API.
mSetForegroundArgs[0] = Boolean.TRUE;
invokeMethod(mSetForeground, mSetForegroundArgs);
notificationManager.notify(id, notification);
}
/**
* This is a wrapper around the new stopForeground method, using the older
* APIs if it is not available.
*/
private void stopForegroundCompat(int id) {
// If we have the new stopForeground API, then use it.
if (mStopForeground != null) {
mStopForegroundArgs[0] = Boolean.TRUE;
invokeMethod(mStopForeground, mStopForegroundArgs);
return;
}
// Fall back on the old API. Note to cancel BEFORE changing the
// foreground state, since we could be killed at that point.
notificationManager.cancel(id);
mSetForegroundArgs[0] = Boolean.FALSE;
invokeMethod(mSetForeground, mSetForegroundArgs);
}
}