package com.nolanlawson.apptracker; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Date; import java.util.Random; import java.util.concurrent.TimeUnit; import android.app.AlarmManager; import android.app.IntentService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import com.nolanlawson.apptracker.data.AnalyzedLogLine; import com.nolanlawson.apptracker.db.AppHistoryDbHelper; import com.nolanlawson.apptracker.helper.LogAnalyzer; import com.nolanlawson.apptracker.helper.PreferenceHelper; import com.nolanlawson.apptracker.util.UtilLogger; /** * Reads logs. Named "AppTrackerService" in order to obfuscate, so the user * won't get freaked out if they see e.g. "LogReaderService" running on their * phone. * * * @author nolan * */ public class AppTrackerService extends IntentService { private static final Class<?>[] mStartForegroundSignature = new Class[] { int.class, Notification.class}; private static final Class<?>[] mStopForegroundSignature = new Class[] { boolean.class}; private static UtilLogger log = new UtilLogger(AppTrackerService.class); private boolean kill = false; private NotificationManager mNM; private Method mStartForeground; private Method mStopForeground; private Object[] mStartForegroundArgs = new Object[2]; private Object[] mStopForegroundArgs = new Object[1]; private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { log.d("Screen waking up; updating widgets"); AppHistoryDbHelper dbHelper = new AppHistoryDbHelper(getApplicationContext()); try { WidgetUpdater.updateWidget(context, dbHelper); } finally { dbHelper.close(); } } }; public AppTrackerService() { super("AppTrackerService"); } @Override public void onCreate() { super.onCreate(); log.d("onCreate()"); // update all widgets when the screen wakes up again - that's the case // where // the user unlocks their screen and sees the home screen, so we need // instant updates log.d("hello0"); registerReceiver(receiver, new IntentFilter(Intent.ACTION_SCREEN_ON)); log.d("hello1"); mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); log.d("hello2"); try { mStartForeground = getClass().getMethod("startForeground", mStartForegroundSignature); mStopForeground = getClass().getMethod("stopForeground", mStopForegroundSignature); } catch (NoSuchMethodException e) { // Running on an older platform. log.d(e,"running on older platform; couldn't find startForeground method"); mStartForeground = mStopForeground = null; } } @Override public void onDestroy() { log.d("onDestroy()"); super.onDestroy(); unregisterReceiver(receiver); // always restart the service if killed restartAppTrackerService(); kill = true; if (PreferenceHelper.getShowNotificationPreference(getApplicationContext())) { // Make sure our notification is gone. stopForegroundCompat(R.string.notification_title); } } @Override public void onLowMemory() { log.d("onLowMemory()"); super.onLowMemory(); // just to be safe, attempt to restart app tracker service 60 seconds after low memory // conditions are detected restartAppTrackerService(); } // This is the old onStart method that will be called on the pre-2.0 // platform. @Override public void onStart(Intent intent, int startId) { log.d("onStart()"); super.onStart(intent, startId); handleCommand(intent); } /* couldn't get this to work public int onStartCommand(Intent intent, int flags, int startId) { log.d("onStartCommand()"); try { Method superMethod = getClass().getSuperclass().getMethod("onStartCommand", Intent.class, int.class, int.class); superMethod.se superMethod.invoke(super, intent, flags, startId); } catch (Exception e) { log.e(e, "couldn't invoke super method", mStartForegroundArgs); } super.onStartCommand(intent, flags, startId); handleCommand(intent); // We want this service to continue running until it is explicitly // stopped, so return sticky. return START_STICKY; }*/ private void handleCommand(Intent intent) { CharSequence tickerText = getText(R.string.notification_ticker); // Set the icon, scrolling text and timestamp Notification notification = new Notification(R.drawable.service_notification_1, tickerText, System.currentTimeMillis()); Intent appTrackerActivityIntent = new Intent(this, AppTrackerActivity.class); appTrackerActivityIntent.setAction(Intent.ACTION_MAIN); appTrackerActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // The PendingIntent to launch our activity if the user selects this notification PendingIntent contentIntent = PendingIntent.getActivity(this, 0, appTrackerActivityIntent, 0); // Set the info for the views that show in the notification panel. notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_subtext), contentIntent); if (PreferenceHelper.getShowNotificationPreference(getApplicationContext())) { startForegroundCompat(R.string.notification_title, notification); } //handleIntent(intent); } /** * 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] = Integer.valueOf(id); mStartForegroundArgs[1] = notification; try { mStartForeground.invoke(this, mStartForegroundArgs); } catch (InvocationTargetException e) { // Should not happen. log.d(e, "Unable to invoke startForeground"); } catch (IllegalAccessException e) { // Should not happen. log.d(e, "Unable to invoke startForeground"); } return; } // Fall back on the old API. setForeground(true); mNM.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; try { mStopForeground.invoke(this, mStopForegroundArgs); } catch (InvocationTargetException e) { // Should not happen. log.d(e, "Unable to invoke stopForeground"); } catch (IllegalAccessException e) { // Should not happen. log.d(e, "Unable to invoke stopForeground"); } 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); setForeground(false); } protected void onHandleIntent(Intent intent) { log.d("onHandleIntent()"); handleIntent(intent); } private void handleIntent(Intent intent) { log.d("Starting up AppTrackerService now with intent: %s", intent); Process logcatProcess = null; BufferedReader reader = null; AppHistoryDbHelper dbHelper = null; try { dbHelper = new AppHistoryDbHelper(this); int numLines = getNumberOfExistingLogLines(); log.d("number of existing lines in logcat log is %d", numLines); int currentLine = 0; // filter logcat only for ActivityManager messages of Info or higher logcatProcess = Runtime.getRuntime().exec( new String[] { "logcat", "ActivityManager:I", "*:S" }); reader = new BufferedReader(new InputStreamReader(logcatProcess .getInputStream())); String line; while ((line = reader.readLine()) != null) { if (kill) { log.d("manually killed AppTrackerService"); break; } if (++currentLine <= numLines) { //log.d("skipping line %d", currentLine); continue; } AnalyzedLogLine analyzedLogLine = LogAnalyzer.analyzeLogLine(line); if (analyzedLogLine == null) { continue; // not an ActivityManager line } else if (analyzedLogLine.isStartHomeActivity()) { // update the widget if it's the home activity, // so that the widgets stay up-to-date when the home screen is invoked WidgetUpdater.updateWidget(this, dbHelper); } else { // valid log line log.d("package name is: " + analyzedLogLine.getPackageName()); log.d("process name is: " + analyzedLogLine.getProcessName()); synchronized (AppHistoryDbHelper.class) { dbHelper.incrementAndUpdate(analyzedLogLine.getPackageName(), analyzedLogLine.getProcessName()); } WidgetUpdater.updateWidget(this, dbHelper); } } } catch (IOException e) { log.e(e, "unexpected exception"); } finally { if (dbHelper != null) { dbHelper.close(); } if (reader != null) { try { reader.close(); } catch (IOException e) { log.e(e, "unexpected exception"); } } if (logcatProcess != null) { logcatProcess.destroy(); } log.i("AppTrackerService died"); } } private int getNumberOfExistingLogLines() throws IOException { // figure out how many lines are already in the logcat log // to do this, just use the -d (for "dump") command in logcat Process logcatProcess = Runtime.getRuntime().exec( new String[] { "logcat", "-d", "ActivityManager:I", "*:S" }); BufferedReader reader = new BufferedReader(new InputStreamReader(logcatProcess .getInputStream())); try { int lines = 0; while (reader.readLine() != null) { lines++; } reader.close(); logcatProcess.destroy(); return lines; } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { log.e(e, "unexpected exception"); } } if (logcatProcess != null) { logcatProcess.destroy(); } } } private void restartAppTrackerService() { log.d("Attempting to restart appTrackerService because it was killed."); Intent restartServiceIntent = new Intent(); restartServiceIntent.setAction(AppTrackerWidgetProvider.ACTION_RESTART_SERVICE); // have to make this unique for God knows what reason restartServiceIntent.setData(Uri.withAppendedPath(Uri.parse(AppTrackerWidgetProvider.URI_SCHEME + "://widget/restart/"), Long.toHexString(new Random().nextLong()))); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0 /* no requestCode */, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT); AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); long timeToExecute = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(60); // start 60 seconds from now alarms.set(AlarmManager.RTC, timeToExecute, pendingIntent); log.i("AppTrackerService will restart at %s", new Date(timeToExecute)); } }