Finalize masquerade rootless functionality with Substratum [2/3]

Change-Id: I61639bfdf395a32b9bd705edb295520642cbd8b5
diff --git a/app/src/main/java/masquerade/substratum/services/JobService.java b/app/src/main/java/masquerade/substratum/services/JobService.java
index 3c1b859..20cf166 100644
--- a/app/src/main/java/masquerade/substratum/services/JobService.java
+++ b/app/src/main/java/masquerade/substratum/services/JobService.java
@@ -1,4 +1,3 @@
-
 package masquerade.substratum.services;
 
 import android.app.ActivityManager;
@@ -15,6 +14,7 @@
 import android.content.IntentSender;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
+import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInstaller;
@@ -26,6 +26,7 @@
 import android.media.RingtoneManager;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -81,12 +82,18 @@
     public static final String JOB_TIME_KEY = "job_time_key";
     public static final String INSTALL_LIST_KEY = "install_list";
     public static final String UNINSTALL_LIST_KEY = "uninstall_list";
-    public static final String WITH_RESTART_UI_KEY = "with_restart_ui";
     public static final String BOOTANIMATION_FILE_NAME = "bootanimation_file_name";
     public static final String FONTS_PID = "fonts_pid";
     public static final String FONTS_FILENAME = "fonts_filename";
     public static final String AUDIO_PID = "audio_pid";
     public static final String AUDIO_FILENAME = "audio_filename";
+    public static final String ENABLE_LIST_KEY = "enable_list";
+    public static final String DISABLE_LIST_KEY = "disable_list";
+    public static final String PRIORITY_LIST_KEY = "priority_list";
+    public static final String SOURCE_FILE_KEY = "source_file";
+    public static final String DESTINATION_FILE_KEY = "destination_file";
+    public static final String WITH_DELETE_PARENT_KEY = "delete_parent";
+    public static final String PROFILE_NAME_KEY = "profile_name";
     public static final String COMMAND_VALUE_JOB_COMPLETE = "job_complete";
     public static final String COMMAND_VALUE_INSTALL = "install";
     public static final String COMMAND_VALUE_UNINSTALL = "uninstall";
@@ -95,6 +102,13 @@
     public static final String COMMAND_VALUE_BOOTANIMATION = "bootanimation";
     public static final String COMMAND_VALUE_FONTS = "fonts";
     public static final String COMMAND_VALUE_AUDIO = "audio";
+    public static final String COMMAND_VALUE_ENABLE = "enable";
+    public static final String COMMAND_VALUE_DISABLE = "disable";
+    public static final String COMMAND_VALUE_PRIORITY = "priority";
+    public static final String COMMAND_VALUE_COPY = "copy";
+    public static final String COMMAND_VALUE_MOVE = "move";
+    public static final String COMMAND_VALUE_DELETE = "delete";
+    public static final String COMMAND_VALUE_PROFILE = "profile";
 
     private static IOverlayManager mOMS;
     private static IPackageManager mPM;
@@ -104,6 +118,7 @@
     private MainHandler mMainHandler;
     private final List<Runnable> mJobQueue = new ArrayList<>(0);
     private long mLastJobTime;
+    private boolean mIsRunning;
 
     @Override
     public void onCreate() {
@@ -121,11 +136,10 @@
             return START_NOT_STICKY;
         }
 
