package com.noshufou.android.su.service; import android.app.IntentService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.database.Cursor; import android.net.Uri; import android.os.Handler; import android.preference.PreferenceManager; import android.util.Log; import android.widget.Toast; import com.noshufou.android.su.HomeActivity; import com.noshufou.android.su.R; import com.noshufou.android.su.SuRequestReceiver; import com.noshufou.android.su.preferences.Preferences; import com.noshufou.android.su.provider.PermissionsProvider.Apps; import com.noshufou.android.su.provider.PermissionsProvider.Logs; import com.noshufou.android.su.util.Util; public class ResultService extends IntentService { private static final String TAG = "Su.ResultService"; public static final String EXTRA_ACTION = "action"; public static final int ACTION_RESULT = 1; public static final int ACTION_RECYCLE = 2; final String LAST_NOTIFICATION_UID = "last_notification_uid"; final String LAST_NOTIFICATION_TIME = "last_notification_time"; public static final String[] PROJECTION = new String[] { Apps._ID, Apps.ALLOW, Apps.NOTIFICATIONS, Apps.LOGGING }; private static final int COLUMN_ID = 0; private static final int COLUMN_ALLOW = 1; private static final int COLUMN_NOTIFICATIONS = 2; private static final int COLUMN_LOGGING = 3; // TODO: Add in a license check here // private boolean mLicenseChecked = false; // private boolean mLicensed = true; private SharedPreferences mPrefs = null; private boolean mNotify = true; private String mNotifyType = "toast"; private boolean mLog = true; private Handler mHandler; public ResultService() { super(TAG); } @Override public void onCreate() { super.onCreate(); mHandler = new Handler(); } @Override protected void onHandleIntent(Intent intent) { switch (intent.getIntExtra(EXTRA_ACTION, 0)) { case ACTION_RESULT: ensurePrefs(); int callerUid = intent.getIntExtra(SuRequestReceiver.EXTRA_CALLERUID, 0); int allow = intent.getIntExtra(SuRequestReceiver.EXTRA_ALLOW, -1); long currentTime = System.currentTimeMillis(); long appId = -1; String appNotify = null; String appLog = null; // get what we need from the database Cursor c = getContentResolver().query( Uri.withAppendedPath(Apps.CONTENT_URI, "uid/" + callerUid), PROJECTION, null, null, null); if (c != null && c.moveToFirst()) { appId = c.getLong(COLUMN_ID); appNotify = c.getString(COLUMN_NOTIFICATIONS); appLog = c.getString(COLUMN_LOGGING); int dbAllow = c.getInt(COLUMN_ALLOW); if (dbAllow != -1) { allow = dbAllow; } } c.close(); sendNotification(appId, callerUid, allow, currentTime, appNotify); addLog(appId, callerUid, intent.getIntExtra(SuRequestReceiver.EXTRA_UID, 0), intent.getStringExtra(SuRequestReceiver.EXTRA_CMD), allow, currentTime, appLog); // No break statement here so that we can fall through and recycle the log case ACTION_RECYCLE: recycle(); break; default: throw new IllegalArgumentException(); } } private void sendNotification(long appId, int callerUid, int allow, long currentTime, String appNotify) { // Check to see if we should notify if ((appNotify == null && !mNotify) || (appNotify != null && appNotify.equals("0")) || allow == -1) { return; } final String notificationMessage = getString( allow==1?R.string.notification_text_allow:R.string.notification_text_deny, Util.getAppName(this, callerUid, false)); if (mNotifyType.equals("toast")) { ensurePrefs(); int lastNotificationUid = mPrefs.getInt(LAST_NOTIFICATION_UID, 0); long lastNotificationTime = mPrefs.getLong(LAST_NOTIFICATION_TIME, 0); final int gravity = Integer.parseInt(mPrefs.getString(Preferences.TOAST_LOCATION, "0")); if (lastNotificationUid != callerUid || lastNotificationTime + (5 * 1000) < currentTime) { mHandler.post(new Runnable() { @Override public void run() { Toast toast = Toast.makeText(getApplicationContext(), notificationMessage, Toast.LENGTH_SHORT); if (gravity > 0) { toast.setGravity(gravity, 0, 0); } toast.show(); } }); Editor editor = mPrefs.edit(); editor.putInt(LAST_NOTIFICATION_UID, callerUid); editor.putLong(LAST_NOTIFICATION_TIME, currentTime); editor.commit(); } } else if (mNotifyType.equals("status")) { NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Intent notificationIntent = new Intent(this, HomeActivity.class); // TODO: Include extras to tell HomeActivity what to do PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); Notification notification = new Notification(R.drawable.stat_su, notificationMessage, currentTime); notification.setLatestEventInfo(this, getString(R.string.app_name), notificationMessage, contentIntent); notification.flags |= Notification.FLAG_AUTO_CANCEL|Notification.FLAG_ONLY_ALERT_ONCE; nm.notify(callerUid, notification); } } private void addLog(long appId, int callerUid, int execUid, String execCmd, int allow, long currentTime, String appLog) { // Check to see if we should log if ((appLog == null && !mLog) || (appLog != null && appLog.equals("0")) || allow == -1) { return; } ContentValues values = new ContentValues(); if (appId == -1) { // App was not found in the database, add a row for logging purposes values.put(Apps.UID, callerUid); values.put(Apps.PACKAGE, Util.getAppPackage(this, callerUid)); values.put(Apps.NAME, Util.getAppName(this, callerUid, false)); values.put(Apps.EXEC_UID, execUid); values.put(Apps.EXEC_CMD, execCmd); values.put(Apps.ALLOW, Apps.AllowType.ASK); appId = Long.parseLong(getContentResolver().insert(Apps.CONTENT_URI, values) .getLastPathSegment()); } values.clear(); values.put(Logs.DATE, currentTime); values.put(Logs.TYPE, allow); getContentResolver() .insert(Uri.withAppendedPath(Logs.CONTENT_URI, String.valueOf(appId)), values); } private void recycle() { ensurePrefs(); if (!mPrefs.getBoolean(Preferences.DELETE_OLD_LOGS, true)) { // Log recycling is disabled, no need to go further return; } int limit = mPrefs.getInt(Preferences.LOG_ENTRY_LIMIT, 200); Cursor c = getContentResolver().query(Logs.CONTENT_URI, new String[] { "COUNT() as rows" }, null, null, null); c.moveToFirst(); int count = c.getInt(0); c.close(); if (count > limit) { c = getContentResolver().query(Logs.CONTENT_URI, new String[] { Logs._ID }, null, null, Logs.DATE + " ASC"); long id = 0; while (count > limit && c.moveToNext()) { id = c.getLong(0); count -= getContentResolver().delete(Logs.CONTENT_URI, Logs._ID + "=?", new String[] { String.valueOf(id) }); } } } private void ensurePrefs() { if (mPrefs == null) { mPrefs = PreferenceManager.getDefaultSharedPreferences(this); // read some global settings that we need every time mNotify = mPrefs.getBoolean(Preferences.NOTIFICATIONS, true); mNotifyType = mPrefs.getString(Preferences.NOTIFICATION_TYPE, "toast"); mLog = mPrefs.getBoolean(Preferences.LOGGING, true); } } }