package com.noshufou.android.su.service;
import com.noshufou.android.su.provider.PermissionsProvider.Apps;
import com.noshufou.android.su.provider.PermissionsProvider.Logs;
import com.noshufou.android.su.util.Util;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.IBinder;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Log;
public class PermissionsDbService extends IntentService {
private static final String TAG = "Su.PermissionsDbService";
private static final String ATTEMPT_PREF = "clean_permissions_attempt";
private static final String[] PROJECTION = new String[] {
Apps._ID, Apps.UID, Apps.PACKAGE, Apps.NAME, Apps.EXEC_UID, Apps.EXEC_CMD,
Apps.ALLOW, Apps.DIRTY
};
private static final int COLUMN_ID = 0;
private static final int COLUMN_UID = 1;
private static final int COLUMN_PACKAGE = 2;
private static final int COLUMN_NAME = 3;
private static final int COLUMN_EXEC_UID = 4;
private static final int COLUMN_EXEC_CMD = 5;
private static final int COLUMN_ALLOW = 6;
public PermissionsDbService() {
super(TAG);
}
@Override
public IBinder onBind(Intent arg0) {
// This service doesn't allow binding.
return null;
}
@Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent()");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
int attempt = prefs.getInt(ATTEMPT_PREF, 0);
boolean allDirty = false;
if (attempt > 10) {
Log.d(TAG, "tried to many times, wipe permissions.sqlite and start again");
deleteDatabase("permissions.sqlite");
allDirty = true;
} else if (attempt > 12) {
// we're obviously not getting anywhere with this, try agin next time
return;
}
SQLiteDatabase pDb = null;
ContentResolver cr = getContentResolver();
try {
pDb = new PermissionsDbOpenHelper(this).getWritableDatabase();
Log.d(TAG, "permissions.sqlite opened");
String where = null;
String whereArgs[] = null;
if (!allDirty) {
where = Apps.DIRTY + "=?";
whereArgs = new String[] { "1" };
}
Cursor c = cr.query(Apps.CONTENT_URI,
PROJECTION, where, whereArgs, null);
Log.d(TAG, "got cursor from su.db");
while (c.moveToNext()) {
Log.d(TAG, "row " + c.getLong(COLUMN_ID) + " dirty, handle it");
String deleteWhere = Apps.UID + "=? AND " + Apps.EXEC_UID
+ "=? AND " + Apps.EXEC_CMD + "=?";
String[] deleteWhereArgs = new String[] {
c.getString(COLUMN_UID), c.getString(COLUMN_EXEC_UID),
c.getString(COLUMN_EXEC_CMD)
};
if (c.getInt(COLUMN_ALLOW) == Apps.AllowType.TO_DELETE) {
Log.d(TAG, "needs deleted");
pDb.delete(Apps.TABLE_NAME, deleteWhere, deleteWhereArgs);
cr.delete(Uri.withAppendedPath(Apps.CONTENT_URI, "clean"),
deleteWhere, deleteWhereArgs);
Log.d(TAG, "delete completed");
continue;
} else if (c.getInt(COLUMN_ALLOW) == Apps.AllowType.ASK) {
continue;
}
Log.d(TAG, "Updating permissions.sqlite for " + c.getString(COLUMN_NAME));
pDb.delete(Apps.TABLE_NAME, deleteWhere, deleteWhereArgs);
ContentValues values = new ContentValues();
values.put(Apps._ID, c.getLong(COLUMN_ID));
values.put(Apps.UID, c.getInt(COLUMN_UID));
values.put(Apps.PACKAGE, c.getString(COLUMN_PACKAGE));
values.put(Apps.NAME, c.getString(COLUMN_NAME));
values.put(Apps.EXEC_UID, c.getInt(COLUMN_EXEC_UID));
values.put(Apps.EXEC_CMD, c.getString(COLUMN_EXEC_CMD));
values.put(Apps.ALLOW, c.getInt(COLUMN_ALLOW));
pDb.insertOrThrow(Apps.TABLE_NAME, null, values);
Log.d(TAG, "completed update");
ContentValues notDirty = new ContentValues();
notDirty.put(Apps.DIRTY, "0");
cr.update(
ContentUris.withAppendedId(Apps.CONTENT_URI, c.getLong(COLUMN_ID)),
notDirty, null, null);
Log.d(TAG, "updated su.db as clean");
}
} catch (SQLiteException e) {
// Something went wrong in the DB, schedule this service to start again
// and quit.
Log.e(TAG, "encountered an exception, schedule a restart", e);
prefs.edit().putInt(ATTEMPT_PREF, attempt + 1).commit();
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + 60 * 1000,
PendingIntent.getBroadcast(this, 0,
new Intent("com.noshufou.android.su.UPDATE_PERMISSIONS"), 0));
return;
} finally {
if (pDb != null) {
Log.d(TAG, "closing permissions.sqlite");
pDb.close();
}
}
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putBoolean("permissions_dirty", false)
.putInt(ATTEMPT_PREF, 0)
.commit();
}
private class PermissionsDbOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "permissions.sqlite";
private static final int DATABASE_VERSION = 8;
private static final String LOG_BLOCK = "CREATE TRIGGER IF NOT EXISTS log_block " +
"AFTER INSERT ON logs BEGIN DELETE FROM logs; END;";
public PermissionsDbOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(Apps.CREATE);
db.execSQL(Logs.CREATE);
db.execSQL(LOG_BLOCK);
makePrefs(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
int upgradeVersion = oldVersion;
// Pattern for upgrade blocks
//
// if (upgradeVersion == [the DATABASE_VERSION you set] - 1) {
// .. your upgrade logic ..
// upgradeVersion = [the DATABASE_VERSION you set]
// }
if (upgradeVersion < 5) {
// Really lazy here... Plus I can't really remember the structure
// of the apps table before version 5...
// Don't drop this table yet, causes the prompt to fail if coming from
// 2.x
// db.execSQL("DROP TABLE IF EXISTS permissions;");
db.execSQL("DROP TABLE IF EXISTS apps;");
db.execSQL("DROP TABLE IF EXISTS logs;");
onCreate(db);
return;
}
if (upgradeVersion == 5) {
try {
db.execSQL("ALTER TABLE apps ADD COLUMN notifications INTEGER");
db.execSQL("ALTER TABLE apps ADD COLUMN logging INTEGER");
} catch (SQLiteException e) {
// We're getting this exception because the columns already exist
// for some reason...
Log.e(TAG, "notifications and logging columns already exist... wut?", e);
}
upgradeVersion = 6;
}
if (upgradeVersion == 6) {
Cursor c = db.query(Apps.TABLE_NAME, new String[] { Apps._ID, Apps.UID },
null, null, null, null, null);
ContentValues values;
while (c.moveToNext()) {
int uid = c.getInt(c.getColumnIndex(Apps.UID));
long appId = c.getLong(c.getColumnIndex(Apps._ID));
values = new ContentValues();
values.put(Apps.NAME, Util.getAppName(PermissionsDbService.this, uid, false));
values.put(Apps.PACKAGE, Util.getAppPackage(PermissionsDbService.this, uid));
db.update(Apps.TABLE_NAME, values, Apps._ID + "=?",
new String[] { String.valueOf(appId) });
}
c.close();
upgradeVersion = 7;
}
if (upgradeVersion == 7) {
db.execSQL(LOG_BLOCK);
makePrefs(db);
}
}
private void makePrefs(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS prefs " +
"(_id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT, value TEXT);");
ContentValues values = new ContentValues();
values.put("key", "notifications");
values.put("value", "1");
db.insert("prefs", null, values);
}
}
}