-        // one job at a time please
-        // if (isProcessing()) {
-        // log("Got start command while still processing last job, aborting");
-        // return START_NOT_STICKY;
-        // }
+        // Don't run job if there is another running job
+        mIsRunning = false;
+        if (isProcessing()) mIsRunning = true;
+
         // filter out duplicate intents
         long jobTime = intent.getLongExtra(JOB_TIME_KEY, 1);
         if (jobTime == 1 || jobTime == mLastJobTime) {
@@ -156,9 +170,7 @@
             for (String _package : packages) {
                 jobs_to_add.add(new Remover(_package));
             }
-            if (intent.getBooleanExtra(WITH_RESTART_UI_KEY, false)) {
-                jobs_to_add.add(new UiResetJob());
-            }
+            if (shouldRestartUi(packages)) jobs_to_add.add(new UiResetJob());
         } else if (TextUtils.equals(command, COMMAND_VALUE_RESTART_UI)) {
             jobs_to_add.add(new UiResetJob());
         } else if (TextUtils.equals(command, COMMAND_VALUE_CONFIGURATION_SHIM)) {
@@ -180,6 +192,44 @@
             String fileName = intent.getStringExtra(AUDIO_FILENAME);
             jobs_to_add.add(new SoundsJob(pid, fileName));
             jobs_to_add.add(new UiResetJob());
+        } else if (TextUtils.equals(command, COMMAND_VALUE_ENABLE)) {
+            List<String> packages = intent.getStringArrayListExtra(ENABLE_LIST_KEY);
+            for (String _package : packages) {
+                jobs_to_add.add(new Enabler(_package));
+            }
+            if (shouldRestartUi(packages)) jobs_to_add.add(new UiResetJob());
+        } else if (TextUtils.equals(command, COMMAND_VALUE_DISABLE)) {
+            List<String> packages = intent.getStringArrayListExtra(DISABLE_LIST_KEY);
+            for (String _package : packages) {
+                jobs_to_add.add(new Disabler(_package));
+            }
+            if (shouldRestartUi(packages)) jobs_to_add.add(new UiResetJob());
+        } else if (TextUtils.equals(command, COMMAND_VALUE_PRIORITY)) {
+            List<String> packages = intent.getStringArrayListExtra(PRIORITY_LIST_KEY);
+            jobs_to_add.add(new PriorityJob(packages));
+            if (shouldRestartUi(packages)) jobs_to_add.add(new UiResetJob());
+        } else if (TextUtils.equals(command, COMMAND_VALUE_COPY)) {
+            String source = intent.getStringExtra(SOURCE_FILE_KEY);
+            String destination = intent.getStringExtra(DESTINATION_FILE_KEY);
+            jobs_to_add.add(new CopyJob(source, destination));
+        } else if (TextUtils.equals(command, COMMAND_VALUE_MOVE)) {
+            String source = intent.getStringExtra(SOURCE_FILE_KEY);
+            String destination = intent.getStringExtra(DESTINATION_FILE_KEY);
+            jobs_to_add.add(new MoveJob(source, destination));
+        } else if (TextUtils.equals(command, COMMAND_VALUE_DELETE)) {
+            String dir = intent.getStringExtra(SOURCE_FILE_KEY);
+            if (intent.getBooleanExtra(WITH_DELETE_PARENT_KEY, true)) {
+                jobs_to_add.add(new DeleteJob(dir));
+            } else {
+                for (File child : new File(dir).listFiles()) {
+                    jobs_to_add.add(new DeleteJob(child.getAbsolutePath()));
+                }
+            }
+        } else if (TextUtils.equals(command, COMMAND_VALUE_PROFILE)) {
+            List<String> enable = intent.getStringArrayListExtra(ENABLE_LIST_KEY);
+            List<String> disable = intent.getStringArrayListExtra(DISABLE_LIST_KEY);
+            String profile = intent.getStringExtra(PROFILE_NAME_KEY);
+            jobs_to_add.add(new ProfileJob(profile, disable, enable));
         }
 
         if (jobs_to_add.size() > 0) {
@@ -233,7 +283,8 @@
 
     private void install(String path, IPackageInstallObserver2 observer) {
         try {
-            getPM().installPackageAsUser(path, observer, PackageManager.INSTALL_REPLACE_EXISTING,
+            getPM().installPackageAsUser(path, observer,
+                    PackageManager.INSTALL_REPLACE_EXISTING,
                     null,
                     UserHandle.USER_SYSTEM);
         } catch (Exception e) {
@@ -241,22 +292,17 @@
         }
     }
 
-    private void uninstall(String packageName) {
+    private void uninstall(String packageName, IPackageDeleteObserver observer) {
         try {
-            final LocalIntentReceiver receiver = new LocalIntentReceiver();
-            getPM().getPackageInstaller().uninstall(packageName, null /* callerPackageName */, 0,
-                    receiver.getIntentSender(), UserHandle.USER_SYSTEM);
-            final Intent result = receiver.getResult();
-            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_FAILURE);
+            getPM().deletePackageAsUser(packageName, observer, 0, UserHandle.USER_SYSTEM);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 
-    private void disableOverlay(String packageName) {
+    private void switchOverlay(String packageName, boolean enable) {
         try {
-            getOMS().setEnabled(packageName, false, UserHandle.USER_SYSTEM, false);
+            getOMS().setEnabled(packageName, enable, UserHandle.USER_SYSTEM, false);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -265,14 +311,25 @@
     private boolean isOverlayEnabled(String packageName) {
         boolean enabled = false;
         try {
-            OverlayInfo info = getOMS().getOverlayInfo(packageName, UserHandle.USER_ALL);
-            enabled = info.isEnabled();
+            OverlayInfo info = getOMS().getOverlayInfo(packageName, UserHandle.USER_SYSTEM);
+            if (info != null) {
+                enabled = info.isEnabled();
+            } else {
+                log("info is null");
+            }
         } catch (RemoteException e) {
             e.printStackTrace();
         }
         return enabled;
     }
 
+    private boolean shouldRestartUi(List<String> overlays) {
+        for (String o : overlays) {
+            if (o.startsWith("com.android.systemui")) return true;
+        }
+        return false;
+    }
+
     private void copyFonts(String pid, String zipFileName) {
         // prepare local cache dir for font package assembly
         log("Copy Fonts - Package ID = " + pid + " filename = " + zipFileName);
@@ -328,23 +385,26 @@
         IOUtils.createFontDirIfNotExists();
         IOUtils.copyFolder(cacheDir.getAbsolutePath(), IOUtils.SYSTEM_THEME_FONT_PATH);
 
-        // set permissions on font files and config xml
-        File themeFonts = new File(IOUtils.SYSTEM_THEME_FONT_PATH);
-        for (File f : themeFonts.listFiles()) {
-            FileUtils.setPermissions(f,
-                    FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IRWXO, -1, -1);
-        }
-
         // let system know it's time for a font change
-        SystemProperties.set("sys.refresh_theme", "1");
-        float fontSize = Float.valueOf(Settings.System.getString(
-                getContentResolver(), Settings.System.FONT_SCALE));
-        Settings.System.putString(getContentResolver(),
-                Settings.System.FONT_SCALE, String.valueOf(fontSize + 0.0000001));
+        refreshFonts();
     }
 
     private void clearFonts() {
         IOUtils.deleteThemedFonts();
+        refreshFonts();
+    }
+
+    private void refreshFonts() {
+        // set permissions on font files and config xml
+        File themeFonts = new File(IOUtils.SYSTEM_THEME_FONT_PATH);
+        if (themeFonts.exists()) {
+            // Set permissions
+            IOUtils.setPermissionsRecursive(themeFonts,
+                    FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IRWXO,
+                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH);
+        }
+
+        // let system know it's time for a font change
         SystemProperties.set("sys.refresh_theme", "1");
         Typeface.recreateDefaults();
         float fontSize = Float.valueOf(Settings.System.getString(
@@ -395,56 +455,29 @@
             File effect_tick_ogg = new File(getCacheDir(), "/SoundsCache/ui/Effect_Tick.ogg");
             if (effect_tick_ogg.exists()) {
                 IOUtils.bufferedCopy(effect_tick_ogg, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "Effect_Tick.ogg"));
-                SoundUtils.setUIAudible(this, effect_tick_ogg, new File
-                        ("/data/system/theme/audio/ui/Effect_Tick.ogg"), RingtoneManager
-                        .TYPE_RINGTONE, "Effect_Tick");
             } else if (effect_tick_mp3.exists()) {
                 IOUtils.bufferedCopy(effect_tick_mp3, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "Effect_Tick.mp3"));
-                SoundUtils.setUIAudible(this, effect_tick_mp3, new File
-                        ("/data/system/theme/audio/ui/Effect_Tick.mp3"), RingtoneManager
-                        .TYPE_RINGTONE, "Effect_Tick");
-            } else {
-                SoundUtils.setDefaultUISounds(getContentResolver(), "lock_sound", "Lock.ogg");
             }
             File new_lock_mp3 = new File(getCacheDir(), "/SoundsCache/ui/Lock.mp3");
             File new_lock_ogg = new File(getCacheDir(), "/SoundsCache/ui/Lock.ogg");
             if (new_lock_ogg.exists()) {
                 IOUtils.bufferedCopy(new_lock_ogg, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "Lock.ogg"));
-                SoundUtils.setUISounds(getContentResolver(), "lock_sound", "/data/system/theme/audio/ui/Lock.ogg");
             } else if (new_lock_mp3.exists()) {
                 IOUtils.bufferedCopy(new_lock_mp3, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "Lock.mp3"));
-                SoundUtils.setUISounds(getContentResolver(), "lock_sound", "/data/system/theme/audio/ui/Lock.mp3");
-            } else {
-                SoundUtils.setDefaultUISounds(getContentResolver(), "lock_sound", "Lock.ogg");
             }
             File new_unlock_mp3 = new File(getCacheDir(), "/SoundsCache/ui/Unlock.mp3");
             File new_unlock_ogg = new File(getCacheDir(), "/SoundsCache/ui/Unlock.ogg");
             if (new_unlock_ogg.exists()) {
                 IOUtils.bufferedCopy(new_unlock_ogg, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "Unlock.ogg"));
-                SoundUtils.setUISounds(getContentResolver(), "unlock_sound", "/data/system/theme/audio/ui/Unlock.ogg");
             } else if (new_unlock_mp3.exists()) {
                 IOUtils.bufferedCopy(new_unlock_mp3, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "Unlock.mp3"));
-                SoundUtils.setUISounds(getContentResolver(), "unlock_sound", "/data/system/theme/audio/ui/Unlock.mp3");
-            } else {
-                SoundUtils.setDefaultUISounds(getContentResolver(), "unlock_sound", "Unlock.ogg");
             }
             File new_lowbattery_mp3 = new File(getCacheDir(), "/SoundsCache/ui/LowBattery.mp3");
             File new_lowbattery_ogg = new File(getCacheDir(), "/SoundsCache/ui/LowBattery.ogg");
             if (new_lowbattery_ogg.exists()) {
                 IOUtils.bufferedCopy(new_lowbattery_ogg, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "LowBattery.ogg"));
-                SoundUtils.setUISounds(getContentResolver(), "low_battery_sound", "/data/system/theme/audio/ui/LowBattery.ogg");
             } else if (new_lowbattery_mp3.exists()) {
                 IOUtils.bufferedCopy(new_lowbattery_mp3, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "LowBattery.mp3"));
-                SoundUtils.setUISounds(getContentResolver(), "low_battery_sound", "/data/system/theme/audio/ui/LowBattery.mp3");
-            } else {
-                SoundUtils.setDefaultUISounds(getContentResolver(), "low_battery_sound", "LowBattery.ogg");
-            }
-            File uiSounds = new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH);
-            if (uiSounds.exists() && uiSounds.isDirectory()) {
-                for (File f : uiSounds.listFiles()) {
-                    FileUtils.setPermissions(f,
-                            FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IRWXO, -1, -1);
-                }
             }
         }
         File alarmCache = new File(getCacheDir(), "/SoundsCache/alarms/");
