package com.robert.maps.applib.trackwriter; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import org.andnav.osm.util.constants.OpenStreetMapConstants; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.preference.PreferenceManager; import android.widget.Toast; import com.robert.maps.applib.R; import com.robert.maps.applib.kml.TrackListActivity; import com.robert.maps.applib.kml.TrackStatHelper; import com.robert.maps.applib.utils.DistanceFormatter; import com.robert.maps.applib.utils.Ut; public class TrackWriterService extends Service implements OpenStreetMapConstants { private SQLiteDatabase db; NotificationManager mNM; Notification mNotification; PendingIntent mContentIntent; final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); private TrackStatHelper mTrackStat = new TrackStatHelper(); private DistanceFormatter mDf; // private String mLogFileName; protected LocationManager mLocationManager; protected SampleLocationListener mLocationListener; final RemoteCallbackList<ITrackWriterCallback> mCallbacks = new RemoteCallbackList<ITrackWriterCallback>(); private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { public void registerCallback(ITrackWriterCallback cb) { if (cb != null) { mCallbacks.register(cb); if(mTrackStat != null) { try { cb.onTrackStatUpdate(mTrackStat.Cnt, mTrackStat.Distance, mTrackStat.Duration, mTrackStat.MaxSpeed, mTrackStat.AvgSpeed, mTrackStat.MoveTime, mTrackStat.AvgMoveSpeed); } catch (RemoteException e) { } } } } public void unregisterCallback(ITrackWriterCallback cb) { if (cb != null) mCallbacks.unregister(cb); } }; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: { // Broadcast to all clients the new value. final int N = mCallbacks.beginBroadcast(); for (int i=0; i<N; i++) { try { final Location loc = (Location)msg.obj; mCallbacks.getBroadcastItem(i).newPointWrited(loc.getLatitude(), loc.getLongitude()); mCallbacks.getBroadcastItem(i).onTrackStatUpdate(mTrackStat.Cnt, mTrackStat.Distance, mTrackStat.Duration, mTrackStat.MaxSpeed, mTrackStat.AvgSpeed, mTrackStat.MoveTime, mTrackStat.AvgMoveSpeed); } catch (RemoteException e) { } } mCallbacks.finishBroadcast(); } break; default: super.handleMessage(msg); } } }; // public class CrashReportHandler implements UncaughtExceptionHandler { // // @Override // public void uncaughtException(Thread thread, Throwable ex) { // StringWriter stackTrace=new StringWriter(); // ex.printStackTrace(new PrintWriter(stackTrace)); // // appendLog(stackTrace.toString()); // Process.killProcess(Process.myPid()); // System.exit(10); // } // // } // @Override public void onCreate() { super.onCreate(); // mLogFileName = Ut.getRMapsMainDir(this, "").getAbsolutePath()+"/trackwriter.log"; // // Thread.setDefaultUncaughtExceptionHandler(new CrashReportHandler()); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); sdf.applyPattern("HH:mm:ss"); // appendLog("onCreate"); mDf = new DistanceFormatter(this); mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); try { mStartForeground = getClass().getMethod("startForeground", mStartForegroundSignature); mStopForeground = getClass().getMethod("stopForeground", mStopForegroundSignature); return; } 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!"); } } //final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>(); @Override public void onStart(Intent intent, int startId) { if(intent != null) handleCommand(intent); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if(intent != null) handleCommand(intent); return START_STICKY; } private void handleCommand(Intent intent) { // appendLog("handleCommand"); final File folder = Ut.getRMapsMainDir(this, "data"); if(folder.canRead()){ try { db = new DatabaseHelper(this, folder.getAbsolutePath() + "/writedtrack.db").getWritableDatabase(); } catch (Exception e) { db = null; } }; if(db == null){ Toast.makeText(this, getString(R.string.message_cantstarttrackwriter)+" "+folder.getAbsolutePath(), Toast.LENGTH_LONG).show(); this.stopSelf(); return; }; final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); final int minTime = Integer.parseInt(pref.getString("pref_trackwriter_mintime", "2000")); final int minDistance = Integer.parseInt(pref.getString("pref_trackwriter_mindistance", "10")); mLocationListener = new SampleLocationListener(); // appendLog("requestLocationUpdates minTime="+minTime+" minDistance="+minDistance); getLocationManager().requestLocationUpdates(GPS, minTime, minDistance, this.mLocationListener); showNotification(); } private void showNotification() { CharSequence text = getText(R.string.remote_service_started); mNotification = new Notification(R.drawable.track_writer_service, text, System.currentTimeMillis()); mNotification.flags = mNotification.flags | Notification.FLAG_NO_CLEAR; mContentIntent = PendingIntent.getActivity(this, 0, new Intent(this, TrackListActivity.class), 0); mNotification.setLatestEventInfo(this, getText(R.string.remote_service_started), text, mContentIntent); startForegroundCompat(R.string.remote_service_started, mNotification); } @Override public IBinder onBind(Intent arg0) { return mBinder; } @Override public void onDestroy() { // appendLog("onDestroy"); stopForegroundCompat(R.string.remote_service_started); if(mLocationListener != null) getLocationManager().removeUpdates(mLocationListener); if(db != null) db.close(); if(mCallbacks != null) mCallbacks.kill(); } private LocationManager getLocationManager() { if(this.mLocationManager == null) this.mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); return this.mLocationManager; } private class SampleLocationListener implements LocationListener { public void onLocationChanged(final Location loc) { if (loc != null){ // appendLog("onLocationChanged "+loc.toString()); addPoint(loc.getLatitude(), loc.getLongitude(), loc.getAltitude(), loc.getSpeed(), loc.getTime()); mTrackStat.addPoint(loc); mHandler.sendMessage(mHandler.obtainMessage(1, loc)); final String text = "" +sdf.format(new Date(mTrackStat.Duration)) +" | " + mDf.formatDistance(mTrackStat.Distance) +" | " + mDf.formatSpeed(mTrackStat.AvgSpeed) ; mNotification.setLatestEventInfo(TrackWriterService.this, getText(R.string.remote_service_started), text, mContentIntent); mNM.notify(R.string.remote_service_started, mNotification); } } public void onStatusChanged(String a, int status, Bundle b) { // appendLog("onStatusChanged provider="+a+" status=" // + (status == LocationProvider.OUT_OF_SERVICE ? "OUT_OF_SERVICE" : status == LocationProvider.TEMPORARILY_UNAVAILABLE ? "TEMPORARILY_UNAVAILABLE" : status == LocationProvider.AVAILABLE ? "AVAILABLE" : "UNKNOWN") // +" satellites="+b.getInt("satellites", -1) // ); } public void onProviderEnabled(String a) { // appendLog("onProviderEnabled provider="+a); } public void onProviderDisabled(String a) { // appendLog("onProviderDisabled provider="+a); } } public void addPoint(double latitude, double longitude, double altitude, float speed, long currentTimeMillis) { final ContentValues cv = new ContentValues(); cv.put("trackid", 0); cv.put("lat", latitude); cv.put("lon", longitude); cv.put("alt", altitude); cv.put("speed", speed); cv.put("date", currentTimeMillis / 1000); this.db.insert("trackpoints", null, cv); } private static final Class<?>[] mSetForegroundSignature = new Class[] { boolean.class}; private static final Class<?>[] mStartForegroundSignature = new Class[] { int.class, Notification.class}; private static final Class<?>[] mStopForegroundSignature = new Class[] { boolean.class}; private Method mSetForeground; private Method mStartForeground; private Method mStopForeground; private Object[] mSetForegroundArgs = new Object[1]; private Object[] mStartForegroundArgs = new Object[2]; private Object[] mStopForegroundArgs = new Object[1]; void invokeMethod(Method method, Object[] args) { try { method.invoke(this, args); } catch (InvocationTargetException e) { } catch (IllegalAccessException e) { } } /** * This is a wrapper around the new startForeground method, using the older * APIs if it is not available. */ void startForegroundCompat(int id, Notification notification) { // If we have the new startForeground API, then use it. if (mStartForeground != null) { mStartForegroundArgs[0] = Integer.valueOf(id); mStartForegroundArgs[1] = notification; invokeMethod(mStartForeground, mStartForegroundArgs); return; } // Fall back on the old API. mSetForegroundArgs[0] = Boolean.TRUE; invokeMethod(mSetForeground, mSetForegroundArgs); mNM.notify(id, notification); } /** * This is a wrapper around the new stopForeground method, using the older * APIs if it is not available. */ 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. mNM.cancel(id); mSetForegroundArgs[0] = Boolean.FALSE; invokeMethod(mSetForeground, mSetForegroundArgs); } }