@@ -454,27 +487,8 @@
             File new_alarm_ogg = new File(getCacheDir(), "/SoundsCache/alarms/alarm.ogg");
             if (new_alarm_ogg.exists()) {
                 IOUtils.bufferedCopy(new_alarm_ogg, new File(IOUtils.SYSTEM_THEME_ALARM_PATH + File.separator + "alarm.ogg"));
-                int metaDataId = getSubsContext().getResources().getIdentifier("content_resolver_alarm_metadata", "string", SUBSTRATUM_PACKAGE);
-                SoundUtils.setAudible(this, new File("/data/system/theme/audio/alarms/alarm.ogg"),
-                        new File(alarmCache.getAbsolutePath(), "alarm.ogg"),
-                        RingtoneManager.TYPE_ALARM,
-                        getSubsContext().getString(metaDataId));
             } else if (new_alarm_mp3.exists()) {
                 IOUtils.bufferedCopy(new_alarm_mp3, new File(IOUtils.SYSTEM_THEME_ALARM_PATH + File.separator + "alarm.mp3"));
-                int metaDataId = getSubsContext().getResources().getIdentifier("content_resolver_alarm_metadata", "string", SUBSTRATUM_PACKAGE);
-                SoundUtils.setAudible(this, new File("/data/system/theme/audio/alarms/alarm.mp3"),
-                        new File(alarmCache.getAbsolutePath(), "alarm.mp3"),
-                        RingtoneManager.TYPE_ALARM,
-                        getSubsContext().getString(metaDataId));
-            } else {
-                SoundUtils.setDefaultAudible(this, RingtoneManager.TYPE_ALARM);
-            }
-            File alarms = new File(IOUtils.SYSTEM_THEME_ALARM_PATH);
-            if (alarms.exists() && alarms.isDirectory()) {
-                for (File f : alarms.listFiles()) {
-                    FileUtils.setPermissions(f,
-                            FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IRWXO, -1, -1);
-                }
             }
         }
         File notifCache = new File(getCacheDir(), "/SoundsCache/notifications/");
@@ -484,27 +498,8 @@
             File new_notif_ogg = new File(getCacheDir(), "/SoundsCache/notifications/notification.ogg");
             if (new_notif_ogg.exists()) {
                 IOUtils.bufferedCopy(new_notif_ogg, new File(IOUtils.SYSTEM_THEME_NOTIFICATION_PATH + File.separator + "notification.ogg"));
-                int metaDataId = getSubsContext().getResources().getIdentifier("content_resolver_notification_metadata", "string", SUBSTRATUM_PACKAGE);
-                SoundUtils.setAudible(this, new File("/data/system/theme/audio/notifications/notification.ogg"),
-                        new File(notifCache.getAbsolutePath(), "notification.ogg"),
-                        RingtoneManager.TYPE_NOTIFICATION,
-                        getSubsContext().getString(metaDataId));
             } else if (new_notif_mp3.exists()) {
                 IOUtils.bufferedCopy(new_notif_mp3, new File(IOUtils.SYSTEM_THEME_NOTIFICATION_PATH + File.separator + "notification.mp3"));
-                int metaDataId = getSubsContext().getResources().getIdentifier("content_resolver_notification_metadata", "string", SUBSTRATUM_PACKAGE);
-                SoundUtils.setAudible(this, new File("/data/system/theme/audio/notifications/notification.mp3"),
-                        new File(notifCache.getAbsolutePath(), "notification.mp3"),
-                        RingtoneManager.TYPE_NOTIFICATION,
-                        getSubsContext().getString(metaDataId));
-            } else {
-                SoundUtils.setDefaultAudible(this, RingtoneManager.TYPE_NOTIFICATION);
-            }
-            File notifs = new File(IOUtils.SYSTEM_THEME_NOTIFICATION_PATH);
-            if (notifs.exists() && notifs.isDirectory()) {
-                for (File f : notifs.listFiles()) {
-                    FileUtils.setPermissions(f,
-                            FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IRWXO, -1, -1);
-                }
             }
         }
         File ringtoneCache = new File(getCacheDir(), "/SoundsCache/ringtones/");
@@ -514,29 +509,13 @@
             File new_ring_ogg = new File(getCacheDir(), "/SoundsCache/ringtones/ringtone.ogg");
             if (new_ring_ogg.exists()) {
                 IOUtils.bufferedCopy(new_ring_ogg, new File(IOUtils.SYSTEM_THEME_RINGTONE_PATH + File.separator + "ringtone.ogg"));
-                int metaDataId = getSubsContext().getResources().getIdentifier("content_resolver_ringtone_metadata", "string", SUBSTRATUM_PACKAGE);
-                SoundUtils.setAudible(this, new File("/data/system/theme/audio/ringtones/ringtone.ogg"),
-                        new File(notifCache.getAbsolutePath(), "ringtone.ogg"),
-                        RingtoneManager.TYPE_RINGTONE,
-                        getSubsContext().getString(metaDataId));
             } else if (new_ring_mp3.exists()) {
                 IOUtils.bufferedCopy(new_ring_mp3, new File(IOUtils.SYSTEM_THEME_RINGTONE_PATH + File.separator + "ringtone.mp3"));
-                int metaDataId = getSubsContext().getResources().getIdentifier("content_resolver_ringtone_metadata", "string", SUBSTRATUM_PACKAGE);
-                SoundUtils.setAudible(this, new File("/data/system/theme/audio/ringtones/ringtone.mp3"),
-                        new File(notifCache.getAbsolutePath(), "ringtone.mp3"),
-                        RingtoneManager.TYPE_RINGTONE,
-                        getSubsContext().getString(metaDataId));
-            } else {
-                SoundUtils.setDefaultAudible(this, RingtoneManager.TYPE_RINGTONE);
-            }
-            File rings = new File(IOUtils.SYSTEM_THEME_RINGTONE_PATH);
-            if (rings.exists() && rings.isDirectory()) {
-                for (File f : rings.listFiles()) {
-                    FileUtils.setPermissions(f,
-                            FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IRWXO, -1, -1);
-                }
             }
         }
+
+        // let system know it's time for a sound change
+        refreshSounds();
     }
 
     private void clearSounds(Context ctx) {
@@ -550,6 +529,121 @@
                 "LowBattery.ogg");
     }
 
+    private void refreshSounds () {
+        File soundsDir = new File(IOUtils.SYSTEM_THEME_AUDIO_PATH);
+        if (soundsDir.exists()) {
+            // Set permissions
+            IOUtils.setPermissionsRecursive(soundsDir,
+                    FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IRWXO,
+                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH);
+
+            File uiDir = new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH);
+            if (uiDir.exists()) {
+                File effect_tick_mp3 = new File(uiDir, "Effect_Tick.mp3");
+                File effect_tick_ogg = new File(uiDir, "Effect_Tick.ogg");
+                if (effect_tick_mp3.exists()) {
+                    SoundUtils.setUIAudible(this, effect_tick_mp3,
+                            effect_tick_mp3, RingtoneManager.TYPE_RINGTONE,
+                            "Effect_Tick");
+                } else if (effect_tick_ogg.exists()) {
+                    SoundUtils.setUIAudible(this, effect_tick_ogg,
+                            effect_tick_ogg, RingtoneManager.TYPE_RINGTONE,
+                            "Effect_Tick");
+                } else {
+                    SoundUtils.setDefaultUISounds(getContentResolver(),
+                            "Effect_Tick",
+                            "Effect_Tick.ogg");
+                }
+
+                File lock_mp3 = new File(uiDir, "Lock.mp3");
+                File lock_ogg = new File(uiDir, "Lock.ogg");
+                if (lock_mp3.exists()) {
+                    SoundUtils.setUISounds(getContentResolver(), "lock_sound",
+                            lock_mp3.getAbsolutePath());
+                } else if (lock_ogg.exists()) {
+                    SoundUtils.setUISounds(getContentResolver(), "lock_sound",
+                            lock_ogg.getAbsolutePath());
+                } else {
+                    SoundUtils.setDefaultUISounds(getContentResolver(),
+                            "lock_sound", "Lock.ogg");
+                }
+
+                File unlock_mp3 = new File(uiDir, "Unlock.mp3");
+                File unlock_ogg = new File(uiDir, "Unlock.ogg");
+                if (unlock_mp3.exists()) {
+                    SoundUtils.setUISounds(getContentResolver(), "unlock_sound",
+                            unlock_mp3.getAbsolutePath());
+                } else if (unlock_ogg.exists()) {
+                    SoundUtils.setUISounds(getContentResolver(), "unlock_sound",
+                            unlock_ogg.getAbsolutePath());
+                } else {
+                    SoundUtils.setDefaultUISounds(getContentResolver(),
+                            "unlock_sound", "Unlock.ogg");
+                }
+
+                File lowbattery_mp3 = new File(uiDir, "LowBattery.mp3");
+                File lowbattery_ogg = new File(uiDir, "LowBattery.ogg");
+                if (lowbattery_mp3.exists()) {
+                    SoundUtils.setUISounds(getContentResolver(), "low_battery_sound",
+                            lowbattery_mp3.getAbsolutePath());
+                } else if (lowbattery_ogg.exists()) {
+                    SoundUtils.setUISounds(getContentResolver(), "low_battery_sound",
+                            lowbattery_ogg.getAbsolutePath());
+                } else {
+                    SoundUtils.setDefaultUISounds(getContentResolver(),
+                            "low_battery_sound", "LowBattery.ogg");
+                }
+            }
+
+            File notifDir = new File(IOUtils.SYSTEM_THEME_NOTIFICATION_PATH);
+            if (notifDir.exists()) {
+                int metaDataId = getSubsContext().getResources().getIdentifier(
+                        "content_resolver_notification_metadata",
+                        "string", SUBSTRATUM_PACKAGE);
+
+                File notification_mp3 = new File(notifDir, "notification.mp3");
+                File notification_ogg = new File(notifDir, "notification.ogg");
+                if (notification_mp3.exists()) {
+                    SoundUtils.setAudible(this, notification_mp3,
+                            notification_mp3,
+                            RingtoneManager.TYPE_NOTIFICATION,
+                            getSubsContext().getString(metaDataId));
+                } else if (notification_ogg.exists()) {
+                    SoundUtils.setAudible(this, notification_ogg,
+                            notification_ogg,
+                            RingtoneManager.TYPE_NOTIFICATION,
+                            getSubsContext().getString(metaDataId));
+                } else {
+                    SoundUtils.setDefaultAudible(this,
+                            RingtoneManager.TYPE_NOTIFICATION);
+                }
+            }
+
+            File ringtoneDir = new File(IOUtils.SYSTEM_THEME_RINGTONE_PATH);
+            if (ringtoneDir.exists()) {
+                int metaDataId = getSubsContext().getResources().getIdentifier(
+                        "content_resolver_notification_metadata",
+                        "string", SUBSTRATUM_PACKAGE);
+
+                File ringtone_mp3 = new File(ringtoneDir, "ringtone.mp3");
+                File ringtone_ogg = new File(ringtoneDir, "ringtone.ogg");
+                if (ringtone_mp3.exists()) {
+                    SoundUtils.setAudible(this, ringtone_mp3,
+                            ringtone_mp3,
+                            RingtoneManager.TYPE_RINGTONE,
+                            getSubsContext().getString(metaDataId));
+                } else if (ringtone_ogg.exists()) {
+                    SoundUtils.setAudible(this, ringtone_ogg,
+                            ringtone_ogg,
+                            RingtoneManager.TYPE_RINGTONE,
+                            getSubsContext().getString(metaDataId));
+                } else {
+                    SoundUtils.setDefaultAudible(this,
+                            RingtoneManager.TYPE_RINGTONE);
+                }
+            }
+        }
+    }
     private void copyBootAnimation(String fileName) {
         try {
             clearBootAnimation();
@@ -557,8 +651,8 @@
             File dest = new File(IOUtils.SYSTEM_THEME_BOOTANIMATION_PATH);
             IOUtils.bufferedCopy(source, dest);
             source.delete();
-            FileUtils.setPermissions(IOUtils.SYSTEM_THEME_BOOTANIMATION_PATH,
-                    FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
+            IOUtils.setPermissions(dest,
+                    FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH);
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -689,7 +783,7 @@
                     synchronized (mJobQueue) {
                         job = mJobQueue.get(0);
                     }
-                    if (job != null) {
+                    if (job != null && !mIsRunning) {
                         job.run();
                     }
                     break;
@@ -698,8 +792,10 @@
                     synchronized (mJobQueue) {
                         mJobQueue.remove(toRemove);
                         if (mJobQueue.size() > 0) {
+                            mIsRunning = false;
                             this.sendEmptyMessage(MESSAGE_CHECK_QUEUE);
                         } else {
+                            mIsRunning = false;
                             log("Job queue empty! All done");
                             mMainHandler.sendEmptyMessage(MainHandler.MSG_JOB_QUEUE_EMPTY);
                         }
@@ -835,7 +931,7 @@
         }
     }
 
-    private class Installer implements Runnable, IPackageInstallObserver2 {
+    private class Installer implements Runnable {
         String mPath;
 
         public Installer(String path) {
@@ -843,27 +939,28 @@
         }
 
         @Override
-        public IBinder asBinder() {
-            return null;
-        }
-
-        @Override
-        public void onUserActionRequired(Intent intent) throws RemoteException {
-            log("Installer - user action required callback with " + mPath);
-        }
-
-        @Override
-        public void onPackageInstalled(String basePackageName, int returnCode, String msg,
-                Bundle extras) throws RemoteException {
-            log("Installer - successfully installed " + basePackageName + " from " + mPath);
-            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE, Installer.this);
-            mJobHandler.sendMessage(message);
-        }
-
-        @Override
         public void run() {
             log("Installer - installing " + mPath);
-            install(mPath, this);
+            PackageInstallObserver observer = new PackageInstallObserver(Installer.this);
+            install(mPath, observer);
+        }
+    }
+
+    private class PackageInstallObserver extends IPackageInstallObserver2.Stub {
+        Object mObject;
+
+        public PackageInstallObserver(Object _object) {
+            mObject = _object;
+        }
+
+        public void onUserActionRequired(Intent intent) throws RemoteException {
+            log("Installer - user action required callback");
+        }
+
+        public void onPackageInstalled(String packageName, int returnCode, String msg, Bundle extras) {
+            log("Installer - successfully installed " + packageName);
+            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE, mObject);
+            mJobHandler.sendMessage(message);
         }
     }
 
@@ -876,12 +973,242 @@
 
         @Override
         public void run() {
+
+            // TODO: Fix isOverlayEnabled function, for now it's causing NPE
             if (isOverlayEnabled(mPackage)) {
                 log("Remover - disabling overlay for " + mPackage);
-                disableOverlay(mPackage);
+                switchOverlay(mPackage, false);
             }
+
             log("Remover - uninstalling " + mPackage);
-            uninstall(mPackage);
+            PackageDeleteObserver observer = new PackageDeleteObserver(Remover.this);
+            uninstall(mPackage, observer);
+        }
+    }
+
+    private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
+        Object mObject;
+
+        public PackageDeleteObserver(Object _object) {
+            mObject = _object;
+        }
+
+        public void packageDeleted(String packageName, int returnCode) {
+            log("Remover - successfully removed " + packageName);
+            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE, mObject);
+            mJobHandler.sendMessage(message);
+        }
+    }
+
+    private class Enabler implements Runnable {
+        String mPackage;
+
+        public Enabler(String _package) {
+            mPackage = _package;
+        }
+
+        @Override
+        public void run() {
+            log("Enabler - enabling overlay for " + mPackage);
+            switchOverlay(mPackage, true);
+            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                    Enabler.this);
+            mJobHandler.sendMessage(message);
+        }
+    }
+
+    private class Disabler implements Runnable {
+        String mPackage;
+
+        public Disabler(String _package) {
+            mPackage = _package;
+        }
+
+        @Override
+        public void run() {
+            log("Disabler - disabling overlay for " + mPackage);
+            switchOverlay(mPackage, false);
+            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                    Disabler.this);
+            mJobHandler.sendMessage(message);
+        }
+    }
+
+    private class PriorityJob implements Runnable {
+        List<String> mPackages;
+
+        public PriorityJob(List<String> _packages) {
+            mPackages = _packages;
+        }
+
+        @Override
+        public void run() {
+            log("PriorityJob - processing priority changes");
+            try {
+                int size = mPackages.size();
+                for (int i = 0; i < size-1; i++) {
+                    String parentName = mPackages.get(i);
+                    String packageName = mPackages.get(i+1);
+                    getOMS().setPriority(packageName, parentName, UserHandle.USER_SYSTEM);
+                }
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                    PriorityJob.this);
+            mJobHandler.sendMessage(message);
+        }
+    }
+
+    private class CopyJob implements Runnable {
+        String mSource;
+        String mDestination;
+
+        public CopyJob(String _source, String _destination) {
+            mSource = _source;
+            mDestination = _destination;
+        }
+
+        @Override
+        public void run() {
+            log("CopyJob - copying " + mSource + " to " + mDestination);
+            File sourceFile = new File(mSource);
+            if (sourceFile.exists()) {
+                if (sourceFile.isFile()) {
+                    IOUtils.bufferedCopy(mSource, mDestination);
+                } else {
+                    IOUtils.copyFolder(mSource, mDestination);
+                }
+            } else {
+                log("CopyJob - " + mSource + " is not exist! aborting...");
+            }
+            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                    CopyJob.this);
+            mJobHandler.sendMessage(message);
+        }
+    }
+
+    private class MoveJob implements Runnable {
+        String mSource;
+        String mDestination;
+
+        public MoveJob(String _source, String _destination) {
+            mSource = _source;
+            mDestination = _destination;
+        }
+
+        @Override
+        public void run() {
+            log("MoveJob - moving " + mSource + " to " + mDestination);
+            File sourceFile = new File(mSource);
+            if (sourceFile.exists()) {
+                if (sourceFile.isFile()) {
+                    IOUtils.bufferedCopy(mSource, mDestination);
+                } else {
+                    IOUtils.copyFolder(mSource, mDestination);
+                }
+                IOUtils.deleteRecursive(sourceFile);
+            } else {
+                log("MoveJob - " + mSource + " is not exist! aborting...");
+            }
+            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                    MoveJob.this);
+            mJobHandler.sendMessage(message);
+        }
+    }
+
+    private class DeleteJob implements Runnable {
+        String mFileOrDirectory;
+
+        public DeleteJob(String _directory) {
+            mFileOrDirectory = _directory;
+        }
+
+        @Override
+        public void run() {
+            log("DeleteJob - deleting " + mFileOrDirectory);
+            File file = new File(mFileOrDirectory);
+            if (file.exists()) {
+                IOUtils.deleteRecursive(file);
+            } else {
+                log("DeleteJob - " + mFileOrDirectory + " is already deleted!");
+            }
+            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                    DeleteJob.this);
+            mJobHandler.sendMessage(message);
+        }
+    }
+
+    private class ProfileJob implements Runnable {
+        String mProfileName;
+        List<String> mToBeDisabled;
+        List<String> mToBeEnabled;
+
+        public ProfileJob(String _name, List<String> _toBeDisabled, List<String> _toBeEnabled) {
+            mProfileName = _name;
+            mToBeDisabled = _toBeDisabled;
+            mToBeEnabled = _toBeEnabled;
+        }
+
+        @Override
+        public void run() {
+            boolean restartUi = false;
+            log("Applying profile...");
+
+            // Need to restart SystemUI?
+            restartUi = shouldRestartUi(mToBeEnabled) || shouldRestartUi(mToBeDisabled);
+
+            // Clear system theme folder content
+            File themeDir = new File(IOUtils.SYSTEM_THEME_PATH);
+            for (File f : themeDir.listFiles()) {
+                IOUtils.deleteRecursive(f);
+            }
+
+            // Process theme folder
+            File profileDir = new File(Environment.getExternalStorageDirectory()
+                    .getAbsolutePath() + "/substratum/profiles/" +
+                    mProfileName + "/theme");
+
+            if (profileDir.exists()) {
+                File profileFonts = new File(profileDir, "fonts");
+                if (profileFonts.exists()) {
+                    IOUtils.copyFolder(profileFonts, new File(IOUtils.SYSTEM_THEME_FONT_PATH));
+                    refreshFonts();
+                    restartUi = true;
+                } else {
+                    clearFonts();
+                }
+
+                File profileSounds = new File(profileDir, "audio");
+                if (profileSounds.exists()) {
+                    IOUtils.copyFolder(profileSounds, new File(IOUtils.SYSTEM_THEME_AUDIO_PATH));
+                    refreshSounds();
+                    restartUi = true;
+                } else {
+                    clearSounds(JobService.this);
+                }
+            }
+
+            // Disable all overlays installed
+            for (String overlay : mToBeDisabled) {
+                switchOverlay(overlay, false);
+            }
+
+            // Enable provided overlays
+            for (String overlay : mToBeEnabled) {
+                switchOverlay(overlay, true);
+            }
+
+            // Restart SystemUI when needed
+            if (restartUi) {
+                synchronized(mJobQueue) {
+                    mJobQueue.add(new UiResetJob());
+                }
+            }
+
+            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                    ProfileJob.this);
+            mJobHandler.sendMessage(message);
         }
     }
 
diff --git a/app/src/main/java/masquerade/substratum/utils/IOUtils.java b/app/src/main/java/masquerade/substratum/utils/IOUtils.java
index 092c58c..06b5260 100644
--- a/app/src/main/java/masquerade/substratum/utils/IOUtils.java
+++ b/app/src/main/java/masquerade/substratum/utils/IOUtils.java
@@ -40,8 +40,8 @@
         if (!dirExists(dirPath)) {
             File dir = new File(dirPath);
             if (dir.mkdir()) {
-                FileUtils.setPermissions(dir, FileUtils.S_IRWXU |
-                        FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1);
+                setPermissions(dir, FileUtils.S_IRWXU | FileUtils.S_IRWXG |
+                        FileUtils.S_IROTH | FileUtils.S_IXOTH);
             }
         }
     }
@@ -77,7 +77,7 @@
             e.printStackTrace();
         }
     }
-    
+
     public static void deleteThemedAudio() {
         try {
             deleteRecursive(new File(SYSTEM_THEME_UI_SOUNDS_PATH));
@@ -89,20 +89,28 @@
         }
     }
 
-    public static void copyFolder(String source, String dest) {
-        File dir = new File(source);
-        File[] files = dir.listFiles();
+    public static void copyFolder(File source, File dest) {
+        if (!dest.exists()) dest.mkdirs();
+        File[] files = source.listFiles();
         for (File file : files) {
             try {
-                String sourceFile = dir + File.separator + file.getName();
-                String destinationFile = dest + File.separator + file.getName();
-                bufferedCopy(new File(sourceFile), new File(destinationFile));
+                File newFile = new File(dest.getAbsolutePath() + File.separator +
+                        file.getName());
+                if (file.isFile()) {
+                    bufferedCopy(file, newFile);
+                } else {
+                    copyFolder(file, newFile);
+                }
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }
 
+    public static void copyFolder(String source, String dest) {
+        copyFolder(new File(source), new File(dest));
+    }
+
     public static void unzip(String source, String destination) {
         try (ZipInputStream inputStream = new ZipInputStream(
                 new BufferedInputStream(new FileInputStream(source)))) {
@@ -167,4 +175,23 @@
         fileOrDirectory.delete();
     }
 
+    public static void setPermissions(File path, int permissions) {
+        FileUtils.setPermissions(path, permissions, -1, -1);
+    }
+
+    public static void setPermissionsRecursive(File dir, int file, int folder) {
+        if (dir.isDirectory()) {
+            for (File child : dir.listFiles()) {
+                if (child.isDirectory()) {
+                    setPermissionsRecursive(child, file, folder);
+                    setPermissions(child, folder);
+                } else {
+                    setPermissions(child, file);
+                }
+            }
+            setPermissions(dir, folder);
+        } else {
+            setPermissions(dir, file);
+        }
+    }
 }
diff --git a/app/src/main/java/masquerade/substratum/utils/SoundUtils.java b/app/src/main/java/masquerade/substratum/utils/SoundUtils.java
index 72c6052..dda38eb 100644
--- a/app/src/main/java/masquerade/substratum/utils/SoundUtils.java
+++ b/app/src/main/java/masquerade/substratum/utils/SoundUtils.java
@@ -27,7 +27,7 @@
     private static final String MEDIA_CONTENT_URI = "content://media/internal/audio/media";
 
     public static void updateGlobalSettings(ContentResolver resolver, String uri, String val) {
-        Settings.Global.putStringForUser(resolver, uri, val, UserHandle.USER_ALL);
+        Settings.Global.putStringForUser(resolver, uri, val, UserHandle.USER_SYSTEM);
     }
 
     public static boolean setUISounds(ContentResolver resolver, String sound_name, String location) {