Update for topic:rootless
authorLuK1337 <priv.luk@gmail.com>
Thu, 16 Feb 2017 16:08:53 +0000 (17:08 +0100)
committerLuK1337 <priv.luk@gmail.com>
Thu, 16 Feb 2017 16:08:53 +0000 (17:08 +0100)
49 files changed:
patch.sh
patches/frameworks/base/0001-OMS7-N-Support-tagging-resources-as-OK-to-overlay-1-.patch
patches/frameworks/base/0002-OMS7-N-Introduce-the-OverlayManagerService-2-11.patch
patches/frameworks/base/0003-OMS7-N-Integrate-OverlayManagerService-into-framewor.patch
patches/frameworks/base/0004-OMS7-N-Set-EXTRA_REPLACING-correctly-in-ACTION_PACKA.patch
patches/frameworks/base/0005-OMS7-N-idmap-suppress-print-for-padded-resources-5-1.patch
patches/frameworks/base/0006-OMS7-N-Fix-memory-leak-during-idmap-creation-6-11.patch
patches/frameworks/base/0007-OMS7-N-installd-add-command-rmidmap-7-11.patch
patches/frameworks/base/0008-OMS7-N-Disable-Zygote-preloaded-drawables-8-11.patch
patches/frameworks/base/0009-OMS7-N-Persistence-on-boot-through-OverlayManagerSer.patch
patches/frameworks/base/0010-OMS7-N-Do-not-enforce-code-policy-limiting-overlay-i.patch
patches/frameworks/base/0011-OMS7-N-Implement-multi-target-enable-disable-and-dis.patch
patches/frameworks/base/0012-Themes-Expose-resolver-hardcoded-colors.patch
patches/frameworks/base/0013-Themes-Allow-Immersive-cling-colors-to-be-fully-them.patch
patches/frameworks/base/0014-Themes-Allow-Permission-Icons-to-be-fully-themed.patch
patches/frameworks/base/0015-Themes-Allow-Navbar-ripple-color-to-be-themed.patch
patches/frameworks/base/0016-SystemUI-Expose-QS-edit-item-decoration-background-c.patch
patches/frameworks/base/0017-Allow-custom-alpha-for-notification-shade-bg-color.patch
patches/frameworks/base/0018-Themes-Expose-various-QuickSettings-text-colors.patch
patches/frameworks/base/0019-Notifications-Expose-a-bool-to-disable-dynamic-color.patch
patches/frameworks/base/0020-Notification-dynamic-colors-bool-compatible-with-OMS.patch
patches/frameworks/base/0021-Allow-prevention-of-doze-notification-color-inversio.patch
patches/frameworks/base/0022-OMS7-compatible-Ambient-notification-inversion.patch
patches/frameworks/base/0023-SystemUI-Use-own-drawables-for-QS-expand-icon.patch
patches/frameworks/base/0024-N-Extras-Add-dynamic-theme-BootAnimation-support.patch
patches/frameworks/base/0025-N-Extras-Add-dynamic-theme-fonts-support.patch
patches/frameworks/base/0026-N-Extras-AudioService-Allow-system-effect-sounds-to-.patch
patches/frameworks/base/0027-OMS7-N-ApplicationsState-add-filter-for-Substratum-o.patch
patches/frameworks/base/0028-OMS7-N-ApplicationsState-add-filter-for-Substratum-i.patch
patches/frameworks/base/0029-Themes-Expose-QS-battery.patch
patches/frameworks/base/0030-OMS-Introduce-MODIFY_OVERLAYS-permission-for-user-ap.patch
patches/frameworks/base/0031-SystemUI-Expose-switch-bar-title.patch
patches/frameworks/base/0032-Themes-Expose-manifest-styles-for-themes.patch
patches/frameworks/base/0033-OMS-StrictMode-and-files-under-data-system-theme.patch
patches/frameworks/base/0034-doze-allow-grayscale-even-if-invert-boolean-is-false.patch
patches/frameworks/base/0035-Expose-external-qs-tile-tint-color.patch
patches/frameworks/base/0036-graphics-ADB-N-icon-compatible-with-OMS7.patch
patches/frameworks/base/0037-Set-external-QS-tiles-tint-mode-to-SRC_ATOP.patch
patches/frameworks/base/0038-Themes-Expose-Keyguard-affordance-circle-background.patch
patches/frameworks/base/0039-Add-a-protected-broadcast-for-Masquerade-events.patch [new file with mode: 0644]
patches/packages/apps/masquerade/0001-Rewrite-Masquerade-for-UID-system-ops-1-3.patch [new file with mode: 0644]
patches/packages/apps/masquerade/0002-Finalize-masquerade-rootless-functionality-with-Subs.patch [new file with mode: 0644]
patches/packages/apps/masquerade/0003-Release-23-Introduce-the-rootless-solution-for-Masqu.patch [new file with mode: 0644]
patches/system/core/0001-Create-theme-extras-directory.patch [new file with mode: 0644]
patches/system/sepolicy/0001-OMS-N-Add-service-overlay-to-service_contexts.patch
patches/system/sepolicy/0002-Introduce-sepolicy-exceptions-for-theme-assets.patch [new file with mode: 0644]
patches/system/sepolicy/0003-sepolicy-fix-themed-boot-animation.patch [new file with mode: 0644]
patches/system/sepolicy/0004-sepolicy-fix-themed-sounds.patch [new file with mode: 0644]
patches/vendor/cm/0001-Remove-custom-CMTE-rules.patch [new file with mode: 0644]

index 66fa8d4686301b684bc5080d75ebdb239bb217f0..97522063aad5a607d00649f6a67a62a2be1907ef 100755 (executable)
--- a/patch.sh
+++ b/patch.sh
@@ -5,10 +5,13 @@ REPOSITORIES=(
     'packages/apps/Settings'
     'packages/apps/ExactCalculator'
     'packages/apps/PhoneCommon'
+    'packages/apps/masquerade'
     'build'
+    'system/core'
     'system/sepolicy'
     'frameworks/native'
     'frameworks/base'
+    'vendor/cm'
 )
 
 for repository in "${REPOSITORIES[@]}"; do
index 49866ce88372fa1aa386231674f6ccab460244b7..51d4813182e194deca25efca56dac62be6e16385 100644 (file)
@@ -1,7 +1,7 @@
-From b43513fd7b0692aa757e1e6b1d299ffe1875dcdd Mon Sep 17 00:00:00 2001
+From 8dc3129826885706e51627f70113794e3f040ed0 Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= <marten.kongstad@sonymobile.com>
 Date: Tue, 15 Dec 2015 16:08:31 +0100
-Subject: [PATCH 01/38] OMS7-N: Support tagging resources as OK to overlay
+Subject: [PATCH 01/39] OMS7-N: Support tagging resources as OK to overlay
  [1/11]
 
 This will allow applications to have a resource xml defining what
index 8a1e4da1218d99af724806b6f3f497311df2890c..4331788b79cf7f3df9d43d30807dd5a6d06dd2e9 100644 (file)
@@ -1,7 +1,7 @@
-From 37fcdce6e621c21cdf1010ca600b7722d5a1bc5a Mon Sep 17 00:00:00 2001
+From c8f9bc6207c9db6805446f41661fdd9345a6d924 Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= <marten.kongstad@sonymobile.com>
 Date: Tue, 15 Dec 2015 16:40:23 +0100
-Subject: [PATCH 02/38] OMS7-N: Introduce the OverlayManagerService [2/11]
+Subject: [PATCH 02/39] OMS7-N: Introduce the OverlayManagerService [2/11]
 
 Add a new system service to manage Runtime Resource Overlays. This will
 offload the PackageManagerService and allow administration of overlay
index 6d4bb110b5db658babfdd40b61fce647c03a9ca1..cf77499cb4d711ee71f5e90c9ee7282aaf0284fc 100644 (file)
@@ -1,7 +1,7 @@
-From ca6a779643bb2b80e14318c8866f6f4970f70a0a Mon Sep 17 00:00:00 2001
+From 5d7e6ea290e2c8c080212193511dfe4989020332 Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= <marten.kongstad@sonymobile.com>
 Date: Thu, 2 Jun 2016 09:35:31 +0200
-Subject: [PATCH 03/38] OMS7-N: Integrate OverlayManagerService into framework
+Subject: [PATCH 03/39] OMS7-N: Integrate OverlayManagerService into framework
  [3/11]
 
 Hand over ownership of overlays to OverlayManagerService.
index 9b3130ec7ef5c1f0ab8f8fcc137206f17911afde..37f23d1b47cfced582320e640298ed31b1184468 100644 (file)
@@ -1,7 +1,7 @@
-From a1d27bf6ba22b9189350bf26df2918ff2ac551c8 Mon Sep 17 00:00:00 2001
+From 982489be050f0b26b0871c1545a608cedbd64497 Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= <marten.kongstad@sonymobile.com>
 Date: Mon, 25 Apr 2016 16:29:22 +0200
-Subject: [PATCH 04/38] OMS7-N: Set EXTRA_REPLACING correctly in
+Subject: [PATCH 04/39] OMS7-N: Set EXTRA_REPLACING correctly in
  ACTION_PACKAGE_ADDED [4/11]
 
 When broadcasting ACTION_PACKAGE_ADDED the recipients of the Intent are
index 096abf53edcc89ba61346225bd277f2013ec3327..f1396a4469155067b50ec8ccb7290fe8950d08d2 100644 (file)
@@ -1,7 +1,7 @@
-From d2d59caef4a066b8149b09d0bd4e25ac4e01732e Mon Sep 17 00:00:00 2001
+From f384d2dd985179f638384b8ffad71f791ebff489 Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= <marten.kongstad@sonymobile.com>
 Date: Mon, 29 Feb 2016 14:12:35 +0100
-Subject: [PATCH 05/38] OMS7-N: idmap: suppress print for padded resources
+Subject: [PATCH 05/39] OMS7-N: idmap: suppress print for padded resources
  [5/11]
 
 Change-Id: I565ccf515068b96927e4317cc9c06543415bb324
index 36585561912a758e5b4311de178d0992d0dd2213..4dd77bd1347c310988b54e1a21cd53f01095615d 100644 (file)
@@ -1,7 +1,7 @@
-From a25413bc2f0537766385bbfbfa2ddad6c10b7e3d Mon Sep 17 00:00:00 2001
+From a511e7456e007b4b2783f78e7f3bdcdfc5686157 Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= <marten.kongstad@sonymobile.com>
 Date: Thu, 2 Jun 2016 09:34:36 +0200
-Subject: [PATCH 06/38] OMS7-N: Fix memory leak during idmap creation [6/11]
+Subject: [PATCH 06/39] OMS7-N: Fix memory leak during idmap creation [6/11]
 
 Plug a memory leak in AssetManager::createIdmap.
 
index bf3eb289a81dbe5f85f2cb5aa12150cfe6459031..28dcfb47845015c13fd79713f5a5760f5747e26e 100644 (file)
@@ -1,7 +1,7 @@
-From e8847e7d2ddacae26e0a571b2c2a2a106a9b4f1f Mon Sep 17 00:00:00 2001
+From 0bf0a38a8adb2c37eb48bfc052d3307cd765943d Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= <marten.kongstad@sonymobile.com>
 Date: Thu, 2 Jun 2016 09:35:09 +0200
-Subject: [PATCH 07/38] OMS7-N: installd: add command 'rmidmap' [7/11]
+Subject: [PATCH 07/39] OMS7-N: installd: add command 'rmidmap' [7/11]
 
 Add an installd command to remove an idmap file. This is the inverse of
 the 'idmap' command and is intended for clean-up once an idmap file is
index 8444337f9b59a6e3a388a762a47855f11e5d5a0a..98fe8cf058fb61e6813d551f33d08ff6be5c6cb0 100644 (file)
@@ -1,7 +1,7 @@
-From 961527ef5940fdf23c7c4311981c835b5fd0f699 Mon Sep 17 00:00:00 2001
+From 1efd19a1d85b18a7d3a64d42fd7318bc3c611e1f Mon Sep 17 00:00:00 2001
 From: Josh Guilfoyle <Josh.Guilfoyle@T-Mobile.com>
 Date: Wed, 26 Jan 2011 23:28:43 -0800
-Subject: [PATCH 08/38] OMS7-N: Disable Zygote preloaded drawables [8/11]
+Subject: [PATCH 08/39] OMS7-N: Disable Zygote preloaded drawables [8/11]
 
 With a theme applied, most of these preloaded drawables go unused.  Any
 assets the theme has redirected will need to be loaded with each app
index 807cd02776fe0510a98451936bdc65c18f687fd0..5b86b9123562ce0620452bcfd012834c897355a2 100644 (file)
@@ -1,7 +1,7 @@
-From 881efe212818d0cd62fb90f41f15bd368c7cc58d Mon Sep 17 00:00:00 2001
+From 9c44db7a6126968c051a1fcfeeef7d3604b7b29c Mon Sep 17 00:00:00 2001
 From: Nicholas Chum <nicholaschum@gmail.com>
 Date: Sun, 19 Jun 2016 10:37:13 -0400
-Subject: [PATCH 09/38] OMS7-N: Persistence on boot through
+Subject: [PATCH 09/39] OMS7-N: Persistence on boot through
  OverlayManagerServiceImpl [9/11]
 
 Overlays should not be enforced by the traditional OverlayManagerService
index eb9d9d7c54933db75b40db2a7559e76b56e23e30..432bb959cc58cfe845ec4261449099ddb842d88b 100644 (file)
@@ -1,7 +1,7 @@
-From accdb618c530796534fc4b84e69fc81ea6d98f07 Mon Sep 17 00:00:00 2001
+From 80ac3c5bd7cda0c8621aec4864a6152b0687b35c Mon Sep 17 00:00:00 2001
 From: Nicholas Chum <nicholaschum@gmail.com>
 Date: Thu, 27 Oct 2016 07:08:00 +0200
-Subject: [PATCH 10/38] OMS7-N: Do not enforce code policy limiting overlay
+Subject: [PATCH 10/39] OMS7-N: Do not enforce code policy limiting overlay
  installation [10/11]
 
 Change-Id: Iea317f3771f25dbfcbf4938e88cace12fd97d7eb
index 317b504b4043ef3e5488f9444a76813bffa231b1..e5cbd7f16ce00a62f9d83f5803439e2cf9a31a07 100644 (file)
@@ -1,7 +1,7 @@
-From d3a24aa7becbcc6ce61d0e13a07b87525d0769fd Mon Sep 17 00:00:00 2001
+From 222657694ae34a145284e35dda583805d3851ae2 Mon Sep 17 00:00:00 2001
 From: Jacob McSwain <jacob.a.mcswain@gmail.com>
 Date: Sun, 26 Jun 2016 15:21:52 -0500
-Subject: [PATCH 11/38] OMS7-N: Implement multi-target enable/disable and
+Subject: [PATCH 11/39] OMS7-N: Implement multi-target enable/disable and
  disable-all [11/11]
 
 Just use the enable option like normal, but you can add more arguments
index b832530c5ca7bc22d5a2debfce57b6a234de709f..9364ff1f9d93974393cea0aebbf510d84864b3b4 100644 (file)
@@ -1,7 +1,7 @@
-From e1469be527249a4015953afaef58ea65b96ab673 Mon Sep 17 00:00:00 2001
+From 30faae7536ebb3d2879161ed8d0adde3bbaf1ebb Mon Sep 17 00:00:00 2001
 From: Dave Kover <dkover@cyngn.com>
 Date: Fri, 9 Dec 2016 10:47:17 -0700
-Subject: [PATCH 12/38] Themes: Expose resolver hardcoded colors
+Subject: [PATCH 12/39] Themes: Expose resolver hardcoded colors
 
 commit dbbd5e70cc65002df41561474b03362022dd6716
 Author: Dave Kover <dkover@cyngn.com>
index b7660db212d0999f14ccc262a51c20c37400e5bc..26a065625466f99ef851bf221848ac46aa72b0e9 100644 (file)
@@ -1,7 +1,7 @@
-From 3871ec03b79a47e40ed9405edbdf502500603dff Mon Sep 17 00:00:00 2001
+From 2626e4fd9102ccdb4b1abf2242dc98d67f9c5382 Mon Sep 17 00:00:00 2001
 From: Nicholas Chum <nicholaschum@gmail.com>
 Date: Tue, 17 Nov 2015 18:57:11 -0500
-Subject: [PATCH 13/38] Themes: Allow Immersive cling colors to be fully themed
+Subject: [PATCH 13/39] Themes: Allow Immersive cling colors to be fully themed
 
 This allows the immersive mode help tooltip to be themed completely by
 removing hardcoded framework calls. Let the themer decide what they want
index 7ef7eaf6bf594d1956e381ed5569e5195924e585..b9851535242a2799b2bc9a93a0cbae595e83f499 100644 (file)
@@ -1,7 +1,7 @@
-From 2d1f47489f89e1d16ba15f126cabf79b592e64e8 Mon Sep 17 00:00:00 2001
+From 7e132632f4317e887c9a4f0a7176d691a10d7e6a Mon Sep 17 00:00:00 2001
 From: Nicholas Chum <nicholaschum@gmail.com>
 Date: Mon, 23 Nov 2015 23:49:15 -0500
-Subject: [PATCH 14/38] Themes: Allow Permission Icons to be fully themed
+Subject: [PATCH 14/39] Themes: Allow Permission Icons to be fully themed
 
 This removes the forced @android:color/black tint on the permission
 icons during app sideload through PackageInstaller.
index 9945da4137ba0bd36d64e9dd4dd8abf1f91255e8..a1c485970583d206fc61a4854f5f5d2c666afce1 100644 (file)
@@ -1,7 +1,7 @@
-From 877df2f3564c0511e5a8ba7b9b2c91c89d2a6e01 Mon Sep 17 00:00:00 2001
+From 115c01b1fb6ca51b5c7599618f0097c0f4b5853e Mon Sep 17 00:00:00 2001
 From: Dave Kover <dkover@cyngn.com>
 Date: Thu, 14 Apr 2016 10:19:13 +0700
-Subject: [PATCH 15/38] Themes: Allow Navbar ripple color to be themed
+Subject: [PATCH 15/39] Themes: Allow Navbar ripple color to be themed
 
 PS1:
 Layers Commit by @setiawanjimmy
index 7c8bf64b41c1d3079b80f2d01defe0e73bd6850d..d2b47878f39aa52f318c264c192d414fc423aa88 100644 (file)
@@ -1,7 +1,7 @@
-From aff3bc9220590e6cced7b8cfaefae6bb005843c6 Mon Sep 17 00:00:00 2001
+From 5dea0080ea8ddb30e3f13e415aee1aa8f1748524 Mon Sep 17 00:00:00 2001
 From: Ivan Iskandar <iiiiskandar14@gmail.com>
 Date: Sun, 18 Sep 2016 21:33:18 +0700
-Subject: [PATCH 16/38] SystemUI: Expose QS edit item decoration background
+Subject: [PATCH 16/39] SystemUI: Expose QS edit item decoration background
  color
 
 PS2:
index 35c30230021c7cadcde5f0ad8c78cf8e8c63da78..b3eaff3fb3763199823b2f14c7adb9d800829226 100644 (file)
@@ -1,7 +1,7 @@
-From 5772f09122cb0bd56cbd7121a3fd4d2389ef8102 Mon Sep 17 00:00:00 2001
+From 98380a5481203bb3b1fc87c9858956de2adc740f Mon Sep 17 00:00:00 2001
 From: Simao Gomes Viana <xdevs23@outlook.com>
 Date: Fri, 25 Nov 2016 20:50:29 +0100
-Subject: [PATCH 17/38] Allow custom alpha for notification shade bg color
+Subject: [PATCH 17/39] Allow custom alpha for notification shade bg color
 
 Change-Id: If621df83d994feae0448a734408ba85ac8329325
 ---
index b0e6c938a7c9cc73f48ebaa21d8b816cd5224748..3a3aab185b92760142c487e5a5a31a64a65de525 100644 (file)
@@ -1,7 +1,7 @@
-From 8a1870f105abf0e2236e96fc315e7851c5af0d9b Mon Sep 17 00:00:00 2001
+From 09d5a2a8420cb4b80e3bd5d5076573d8cd5f29ff Mon Sep 17 00:00:00 2001
 From: "Niklas Schnettler (Sh4dowSoul)" <niklas.schnettler@gmail.com>
 Date: Wed, 5 Oct 2016 18:07:43 +0200
-Subject: [PATCH 18/38] Themes: Expose various QuickSettings text colors
+Subject: [PATCH 18/39] Themes: Expose various QuickSettings text colors
 
 Change-Id: Iaea71ca83afbc3d8cc6faea6afac16cabb46cfff
 ---
index d9a2078782c01430b40fbc69c20bedc536d52c84..f0f5625957b3683083e650c5e3b6c741944889b4 100644 (file)
@@ -1,7 +1,7 @@
-From 3a0db1599e4075a7e9b08d0926e699da38378f45 Mon Sep 17 00:00:00 2001
+From b072c46400630130d11199c37dbcdc00f122188e Mon Sep 17 00:00:00 2001
 From: Nicholas Chum <nicholaschum@gmail.com>
 Date: Sat, 27 Aug 2016 10:56:46 -0400
-Subject: [PATCH 19/38] Notifications: Expose a bool to disable dynamic colors
+Subject: [PATCH 19/39] Notifications: Expose a bool to disable dynamic colors
 
 This commit allows a themer to overlay a boolean value in config.xml to
 disable dynamic colors applied to the app title and app icon of each
index 38b92749990c31c0c646e9668203abb1f3bcfe01..cccb0aa79d928b85ae37aee4bb1ae137b46ab018 100644 (file)
@@ -1,7 +1,7 @@
-From e484b9a479870311436fa12bc6df4494a9822525 Mon Sep 17 00:00:00 2001
+From 523b039278acdf5a7044b1e33af5844bde06cc51 Mon Sep 17 00:00:00 2001
 From: George G <kreach3r@users.noreply.github.com>
 Date: Mon, 14 Nov 2016 14:49:47 +0200
-Subject: [PATCH 20/38] Notification dynamic colors bool compatible with OMS7
+Subject: [PATCH 20/39] Notification dynamic colors bool compatible with OMS7
 
 OMS7 introduced this fine piece of code: https://github.com/SubstratumResources/platform_frameworks_base/blob/n-oms7/core/java/android/app/ResourcesManager.java#L897..#L904
 
index ac03ef9949780a9f30535041ec32744a848f5040..d88eb54d3d6773f1e8a6d72854e334990a495722 100644 (file)
@@ -1,7 +1,7 @@
-From acc1fe734b3cf4343191dad13cef6f651f9d044a Mon Sep 17 00:00:00 2001
+From ec0c1b4b8f2088b531f5e28666ff2d2c536cd7eb Mon Sep 17 00:00:00 2001
 From: Daniel Koman <dankoman30@gmail.com>
 Date: Fri, 17 Apr 2015 11:56:28 -0600
-Subject: [PATCH 21/38] Allow prevention of doze notification color inversion
+Subject: [PATCH 21/39] Allow prevention of doze notification color inversion
 
 Removed empty newline at the end -- KreAch3R
 Removed slims files for aosp roms -- Bgill55
index b367c9035e86be00e06b5d49c5b6639e48a8e9f3..81b45bde2ea11792e1fbbc0b3d595f799f310940 100644 (file)
@@ -1,7 +1,7 @@
-From 1e00e3dec282ac00f3eab9d663adce880d519c4b Mon Sep 17 00:00:00 2001
+From 45f68624cd0345615aeffda1158d402ac7c5acac Mon Sep 17 00:00:00 2001
 From: George G <kreach3r@users.noreply.github.com>
 Date: Mon, 14 Nov 2016 14:44:17 +0200
-Subject: [PATCH 22/38] OMS7 compatible 'Ambient notification inversion'
+Subject: [PATCH 22/39] OMS7 compatible 'Ambient notification inversion'
 
 OMS7 introduced this fine piece of code: https://github.com/SubstratumResources/platform_frameworks_base/blob/n-oms7/core/java/android/app/ResourcesManager.java#L897..#L904
 
index 7342e2ff593629fae386fb8e7fb8f139419ea2c0..b1817365adbece5b39e89c69f79685e6c8831eae 100644 (file)
@@ -1,7 +1,7 @@
-From 4935cc9f77a490d87115c679812d844a44f4c099 Mon Sep 17 00:00:00 2001
+From 4a9f18596e8c45c0dc17407039b730fbf173d03d Mon Sep 17 00:00:00 2001
 From: Ivan Iskandar <iiiiskandar14@gmail.com>
 Date: Mon, 5 Dec 2016 19:00:04 +0700
-Subject: [PATCH 23/38] SystemUI: Use own drawables for QS expand icon
+Subject: [PATCH 23/39] SystemUI: Use own drawables for QS expand icon
 
 This was using the volume panel drawables used also on volume panel.
 So with this commit themers can give different icon for either QS
index 46c73afa78f9cb47b59009a8ace6441db2d54d4b..f4c334a9cccff965178359f1b59e82f04e62c65a 100644 (file)
@@ -1,7 +1,7 @@
-From 91bfe07e4c527b269e263730f650d36f2d699593 Mon Sep 17 00:00:00 2001
+From 5bfb6898a0b37ee4d1835fc31e39d83af78d8ff7 Mon Sep 17 00:00:00 2001
 From: 0xD34D <clark@scheffsblend.com>
 Date: Mon, 9 Jan 2017 07:19:41 +0530
-Subject: [PATCH 24/38] N-Extras: Add dynamic theme BootAnimation support
+Subject: [PATCH 24/39] N-Extras: Add dynamic theme BootAnimation support
 
 Extracted from "Themes: Port to CM13 [1/3]"
 http://review.cyanogenmod.org/#/c/113273/14
index 6cc141866cedf7cb2693126eb5c6dd78531fdea4..52a2850e487742d84e0aad5c0313df1d48840228 100644 (file)
@@ -1,7 +1,7 @@
-From f94acee53b3fccbc654e6cf433d54a8372257c6e Mon Sep 17 00:00:00 2001
+From b2a5047293b699bad5b3402b4a89b9cade942bc7 Mon Sep 17 00:00:00 2001
 From: 0xD34D <clark@scheffsblend.com>
 Date: Wed, 22 Jun 2016 23:54:23 +0300
-Subject: [PATCH 25/38] N-Extras: Add dynamic theme fonts support
+Subject: [PATCH 25/39] N-Extras: Add dynamic theme fonts support
 
 Due to the nature of the removal of assetSeq in OMS7+, we now use the
 more controllable font scale updating code to update the fonts on
index 6a10837cb1207990f2d6a0bf1c1fc049775c6e41..60165dc24122ca9cf2ccda486cb2682bc535bd3c 100644 (file)
@@ -1,7 +1,7 @@
-From 2ae9c134e5eefee70bb1abce584b0db310baa8f5 Mon Sep 17 00:00:00 2001
+From 053a5873c01639b5911dc0218b225a97d41e6517 Mon Sep 17 00:00:00 2001
 From: Nicholas Chum <nicholaschum@gmail.com>
 Date: Sun, 17 Jul 2016 17:56:40 -0400
-Subject: [PATCH 26/38] N-Extras: AudioService: Allow system effect sounds to
+Subject: [PATCH 26/39] N-Extras: AudioService: Allow system effect sounds to
  be themed
 
 This commit checks whether there is a preexisting file in the themed
index 69527259423a885d0654d5a20aacc59bbfab74a9..0144f5f1a0be2c44000d1974e4b42f7822fa93e5 100644 (file)
@@ -1,7 +1,7 @@
-From 3722812f93a0ba0de99a435bfdc0f121a5f650d7 Mon Sep 17 00:00:00 2001
+From 82df27f007a1b852634bc305eafe044a7aa48736 Mon Sep 17 00:00:00 2001
 From: George G <kreach3r@users.noreply.github.com>
 Date: Mon, 4 Jul 2016 06:25:15 +0300
-Subject: [PATCH 27/38] OMS7-N: ApplicationsState: add filter for Substratum
+Subject: [PATCH 27/39] OMS7-N: ApplicationsState: add filter for Substratum
  overlays [1/2]
 
 This commit allows the framework to handle the filtering of the
index cd37775f820367f7d1903229a20234b18d7517de..6bfe3e26a49deeff4a5caa0636f6a3fbbd417f0d 100644 (file)
@@ -1,7 +1,7 @@
-From 2cffbe56ae7585e6d863b9647e88303e921bc4de Mon Sep 17 00:00:00 2001
+From c9f5177b9a4614f0cd5602993839050fae0fa643 Mon Sep 17 00:00:00 2001
 From: Kuba Schenk <abukcz@gmail.com>
 Date: Thu, 1 Dec 2016 21:48:26 +0100
-Subject: [PATCH 28/38] OMS7-N: ApplicationsState: add filter for Substratum
+Subject: [PATCH 28/39] OMS7-N: ApplicationsState: add filter for Substratum
  icon overlays [1/2]
 
 This commit allows the framework to handle the filtering of the icon overlays found for OMS.
index 27cb85e3877bb5b2cc9200e76e44c6ceea911fbb..c79f427b68e96601a54860ef266485a1b9a2fc77 100644 (file)
@@ -1,7 +1,7 @@
-From 81a4c442d12408b9d19ead73134d12cc777adf4b Mon Sep 17 00:00:00 2001
+From edb54e92df68678b2d3b7178c2e23b1f890972da Mon Sep 17 00:00:00 2001
 From: Abdulwahab Isam <abdoi94.iq@gmail.com>
 Date: Fri, 7 Oct 2016 08:30:11 +0300
-Subject: [PATCH 29/38] Themes: Expose QS battery
+Subject: [PATCH 29/39] Themes: Expose QS battery
 
 This is needed for white themes like Belo. Should function the same with dark themes as well.
 
index 9cc93f782eec2382bb5833b5d095f218def7d65d..e7230d0f7b0e5c41739adf413b2499ec827febe4 100644 (file)
@@ -1,7 +1,7 @@
-From 1808dee12401982a7df5f7538a40f78de9c1dfe8 Mon Sep 17 00:00:00 2001
+From 3f3902d3bae9bc8cb3c304580ea69936cb0edd9c Mon Sep 17 00:00:00 2001
 From: bigrushdog <randall.rushing@gmail.com>
 Date: Mon, 19 Dec 2016 04:33:31 -0800
-Subject: [PATCH 30/38] OMS: Introduce MODIFY_OVERLAYS permission for user apps
+Subject: [PATCH 30/39] OMS: Introduce MODIFY_OVERLAYS permission for user apps
 
 This permission will grant the app read and write permissions
 to access OverlayManagerService. If caller does not posess
index 816ed3ccc131733b4891fa69a688a1b37535de4e..408159b85533e2ed1f863d170d3b8eb12e4b82c4 100644 (file)
@@ -1,7 +1,7 @@
-From 05d4715f44f7ae4e33517d404c528ac90520cf91 Mon Sep 17 00:00:00 2001
+From 49d41589da4ae6d7229cde9fa3bf7c9679788e45 Mon Sep 17 00:00:00 2001
 From: daveyannihilation <daveyannihilation@hotmail.com>
 Date: Sun, 1 Jan 2017 01:47:53 -0700
-Subject: [PATCH 31/38] SystemUI: Expose switch bar title
+Subject: [PATCH 31/39] SystemUI: Expose switch bar title
 
 This is needed for the power notifications switchbar in SystemUI Tuner, amongst other things.
 
index 8e0645018c4f3a2b37e15dc665ca601cb45aca06..05a1baa28c97e4e890d055c2bf8ef2a5937d76ef 100644 (file)
@@ -1,7 +1,7 @@
-From e1dfd205fa7e1f3c7008e72fb15fe01577ad944c Mon Sep 17 00:00:00 2001
+From 9eeec9c55f7509b89963a5fe36c3d274d871ab60 Mon Sep 17 00:00:00 2001
 From: Bryan Owens <djbryan3540@gmail.com>
 Date: Fri, 6 Jan 2017 21:12:15 +0800
-Subject: [PATCH 32/38] Themes: Expose manifest styles for themes
+Subject: [PATCH 32/39] Themes: Expose manifest styles for themes
 
 Change-Id: Ie3a4fdead4f4fa1c121018b38de1c86a05bbcff2
 ---
index fb4a2d6adedbfbfa89649cd2e070caa074112500..bb8bf7a77dae4d638b89dc449566f12a2412bbd5 100644 (file)
@@ -1,7 +1,7 @@
-From 6f4471f0ce013aa2372be0d1740a18003a348a1e Mon Sep 17 00:00:00 2001
+From 96b9021830fde7f2544016d5c51c16a076a49484 Mon Sep 17 00:00:00 2001
 From: mickybart <mickybart@pygoscelis.org>
 Date: Sat, 19 Nov 2016 19:05:05 -0500
-Subject: [PATCH 33/38] OMS: StrictMode and files under /data/system/theme/
+Subject: [PATCH 33/39] OMS: StrictMode and files under /data/system/theme/
 
 Themes are using /data/system/theme/ to push some files like LowBattery.ogg (audio notification)
 When the device battery trigger the low battery state, the sound is not played due
index 0cc255eacf30cd2c2742a112d55528bcd749fce3..9d91eab69df5ca633d6cf2936f9e8d7d94ba0bea 100644 (file)
@@ -1,7 +1,7 @@
-From 6b8eab5e96bf1889d8ffb4f4da9ed6552018d81f Mon Sep 17 00:00:00 2001
+From 29d865f6db1f5ea6d6866831943ead4382a958bb Mon Sep 17 00:00:00 2001
 From: Daniel Koman <dankoman30@gmail.com>
 Date: Wed, 28 Sep 2016 15:28:26 +0200
-Subject: [PATCH 34/38] doze: allow grayscale even if invert boolean is false
+Subject: [PATCH 34/39] doze: allow grayscale even if invert boolean is false
 
 for dark themes, we are setting the config boolean for inverting
     doze notifications to false.  in addition to preventing
index bedb5dda0177ef7e42267d00248c8fdbfe45314f..cf154e0a68f15e8c4a8c109ef92e9db3091a645c 100644 (file)
@@ -1,7 +1,7 @@
-From 3dc4ac418abd5cb3e3fa49377fbc12a6c88fae8c Mon Sep 17 00:00:00 2001
+From 344ae621e0b9fea43506d99b7fb12e95414f7825 Mon Sep 17 00:00:00 2001
 From: Alex Cruz <mazdarider23@gmail.com>
 Date: Tue, 24 Jan 2017 11:14:46 +0100
-Subject: [PATCH 35/38] Expose external qs tile tint color
+Subject: [PATCH 35/39] Expose external qs tile tint color
 
 This should allow themers to get around issues like this (see pic below)
 
index 01974739eada7a8ee521646f3031b4c536aa6187..71c2b227a5366d3f751e3ebcacbbeb01a47c6904 100644 (file)
@@ -1,7 +1,7 @@
-From eef9bb93ec4e320cda2d3bdcb52838226040626b Mon Sep 17 00:00:00 2001
+From 4160deefa178641c4ad0a04d4666e2f1f5032d79 Mon Sep 17 00:00:00 2001
 From: George G <kreach3r@users.noreply.github.com>
 Date: Thu, 2 Feb 2017 01:52:27 +0200
-Subject: [PATCH 36/38] graphics: ADB "N" icon compatible with OMS7
+Subject: [PATCH 36/39] graphics: ADB "N" icon compatible with OMS7
 
 It's the same problem as the booleans again. This time, it affected the adb "N" icon in the statusbar.
 This commit should fix this.
index b506a2d9c7e985aa7a5fef5588fe55a50b69a440..0f7067818d91b5334d366dbff569471ce3ab3d6a 100644 (file)
@@ -1,7 +1,7 @@
-From 00fbcc2ad550555e8059fac66d621d427851ed81 Mon Sep 17 00:00:00 2001
+From 7c68e1ec39a7031020eaaadebdb0aee86f48cec1 Mon Sep 17 00:00:00 2001
 From: Alex Cruz <mazdarider23@gmail.com>
 Date: Sat, 4 Feb 2017 14:13:26 +0100
-Subject: [PATCH 37/38] Set external QS tiles tint mode to SRC_ATOP
+Subject: [PATCH 37/39] Set external QS tiles tint mode to SRC_ATOP
 
 While the external qs tile tint color was exposed, we had the same problem
 we had with the external icons in Settings which is if a themer set the color
index c400219beee6c0bd105d88e1141076325ca1c366..539a2f4c5776a90fe75bf834921ae37d19d06da5 100644 (file)
@@ -1,7 +1,7 @@
-From 727d6a18ff231377156a16bcbd544a71dd0fdf3f Mon Sep 17 00:00:00 2001
+From 4bfd7364e528492112373eb81ae4ff97a42658f0 Mon Sep 17 00:00:00 2001
 From: Branden M <wasabi.dev@gmail.com>
 Date: Wed, 1 Feb 2017 22:22:45 -0600
-Subject: [PATCH 38/38] Themes: Expose Keyguard affordance circle background
+Subject: [PATCH 38/39] Themes: Expose Keyguard affordance circle background
 
 Change-Id: Id4a078cdbc944fa0c0736103045a0382d49ecb80
 ---
diff --git a/patches/frameworks/base/0039-Add-a-protected-broadcast-for-Masquerade-events.patch b/patches/frameworks/base/0039-Add-a-protected-broadcast-for-Masquerade-events.patch
new file mode 100644 (file)
index 0000000..bb47cd2
--- /dev/null
@@ -0,0 +1,44 @@
+From f0e66274ad4cc5f8adc34ccb07db3162f11a35f8 Mon Sep 17 00:00:00 2001
+From: bigrushdog <randall.rushing@gmail.com>
+Date: Sat, 14 Jan 2017 23:33:38 -0800
+Subject: [PATCH 39/39] Add a protected broadcast for Masquerade events
+
+Parse extras to get event details. Docs will be available
+in Masquerade source code
+
+Change-Id: I24ca3d11438bb830ce97af8b0e935c0700b394e6
+---
+ core/res/AndroidManifest.xml                                        | 2 ++
+ services/core/java/com/android/server/pm/PackageManagerService.java | 3 ++-
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
+index 36df6eac13e..768b04c86c5 100644
+--- a/core/res/AndroidManifest.xml
++++ b/core/res/AndroidManifest.xml
+@@ -517,6 +517,8 @@
+     <protected-broadcast android:name="com.android.server.retaildemo.ACTION_RESET_DEMO" />
+     <protected-broadcast android:name="cyanogenmod.intent.action.LID_STATE_CHANGED" />
++    <protected-broadcast android:name="masquerade.substratum.STATUS_CHANGED" />
++
+     <!-- ====================================================================== -->
+     <!--                          RUNTIME PERMISSIONS                           -->
+     <!-- ====================================================================== -->
+diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
+index faaff1f08a2..79032a915ca 100644
+--- a/services/core/java/com/android/server/pm/PackageManagerService.java
++++ b/services/core/java/com/android/server/pm/PackageManagerService.java
+@@ -4568,7 +4568,8 @@ public class PackageManagerService extends IPackageManager.Stub {
+                 if (actionName.startsWith("android.net.netmon.lingerExpired")
+                         || actionName.startsWith("com.android.server.sip.SipWakeupTimer")
+                         || actionName.startsWith("com.android.internal.telephony.data-reconnect")
+-                        || actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) {
++                        || actionName.startsWith("android.net.netmon.launchCaptivePortalApp")
++                        || actionName.startsWith("masquerade.substratum.STATUS_CHANGED")) {
+                     return true;
+                 }
+             }
+-- 
+2.11.1
+
diff --git a/patches/packages/apps/masquerade/0001-Rewrite-Masquerade-for-UID-system-ops-1-3.patch b/patches/packages/apps/masquerade/0001-Rewrite-Masquerade-for-UID-system-ops-1-3.patch
new file mode 100644 (file)
index 0000000..1789e66
--- /dev/null
@@ -0,0 +1,2284 @@
+From 2b7192e30c8a93b716def9fc564e90867e0f96f3 Mon Sep 17 00:00:00 2001
+From: bigrushdog <randall.rushing@gmail.com>
+Date: Wed, 14 Dec 2016 01:06:32 -0800
+Subject: [PATCH 1/3] Rewrite Masquerade for UID system ops [1/3]
+
+Allow Substratum to offload a handful of functions
+to system to support rootless operation
+
+Credit @Surge1223 for cleaning up restartUI function
+https://github.com/VelvetProject/packages_apps_masquerade/commit/3484db3dda1e7bf7e3f6fda049b903968f1506e7
+
+Change-Id: Ie379fc81e5b80550e044df59c80a60b4890c340c
+---
+ app/src/main/AndroidManifest.xml                   |   33 +-
+ .../substratum/activities/LoaderActivity.java      |   17 -
+ .../substratum/receivers/BootReceiver.java         |   13 +
+ .../UninstallReceiver.java}                        |   11 +-
+ .../substratum/services/BootDetector.java          |   15 -
+ .../masquerade/substratum/services/JobService.java | 1005 ++++++++++++++++++++
+ .../masquerade/substratum/services/MasqDemo.java   |  137 +++
+ .../java/masquerade/substratum/util/Helper.java    |  113 ---
+ .../substratum/util/IconPackApplicator.java        |  136 ---
+ .../substratum/util/ReadOverlaysFile.java          |   46 -
+ .../main/java/masquerade/substratum/util/Root.java |   83 --
+ .../masquerade/substratum/util/Uninstaller.java    |  107 ---
+ .../java/masquerade/substratum/utils/IOUtils.java  |  170 ++++
+ .../masquerade/substratum/utils/SoundUtils.java    |  210 ++++
+ 14 files changed, 1555 insertions(+), 541 deletions(-)
+ delete mode 100644 app/src/main/java/masquerade/substratum/activities/LoaderActivity.java
+ create mode 100644 app/src/main/java/masquerade/substratum/receivers/BootReceiver.java
+ rename app/src/main/java/masquerade/substratum/{services/UninstallDetector.java => receivers/UninstallReceiver.java} (87%)
+ delete mode 100644 app/src/main/java/masquerade/substratum/services/BootDetector.java
+ create mode 100644 app/src/main/java/masquerade/substratum/services/JobService.java
+ create mode 100644 app/src/main/java/masquerade/substratum/services/MasqDemo.java
+ delete mode 100644 app/src/main/java/masquerade/substratum/util/Helper.java
+ delete mode 100644 app/src/main/java/masquerade/substratum/util/IconPackApplicator.java
+ delete mode 100644 app/src/main/java/masquerade/substratum/util/ReadOverlaysFile.java
+ delete mode 100644 app/src/main/java/masquerade/substratum/util/Root.java
+ delete mode 100644 app/src/main/java/masquerade/substratum/util/Uninstaller.java
+ create mode 100644 app/src/main/java/masquerade/substratum/utils/IOUtils.java
+ create mode 100644 app/src/main/java/masquerade/substratum/utils/SoundUtils.java
+
+diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
+index aaffdf2..13db0f9 100644
+--- a/app/src/main/AndroidManifest.xml
++++ b/app/src/main/AndroidManifest.xml
+@@ -1,12 +1,20 @@
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+           package="masquerade.substratum"
+-          android:versionCode="20"
+-          android:versionName="twenty - procyon">
++          android:versionCode="21"
++          android:versionName="twentyone - something"
++          coreApp="true"
++          android:sharedUserId="android.uid.system">
+     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/>
++    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
++    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
++    <uses-permission android:name="android.permission.DELETE_PACKAGES"/>
++    <uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
++    <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"/>
++    <uses-permission android:name="android.permission.MODIFY_OVERLAYS"/>
+     <application
+         android:allowBackup="true"
+@@ -14,30 +22,19 @@
+         android:label="@string/app_name"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+-        <receiver android:name="masquerade.substratum.services.BootDetector">
++        <receiver android:name=".receivers.BootReceiver">
+             <intent-filter>
+                 <action android:name="android.intent.action.BOOT_COMPLETED"/>
+             </intent-filter>
+         </receiver>
+-        <receiver android:name=".services.UninstallDetector">
++        <receiver android:name=".receivers.UninstallReceiver">
+             <intent-filter>
+                 <action android:name="android.intent.action.PACKAGE_REMOVED"/>
+                 <data android:scheme="package"/>
+             </intent-filter>
+         </receiver>
+-        <receiver android:name=".util.Helper">
+-            <intent-filter>
+-                <action android:name="masquerade.substratum.COMMANDS"/>
+-            </intent-filter>
+-        </receiver>
+-
+-        <activity android:name="masquerade.substratum.activities.LoaderActivity">
+-            <intent-filter>
+-                <action android:name="android.intent.action.MAIN"/>
+-                <action android:name="masquerade.substratum.INITIALIZE"/>
+-
+-                <category android:name="android.intent.category.DEFAULT"/>
+-            </intent-filter>
+-        </activity>
++        <service android:name=".services.JobService"
++            android:exported="true"
++            android:permission="android.permission.MODIFY_OVERLAYS" />
+     </application>
+ </manifest>
+diff --git a/app/src/main/java/masquerade/substratum/activities/LoaderActivity.java b/app/src/main/java/masquerade/substratum/activities/LoaderActivity.java
+deleted file mode 100644
+index 3af5ec3..0000000
+--- a/app/src/main/java/masquerade/substratum/activities/LoaderActivity.java
++++ /dev/null
+@@ -1,17 +0,0 @@
+-package masquerade.substratum.activities;
+-
+-import android.app.Activity;
+-import android.os.Bundle;
+-import android.util.Log;
+-
+-import masquerade.substratum.util.Root;
+-
+-public class LoaderActivity extends Activity {
+-    @Override
+-    protected void onCreate(Bundle savedInstanceState) {
+-        super.onCreate(savedInstanceState);
+-        Log.d("Masquerade", "Masquerade is now securing superuser permissions!");
+-        Root.requestRootAccess();
+-        finish();
+-    }
+-}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/receivers/BootReceiver.java b/app/src/main/java/masquerade/substratum/receivers/BootReceiver.java
+new file mode 100644
+index 0000000..ebc0c8b
+--- /dev/null
++++ b/app/src/main/java/masquerade/substratum/receivers/BootReceiver.java
+@@ -0,0 +1,13 @@
++package masquerade.substratum.receivers;
++
++import android.content.BroadcastReceiver;
++import android.content.Context;
++import android.content.Intent;
++
++
++public class BootReceiver extends BroadcastReceiver {
++    @Override
++    public void onReceive(Context context, Intent intent) {
++        // do something
++    }
++}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/services/UninstallDetector.java b/app/src/main/java/masquerade/substratum/receivers/UninstallReceiver.java
+similarity index 87%
+rename from app/src/main/java/masquerade/substratum/services/UninstallDetector.java
+rename to app/src/main/java/masquerade/substratum/receivers/UninstallReceiver.java
+index 7af387b..b377e27 100644
+--- a/app/src/main/java/masquerade/substratum/services/UninstallDetector.java
++++ b/app/src/main/java/masquerade/substratum/receivers/UninstallReceiver.java
+@@ -1,4 +1,4 @@
+-package masquerade.substratum.services;
++package masquerade.substratum.receivers;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+@@ -11,20 +11,18 @@ import android.util.Log;
+ import java.util.ArrayList;
+ import java.util.List;
+-import masquerade.substratum.util.ReadOverlaysFile;
+-import masquerade.substratum.util.Root;
+-public class UninstallDetector extends BroadcastReceiver {
++public class UninstallReceiver extends BroadcastReceiver {
+     @Override
+     public void onReceive(Context context, Intent intent) {
+         if ("android.intent.action.PACKAGE_REMOVED".equals(intent.getAction())) {
+             Uri packageName = intent.getData();
+             if (packageName.toString().substring(8).equals("projekt.substratum")) {
+-                new performUninstalls().execute("");
++//                new performUninstalls().execute("");
+             }
+         }
+     }
+-
++/*
+     public class performUninstalls extends AsyncTask<String, Integer, String> {
+         @Override
+         protected String doInBackground(String... sUrl) {
+@@ -45,4 +43,5 @@ public class UninstallDetector extends BroadcastReceiver {
+             return null;
+         }
+     }
++    */
+ }
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/services/BootDetector.java b/app/src/main/java/masquerade/substratum/services/BootDetector.java
+deleted file mode 100644
+index 3981487..0000000
+--- a/app/src/main/java/masquerade/substratum/services/BootDetector.java
++++ /dev/null
+@@ -1,15 +0,0 @@
+-package masquerade.substratum.services;
+-
+-import android.content.BroadcastReceiver;
+-import android.content.Context;
+-import android.content.Intent;
+-
+-import masquerade.substratum.util.Helper;
+-
+-public class BootDetector extends BroadcastReceiver {
+-    @Override
+-    public void onReceive(Context context, Intent intent) {
+-        Intent pushIntent = new Intent(context, Helper.class);
+-        context.startService(pushIntent);
+-    }
+-}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/services/JobService.java b/app/src/main/java/masquerade/substratum/services/JobService.java
+new file mode 100644
+index 0000000..3c1b859
+--- /dev/null
++++ b/app/src/main/java/masquerade/substratum/services/JobService.java
+@@ -0,0 +1,1005 @@
++
++package masquerade.substratum.services;
++
++import android.app.ActivityManager;
++import android.app.ActivityManagerNative;
++import android.app.PendingIntent;
++import android.app.Service;
++import android.content.BroadcastReceiver;
++import android.content.ComponentName;
++import android.content.Context;
++import android.content.IIntentReceiver;
++import android.content.IIntentSender;
++import android.content.Intent;
++import android.content.IntentFilter;
++import android.content.IntentSender;
++import android.content.om.IOverlayManager;
++import android.content.om.OverlayInfo;
++import android.content.pm.IPackageInstallObserver2;
++import android.content.pm.IPackageManager;
++import android.content.pm.PackageInstaller;
++import android.content.pm.PackageManager;
++import android.content.pm.PackageManager.NameNotFoundException;
++import android.content.res.AssetManager;
++import android.content.res.Configuration;
++import android.graphics.Typeface;
++import android.media.RingtoneManager;
++import android.os.Binder;
++import android.os.Bundle;
++import android.os.FileUtils;
++import android.os.Handler;
++import android.os.HandlerThread;
++import android.os.IBinder;
++import android.os.Looper;
++import android.os.Message;
++import android.os.Process;
++import android.os.RemoteException;
++import android.os.ServiceManager;
++import android.os.SystemProperties;
++import android.os.UserHandle;
++import android.provider.Settings;
++import android.text.TextUtils;
++import android.util.Log;
++
++import java.io.BufferedInputStream;
++import java.io.BufferedOutputStream;
++import java.io.File;
++import java.io.FileInputStream;
++import java.io.FileNotFoundException;
++import java.io.FileOutputStream;
++import java.io.IOException;
++import java.io.InputStream;
++import java.io.OutputStream;
++import java.lang.reflect.Method;
++import java.util.ArrayList;
++import java.util.List;
++import java.util.Locale;
++import java.util.concurrent.SynchronousQueue;
++import java.util.concurrent.TimeUnit;
++import java.util.zip.ZipEntry;
++import java.util.zip.ZipInputStream;
++
++import masquerade.substratum.utils.IOUtils;
++import masquerade.substratum.utils.SoundUtils;
++
++import com.android.internal.statusbar.IStatusBarService;
++
++public class JobService extends Service {
++    private static final String TAG = JobService.class.getSimpleName();
++    private static final boolean DEBUG = true;
++
++    private static final String MASQUERADE_TOKEN = "masquerade_token";
++    private static final String SUBSTRATUM_PACKAGE = "projekt.substratum";
++    private static final String[] AUTHORIZED_CALLERS = new String[] {
++            SUBSTRATUM_PACKAGE,
++            "masquerade.substratum"
++    };
++
++    public static final String INTENT_STATUS_CHANGED = "masquerade.substratum.STATUS_CHANGED";
++
++    public static final String PRIMARY_COMMAND_KEY = "primary_command_key";
++    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 COMMAND_VALUE_JOB_COMPLETE = "job_complete";
++    public static final String COMMAND_VALUE_INSTALL = "install";
++    public static final String COMMAND_VALUE_UNINSTALL = "uninstall";
++    public static final String COMMAND_VALUE_RESTART_UI = "restart_ui";
++    public static final String COMMAND_VALUE_CONFIGURATION_SHIM = "configuration_shim";
++    public static final String COMMAND_VALUE_BOOTANIMATION = "bootanimation";
++    public static final String COMMAND_VALUE_FONTS = "fonts";
++    public static final String COMMAND_VALUE_AUDIO = "audio";
++
++    private static IOverlayManager mOMS;
++    private static IPackageManager mPM;
++
++    private HandlerThread mWorker;
++    private JobHandler mJobHandler;
++    private MainHandler mMainHandler;
++    private final List<Runnable> mJobQueue = new ArrayList<>(0);
++    private long mLastJobTime;
++
++    @Override
++    public void onCreate() {
++        mWorker = new HandlerThread("BackgroundWorker", Process.THREAD_PRIORITY_BACKGROUND);
++        mWorker.start();
++        mJobHandler = new JobHandler(mWorker.getLooper());
++        mMainHandler = new MainHandler(Looper.getMainLooper());
++    }
++
++    @Override
++    public int onStartCommand(Intent intent, int flags, int startId) {
++        // verify identity
++        if (!isCallerAuthorized(intent)) {
++            log("caller not authorized, aborting");
++            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;
++        // }
++        // filter out duplicate intents
++        long jobTime = intent.getLongExtra(JOB_TIME_KEY, 1);
++        if (jobTime == 1 || jobTime == mLastJobTime) {
++            log("got empty jobtime or duplicate job time, aborting");
++            return START_NOT_STICKY;
++        }
++        mLastJobTime = jobTime;
++
++        // must have a primary command
++        String command = intent.getStringExtra(PRIMARY_COMMAND_KEY);
++        if (TextUtils.isEmpty(command)) {
++            log("Got empty primary command, aborting");
++            return START_NOT_STICKY;
++        }
++
++        // queue up the job
++
++        List<Runnable> jobs_to_add = new ArrayList<>(0);
++
++        log("Starting job with primary command " + command + " With job time " + jobTime);
++        if (TextUtils.equals(command, COMMAND_VALUE_INSTALL)) {
++            List<String> paths = intent.getStringArrayListExtra(INSTALL_LIST_KEY);
++            for (String path : paths) {
++                jobs_to_add.add(new Installer(path));
++            }
++        } else if (TextUtils.equals(command, COMMAND_VALUE_UNINSTALL)) {
++            List<String> packages = intent.getStringArrayListExtra(UNINSTALL_LIST_KEY);
++            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());
++            }
++        } else if (TextUtils.equals(command, COMMAND_VALUE_RESTART_UI)) {
++            jobs_to_add.add(new UiResetJob());
++        } else if (TextUtils.equals(command, COMMAND_VALUE_CONFIGURATION_SHIM)) {
++            jobs_to_add.add(new LocaleChanger(getApplicationContext(), mMainHandler));
++        } else if (TextUtils.equals(command, COMMAND_VALUE_BOOTANIMATION)) {
++            String fileName = intent.getStringExtra(BOOTANIMATION_FILE_NAME);
++            if (TextUtils.isEmpty(fileName)) {
++                jobs_to_add.add(new BootAnimationJob(true));
++            } else {
++                jobs_to_add.add(new BootAnimationJob(fileName));
++            }
++        } else if (TextUtils.equals(command, COMMAND_VALUE_FONTS)) {
++            String pid = intent.getStringExtra(FONTS_PID);
++            String fileName = intent.getStringExtra(FONTS_FILENAME);
++            jobs_to_add.add(new FontsJob(pid, fileName));
++            jobs_to_add.add(new UiResetJob());
++        } else if (TextUtils.equals(command, COMMAND_VALUE_AUDIO)) {
++            String pid = intent.getStringExtra(AUDIO_PID);
++            String fileName = intent.getStringExtra(AUDIO_FILENAME);
++            jobs_to_add.add(new SoundsJob(pid, fileName));
++            jobs_to_add.add(new UiResetJob());
++        }
++
++        if (jobs_to_add.size() > 0) {
++            log("Adding new jobs to job queue");
++            synchronized (mJobQueue) {
++                mJobQueue.addAll(jobs_to_add);
++            }
++            if (!mJobHandler.hasMessages(JobHandler.MESSAGE_CHECK_QUEUE)) {
++                mJobHandler.sendEmptyMessage(JobHandler.MESSAGE_CHECK_QUEUE);
++            }
++        }
++
++        return START_NOT_STICKY;
++    }
++
++    @Override
++    public IBinder onBind(Intent intent) {
++        return null;
++    }
++
++    @Override
++    public void onDestroy() {
++
++    }
++
++    private boolean isProcessing() {
++        return mJobQueue.size() > 0;
++    }
++
++    private class LocalService extends Binder {
++        public JobService getService() {
++            return JobService.this;
++        }
++    }
++
++    private static IOverlayManager getOMS() {
++        if (mOMS == null) {
++            mOMS = IOverlayManager.Stub.asInterface(
++                    ServiceManager.getService("overlay"));
++        }
++        return mOMS;
++    }
++
++    private static IPackageManager getPM() {
++        if (mPM == null) {
++            mPM = IPackageManager.Stub.asInterface(
++                    ServiceManager.getService("package"));
++        }
++        return mPM;
++    }
++
++    private void install(String path, IPackageInstallObserver2 observer) {
++        try {
++            getPM().installPackageAsUser(path, observer, PackageManager.INSTALL_REPLACE_EXISTING,
++                    null,
++                    UserHandle.USER_SYSTEM);
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    private void uninstall(String packageName) {
++        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);
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    private void disableOverlay(String packageName) {
++        try {
++            getOMS().setEnabled(packageName, false, UserHandle.USER_SYSTEM, false);
++        } catch (RemoteException e) {
++            e.printStackTrace();
++        }
++    }
++
++    private boolean isOverlayEnabled(String packageName) {
++        boolean enabled = false;
++        try {
++            OverlayInfo info = getOMS().getOverlayInfo(packageName, UserHandle.USER_ALL);
++            enabled = info.isEnabled();
++        } catch (RemoteException e) {
++            e.printStackTrace();
++        }
++        return enabled;
++    }
++
++    private void copyFonts(String pid, String zipFileName) {
++        // prepare local cache dir for font package assembly
++        log("Copy Fonts - Package ID = " + pid + " filename = " + zipFileName);
++        File cacheDir = new File(getCacheDir(), "/FontCache/");
++        if (cacheDir.exists()) {
++            IOUtils.deleteRecursive(cacheDir);
++        }
++        cacheDir.mkdir();
++
++        // copy system fonts into our cache dir
++        IOUtils.copyFolder("/system/fonts", cacheDir.getAbsolutePath());
++
++        // append zip to filename since it is probably removed
++        // for list presentation
++        if (!zipFileName.endsWith(".zip")) {
++            zipFileName = zipFileName + ".zip";
++        }
++
++        // copy target themed fonts zip to our cache dir
++        Context themeContext = getAppContext(pid);
++        AssetManager am = themeContext.getAssets();
++        try {
++            InputStream inputStream = am.open("fonts/" + zipFileName);
++            OutputStream outputStream = new FileOutputStream(getCacheDir()
++                    .getAbsolutePath() + "/FontCache/" + zipFileName);
++            IOUtils.bufferedCopy(inputStream, outputStream);
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++
++        // unzip new fonts and delete zip file, overwriting any system fonts
++        File fontZip = new File(getCacheDir(), "/FontCache/" + zipFileName);
++        IOUtils.unzip(fontZip.getAbsolutePath(), cacheDir.getAbsolutePath());
++        fontZip.delete();
++
++        // check if theme zip included a fonts.xml. If not, Substratum
++        // is kind enough to provide one for us in it's assets
++        try {
++            File testConfig = new File(getCacheDir(), "/FontCache/" + "fonts.xml");
++            if (!testConfig.exists()) {
++                Context subContext = getSubsContext();
++                AssetManager subsAm = subContext.getAssets();
++                InputStream inputStream = subsAm.open("fonts.xml");
++                OutputStream outputStream = new FileOutputStream(testConfig);
++                IOUtils.bufferedCopy(inputStream, outputStream);
++            }
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++
++        // prepare system theme fonts folder and copy new fonts folder from our cache
++        IOUtils.deleteThemedFonts();
++        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));
++    }
++
++    private void clearFonts() {
++        IOUtils.deleteThemedFonts();
++        SystemProperties.set("sys.refresh_theme", "1");
++        Typeface.recreateDefaults();
++        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));
++    }
++
++    private void applyThemedSounds(String pid, String zipFileName) {
++        // prepare local cache dir for font package assembly
++        log("Copy sounds - Package ID = " + pid + " filename = " + zipFileName);
++        File cacheDir = new File(getCacheDir(), "/SoundsCache/");
++        if (cacheDir.exists()) {
++            IOUtils.deleteRecursive(cacheDir);
++        }
++        cacheDir.mkdir();
++
++        // append zip to filename since it is probably removed
++        // for list presentation
++        if (!zipFileName.endsWith(".zip")) {
++            zipFileName = zipFileName + ".zip";
++        }
++
++        // copy target themed sounds zip to our cache dir
++        Context themeContext = getAppContext(pid);
++        AssetManager am = themeContext.getAssets();
++        try {
++            InputStream inputStream = am.open("audio/" + zipFileName);
++            OutputStream outputStream = new FileOutputStream(getCacheDir()
++                    .getAbsolutePath() + "/SoundsCache/" + zipFileName);
++            IOUtils.bufferedCopy(inputStream, outputStream);
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++
++        // unzip new sounds and delete zip file
++        File soundsZip = new File(getCacheDir(), "/SoundsCache/" + zipFileName);
++        IOUtils.unzip(soundsZip.getAbsolutePath(), cacheDir.getAbsolutePath());
++        soundsZip.delete();
++
++        clearSounds(this);
++        IOUtils.createAudioDirIfNotExists();
++
++        File uiSoundsCache = new File(getCacheDir(), "/SoundsCache/ui/");
++        if (uiSoundsCache.exists() && uiSoundsCache.isDirectory()) {
++            IOUtils.createUiSoundsDirIfNotExists();
++            File effect_tick_mp3 = new File(getCacheDir(), "/SoundsCache/ui/Effect_Tick.mp3");
++            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/");
++        if (alarmCache.exists() && alarmCache.isDirectory()) {
++            IOUtils.createAlarmDirIfNotExists();
++            File new_alarm_mp3 = new File(getCacheDir(), "/SoundsCache/alarms/alarm.mp3");
++            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/");
++        if (notifCache.exists() && notifCache.isDirectory()) {
++            IOUtils.createNotificationDirIfNotExists();
++            File new_notif_mp3 = new File(getCacheDir(), "/SoundsCache/notifications/notification.mp3");
++            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/");
++        if (ringtoneCache.exists() && ringtoneCache.isDirectory()) {
++            IOUtils.createRingtoneDirIfNotExists();
++            File new_ring_mp3 = new File(getCacheDir(), "/SoundsCache/ringtones/ringtone.mp3");
++            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);
++                }
++            }
++        }
++    }
++
++    private void clearSounds(Context ctx) {
++        IOUtils.deleteThemedAudio();
++        SoundUtils.setDefaultAudible(JobService.this, RingtoneManager.TYPE_ALARM);
++        SoundUtils.setDefaultAudible(JobService.this, RingtoneManager.TYPE_NOTIFICATION);
++        SoundUtils.setDefaultAudible(JobService.this, RingtoneManager.TYPE_RINGTONE);
++        SoundUtils.setDefaultUISounds(getContentResolver(), "lock_sound", "Lock.ogg");
++        SoundUtils.setDefaultUISounds(getContentResolver(), "unlock_sound", "Unlock.ogg");
++        SoundUtils.setDefaultUISounds(getContentResolver(), "low_battery_sound",
++                "LowBattery.ogg");
++    }
++
++    private void copyBootAnimation(String fileName) {
++        try {
++            clearBootAnimation();
++            File source = new File(fileName);
++            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);
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    private void clearBootAnimation() {
++        try {
++            File f = new File(IOUtils.SYSTEM_THEME_BOOTANIMATION_PATH);
++            if (f.exists()) {
++                f.delete();
++            }
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    private void restartUi() {
++        try {
++            ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
++            Class ActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
++            Method getDefault = ActivityManagerNative.getDeclaredMethod("getDefault", null);
++            Object amn = getDefault.invoke(null, null);
++            Method killApplicationProcess = amn.getClass().getDeclaredMethod("killApplicationProcess", String.class, int.class);
++            stopService(new Intent().setComponent(new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService")));
++            am.killBackgroundProcesses("com.android.systemui");
++            for (ActivityManager.RunningAppProcessInfo app : am.getRunningAppProcesses()) {
++                if ("com.android.systemui".equals(app.processName)) {
++                    killApplicationProcess.invoke(amn, app.processName, app.uid);
++                    break;
++                }
++            }
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    private void killPackage(String packageName) {
++        try {
++            ActivityManagerNative.getDefault().forceStopPackage(packageName,
++                    UserHandle.USER_SYSTEM);
++        } catch (RemoteException e) {
++            e.printStackTrace();
++        }
++    }
++
++    private Context getSubsContext() {
++        return getAppContext(SUBSTRATUM_PACKAGE);
++    }
++
++    private Context getAppContext(String packageName) {
++        Context ctx = null;
++        try {
++            ctx = getApplicationContext().createPackageContext(packageName,
++                    Context.CONTEXT_IGNORE_SECURITY);
++        } catch (NameNotFoundException e) {
++            e.printStackTrace();
++        }
++        return ctx;
++    }
++
++    public static void log(String msg) {
++        if (DEBUG) {
++            Log.e(TAG, msg);
++        }
++    }
++
++    private boolean isCallerAuthorized(Intent intent) {
++        PendingIntent token = null;
++        try {
++            token = (PendingIntent) intent.getParcelableExtra(MASQUERADE_TOKEN);
++        } catch (Exception e) {
++            log("Attempt to start serivce without a token, unauthorized");
++        }
++        if (token == null) {
++            return false;
++        }
++        // SECOND: we got a token, validate originating package
++        // if not in our whitelist, return null
++        String callingPackage = token.getCreatorPackage();
++        boolean isValidPackage = false;
++        for (int i = 0; i < AUTHORIZED_CALLERS.length; i++) {
++            if (TextUtils.equals(callingPackage, AUTHORIZED_CALLERS[i])) {
++                log(callingPackage
++                        + " is an authorized calling package, next validate calling package perms");
++                isValidPackage = true;
++                break;
++            }
++        }
++        if (!isValidPackage) {
++            log(callingPackage + " is not an authorized calling package");
++            return false;
++        }
++        return true;
++    }
++
++    private class MainHandler extends Handler {
++        public static final int MSG_JOB_QUEUE_EMPTY = 1;
++
++        public MainHandler(Looper looper) {
++            super(looper);
++        }
++
++        @Override
++        public void handleMessage(Message msg) {
++            switch (msg.what) {
++                case MSG_JOB_QUEUE_EMPTY:
++                    Intent intent = new Intent(INTENT_STATUS_CHANGED);
++                    intent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_JOB_COMPLETE);
++                    sendBroadcastAsUser(intent, UserHandle.ALL);
++                    break;
++            }
++        }
++    }
++
++    private class JobHandler extends Handler {
++        private static final int MESSAGE_CHECK_QUEUE = 1;
++        private static final int MESSAGE_DEQUEUE = 2;
++
++        public JobHandler(Looper looper) {
++            super(looper);
++        }
++
++        @Override
++        public void handleMessage(Message msg) {
++            switch (msg.what) {
++                case MESSAGE_CHECK_QUEUE:
++                    Runnable job;
++                    synchronized (mJobQueue) {
++                        job = mJobQueue.get(0);
++                    }
++                    if (job != null) {
++                        job.run();
++                    }
++                    break;
++                case MESSAGE_DEQUEUE:
++                    Runnable toRemove = (Runnable) msg.obj;
++                    synchronized (mJobQueue) {
++                        mJobQueue.remove(toRemove);
++                        if (mJobQueue.size() > 0) {
++                            this.sendEmptyMessage(MESSAGE_CHECK_QUEUE);
++                        } else {
++                            log("Job queue empty! All done");
++                            mMainHandler.sendEmptyMessage(MainHandler.MSG_JOB_QUEUE_EMPTY);
++                        }
++                    }
++                    break;
++                default:
++                    log("Unknown message " + msg.what);
++                    break;
++            }
++        }
++    }
++
++    private class StopPackageJob implements Runnable {
++        String mPackage;
++
++        public void StopPackageJob(String _package) {
++            mPackage = _package;
++        }
++
++        @Override
++        public void run() {
++            killPackage(mPackage);
++            log("Killed package " + mPackage);
++            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
++                    StopPackageJob.this);
++            mJobHandler.sendMessage(message);
++        }
++    }
++
++    private class UiResetJob implements Runnable {
++        @Override
++        public void run() {
++            restartUi();
++            log("Restarting SystemUI...");
++            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
++                    UiResetJob.this);
++            mJobHandler.sendMessage(message);
++        }
++    }
++
++    private class FontsJob implements Runnable {
++        boolean mClear;
++        String mPid;
++        String mFileName;
++
++        public FontsJob(String pid, String fileName) {
++            if (pid == null) {
++                mClear = true;
++            } else {
++                mPid = pid;
++                mFileName = fileName;
++            }
++        }
++
++        @Override
++        public void run() {
++            if (mClear) {
++                log("Resetting system font");
++                clearFonts();
++            } else {
++                log("Setting theme font");
++                copyFonts(mPid, mFileName);
++            }
++            Intent intent = new Intent(INTENT_STATUS_CHANGED);
++            intent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_FONTS);
++            sendBroadcastAsUser(intent, UserHandle.ALL);
++            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
++                    FontsJob.this);
++            mJobHandler.sendMessage(message);
++        }
++    }
++
++    private class SoundsJob implements Runnable {
++        boolean mClear;
++        String mPid;
++        String mFileName;
++
++        public SoundsJob(String pid, String fileName) {
++            if (pid == null) {
++                mClear = true;
++            } else {
++                mPid = pid;
++                mFileName = fileName;
++            }
++        }
++
++        @Override
++        public void run() {
++            if (mClear) {
++                log("Resetting system sounds");
++                clearSounds(JobService.this);
++            } else {
++                log("Setting theme sounds");
++                applyThemedSounds(mPid, mFileName);
++            }
++            Intent intent = new Intent(INTENT_STATUS_CHANGED);
++            intent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_AUDIO);
++            sendBroadcastAsUser(intent, UserHandle.ALL);
++            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
++                    SoundsJob.this);
++            mJobHandler.sendMessage(message);
++        }
++    }
++
++    private class BootAnimationJob implements Runnable {
++        String mFileName;
++        final boolean mClear;
++
++        public BootAnimationJob(boolean clear) {
++            mClear = true;
++        }
++
++        public BootAnimationJob(String fileName) {
++            mFileName = fileName;
++            mClear = false;
++        }
++
++        @Override
++        public void run() {
++            if (mClear) {
++                log("Resetting system boot animation");
++                clearBootAnimation();
++            } else {
++                log("Setting themed boot animation");
++                copyBootAnimation(mFileName);
++            }
++            Intent intent = new Intent(INTENT_STATUS_CHANGED);
++            intent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_BOOTANIMATION);
++            sendBroadcastAsUser(intent, UserHandle.ALL);
++            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
++                    BootAnimationJob.this);
++            mJobHandler.sendMessage(message);
++        }
++    }
++
++    private class Installer implements Runnable, IPackageInstallObserver2 {
++        String mPath;
++
++        public Installer(String path) {
++            mPath = path;
++        }
++
++        @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);
++        }
++    }
++
++    private class Remover implements Runnable {
++        String mPackage;
++
++        public Remover(String _package) {
++            mPackage = _package;
++        }
++
++        @Override
++        public void run() {
++            if (isOverlayEnabled(mPackage)) {
++                log("Remover - disabling overlay for " + mPackage);
++                disableOverlay(mPackage);
++            }
++            log("Remover - uninstalling " + mPackage);
++            uninstall(mPackage);
++        }
++    }
++
++    private class LocaleChanger extends BroadcastReceiver implements Runnable {
++        private boolean mIsRegistered;
++        private boolean mDoRestore;
++        private Context mContext;
++        private Handler mHandler;
++        private Locale mCurrentLocale;
++
++        public LocaleChanger(Context context, Handler mainHandler) {
++            mContext = context;
++            mHandler = mainHandler;
++        }
++
++        @Override
++        public void run() {
++            Intent i = new Intent(Intent.ACTION_MAIN);
++            i.addCategory(Intent.CATEGORY_HOME);
++            mContext.startActivity(i);
++            mHandler.postDelayed(new Runnable() {
++                @Override
++                public void run() {
++                    spoofLocale();
++                }
++            }, 500);
++        }
++
++        private void register() {
++            if (!mIsRegistered) {
++                IntentFilter filter = new IntentFilter();
++                filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
++                mContext.registerReceiver(LocaleChanger.this, filter);
++                mIsRegistered = true;
++            }
++        }
++
++        private void unregister() {
++            if (mIsRegistered) {
++                mContext.unregisterReceiver(LocaleChanger.this);
++                mIsRegistered = false;
++            }
++        }
++
++        private void spoofLocale() {
++            Configuration config;
++            log("LocaleChanger - spoofing locale for configuation change shim");
++            try {
++                register();
++                config = ActivityManagerNative.getDefault().getConfiguration();
++                mCurrentLocale = config.locale;
++                Locale toSpoof = Locale.JAPAN;
++                if (mCurrentLocale == Locale.JAPAN) {
++                    toSpoof = Locale.CHINA;
++                }
++                config.setLocale(toSpoof);
++                config.userSetLocale = true;
++                ActivityManagerNative.getDefault().updateConfiguration(config);
++            } catch (RemoteException e) {
++                e.printStackTrace();
++                return;
++            }
++        }
++
++        private void restoreLocale() {
++            Configuration config;
++            log("LocaleChanger - restoring original locale for configuation change shim");
++            try {
++                unregister();
++                config = ActivityManagerNative.getDefault().getConfiguration();
++                config.setLocale(mCurrentLocale);
++                config.userSetLocale = true;
++                ActivityManagerNative.getDefault().updateConfiguration(config);
++            } catch (RemoteException e) {
++                e.printStackTrace();
++                return;
++            }
++            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
++                    LocaleChanger.this);
++            mJobHandler.sendMessage(message);
++        }
++
++        @Override
++        public void onReceive(Context context, Intent intent) {
++            mHandler.postDelayed(new Runnable() {
++                @Override
++                public void run() {
++                    restoreLocale();
++                }
++            }, 500);
++        }
++    }
++
++    private static class LocalIntentReceiver {
++        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
++
++        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
++            @Override
++            public void send(int code, Intent intent, String resolvedType,
++                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
++                try {
++                    mResult.offer(intent, 5, TimeUnit.SECONDS);
++                } catch (InterruptedException e) {
++                    throw new RuntimeException(e);
++                }
++            }
++        };
++
++        public IntentSender getIntentSender() {
++            return new IntentSender((IIntentSender) mLocalSender);
++        }
++
++        public Intent getResult() {
++            try {
++                return mResult.take();
++            } catch (InterruptedException e) {
++                throw new RuntimeException(e);
++            }
++        }
++    }
++}
+diff --git a/app/src/main/java/masquerade/substratum/services/MasqDemo.java b/app/src/main/java/masquerade/substratum/services/MasqDemo.java
+new file mode 100644
+index 0000000..b00d6e0
+--- /dev/null
++++ b/app/src/main/java/masquerade/substratum/services/MasqDemo.java
+@@ -0,0 +1,137 @@
++
++package masquerade.substratum.services;
++
++import java.util.ArrayList;
++import java.util.List;
++
++import android.app.PendingIntent;
++import android.content.BroadcastReceiver;
++import android.content.Context;
++import android.content.Intent;
++import android.text.TextUtils;
++
++public class MasqDemo {
++    public static final String MASQUERADE_TOKEN = "masquerade_token";
++    public static final String PRIMARY_COMMAND_KEY = "primary_command_key";
++    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_PID_KEY = "bootanimation_pid";
++    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 COMMAND_VALUE_INSTALL = "install";
++    public static final String COMMAND_VALUE_UNINSTALL = "uninstall";
++    public static final String COMMAND_VALUE_RESTART_UI = "restart_ui";
++    public static final String COMMAND_VALUE_CONFIGURATION_SHIM = "configuration_shim";
++    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 INTENT_STATUS_CHANGED = "masquerade.substratum.STATUS_CHANGED";
++    public static final String COMMAND_VALUE_JOB_COMPLETE = "job_complete";
++
++    class MasqReceiver extends BroadcastReceiver {
++        @Override
++        public void onReceive(Context context, Intent intent) {
++            if (TextUtils.equals(intent.getAction(), INTENT_STATUS_CHANGED)) {
++                String command = intent.getStringExtra(PRIMARY_COMMAND_KEY);
++                if (TextUtils.equals(command, COMMAND_VALUE_FONTS)) {
++                    // update ui, dismiss progress dialog, etc
++                } else if (TextUtils.equals(command, COMMAND_VALUE_BOOTANIMATION)) {
++
++                } else if (TextUtils.equals(command, COMMAND_VALUE_AUDIO)) {
++
++                } else if (TextUtils.equals(command, COMMAND_VALUE_JOB_COMPLETE)) {
++
++                }
++            }
++        }
++    }
++
++    // demo code for building a base intent for JobService commands
++    public static Intent getMasqIntent(Context ctx) {
++        Intent intent = new Intent();
++        intent.setClassName("masquerade.substratum", "masquerade.substratum.services.JobService");
++        // Credit StackOverflow http://stackoverflow.com/a/28132098
++        // Use dummy PendingIntent for service to validate caller at onBind
++        PendingIntent pending = PendingIntent.getActivity(ctx, 0, new Intent(), 0);
++        intent.putExtra(MASQUERADE_TOKEN, pending);
++        intent.putExtra(JOB_TIME_KEY, System.currentTimeMillis());
++        return intent;
++    }
++
++    public static void install(Context context, ArrayList<String> overlay_apks) {
++        // populate list however
++        Intent masqIntent = getMasqIntent(context);
++        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_INSTALL);
++        masqIntent.putExtra(INSTALL_LIST_KEY, overlay_apks);
++        context.startService(masqIntent);
++    }
++
++    public static void uninstall(Context context, ArrayList<String> packages_to_remove) {
++        Intent masqIntent = getMasqIntent(context);
++        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_UNINSTALL);
++        masqIntent.putExtra(UNINSTALL_LIST_KEY, packages_to_remove);
++        // only need to set if true, will restart SystemUI when done processing packages
++        masqIntent.putExtra(WITH_RESTART_UI_KEY, true);
++        context.startService(masqIntent);
++    }
++
++    public static void restartSystemUI(Context context) {
++        Intent masqIntent = getMasqIntent(context);
++        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_RESTART_UI);
++        context.startService(masqIntent);
++    }
++
++    public static void configurationChangeShim(Context context) {
++        Intent masqIntent = getMasqIntent(context);
++        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_CONFIGURATION_SHIM);
++        context.startService(masqIntent);
++    }
++    
++    public static void clearThemedBootAnimation(Context context) {
++        applyThemedBootAnimation(context, null);
++    }
++    
++    public static void applyThemedBootAnimation(Context context, String fileName) {
++        Intent masqIntent = getMasqIntent(context);
++        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_BOOTANIMATION);
++        if (fileName != null) {
++            masqIntent.putExtra(BOOTANIMATION_FILE_NAME, fileName);
++        } else {
++            // nothing. to reset to stock, just don't add PID and FILE
++        }
++        context.startService(masqIntent);
++    }
++    
++    public static void clearThemedFont(Context context) {
++        applyThemedFont(context, null, null);
++    }
++
++    public static void applyThemedFont(Context context, String pid, String fileName) {
++        Intent masqIntent = getMasqIntent(context);
++        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_FONTS);
++        if (pid != null) {
++            masqIntent.putExtra(FONTS_PID, pid);
++            masqIntent.putExtra(FONTS_FILENAME, fileName);
++        }
++        context.startService(masqIntent);
++    }
++
++    public static void clearThemedSounds(Context context) {
++        applyThemedSounds(context, null, null);
++    }
++
++    public static void applyThemedSounds(Context context, String pid, String fileName) {
++        Intent masqIntent = getMasqIntent(context);
++        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_AUDIO);
++        if (pid != null) {
++            masqIntent.putExtra(AUDIO_PID, pid);
++            masqIntent.putExtra(AUDIO_FILENAME, fileName);
++        }
++        context.startService(masqIntent);
++    }
++}
+diff --git a/app/src/main/java/masquerade/substratum/util/Helper.java b/app/src/main/java/masquerade/substratum/util/Helper.java
+deleted file mode 100644
+index cccf493..0000000
+--- a/app/src/main/java/masquerade/substratum/util/Helper.java
++++ /dev/null
+@@ -1,113 +0,0 @@
+-package masquerade.substratum.util;
+-
+-import android.app.PendingIntent;
+-import android.content.BroadcastReceiver;
+-import android.content.Context;
+-import android.content.Intent;
+-import android.os.Handler;
+-import android.text.TextUtils;
+-import android.util.Log;
+-
+-public class Helper extends BroadcastReceiver {
+-
+-    private static final String SUBSTRATUM_PACKAGE = "projekt.substratum";
+-    private static final String MASQUERADE_TOKEN = "masquerade_token";
+-    private static final String[] AUTHORIZED_CALLERS = new String[]{
+-            SUBSTRATUM_PACKAGE,
+-            "masquerade.substratum"
+-    };
+-
+-    @Override
+-    public void onReceive(Context context, Intent intent) {
+-        if (!isCallerAuthorized(intent)) {
+-            Log.d("Masquerade", "Caller not authorized");
+-            return;
+-        }
+-        Log.d("Masquerade",
+-                "BroadcastReceiver has accepted Substratum's commands and is running now...");
+-        Root.requestRootAccess();
+-
+-        if (intent.getStringExtra("substratum-check") != null) {
+-            if (intent.getStringExtra("substratum-check").equals("masquerade-ball")) {
+-                Intent runCommand = new Intent();
+-                runCommand.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+-                runCommand.setAction("projekt.substratum.MASQUERADE_BALL");
+-                runCommand.putExtra("substratum-check", "masquerade-ball");
+-                context.sendBroadcast(runCommand);
+-                Log.d("Masquerade", "BroadcastReceiver was triggered to check for system " +
+-                        "integrity and service activation.");
+-            }
+-        } else if (intent.getStringArrayListExtra("pm-uninstall") != null) {
+-            new Uninstaller().uninstall(intent, "pm-uninstall", false,
+-                    intent.getBooleanExtra("restart_systemui", false));
+-        } else if (intent.getStringArrayListExtra("pm-uninstall-specific") != null) {
+-            new Uninstaller().uninstall(intent, "pm-uninstall-specific", true,
+-                    intent.getBooleanExtra("restart_systemui", false));
+-        } else if (intent.getStringArrayListExtra("icon-handler") != null) {
+-            String icon_pack_name = intent.getStringArrayListExtra("icon-handler").get(0);
+-            String main_delay = intent.getStringArrayListExtra("icon-handler").get(2);
+-            String delay_one = intent.getStringArrayListExtra("icon-handler").get(3);
+-            String delay_two = intent.getStringArrayListExtra("icon-handler").get(4);
+-            final String bypass = intent.getStringArrayListExtra("icon-handler").get(5);
+-            if (bypass == null) {
+-                if (intent.getStringArrayListExtra("icon-handler").get(1).contains("pm") ||
+-                        intent.getStringArrayListExtra("icon-handler").get(1).contains("om") ||
+-                        intent.getStringArrayListExtra("icon-handler").get(1).contains("overlay")) {
+-                    Log.d("Masquerade", "Running command: \"" +
+-                            intent.getStringArrayListExtra("icon-handler").get(1) + "\"");
+-                    Root.runCommand(intent.getStringArrayListExtra("icon-handler").get(1));
+-                }
+-            }
+-            final Context mContext = context;
+-            final String icon_pack = icon_pack_name;
+-            final String delay_one_time = delay_one;
+-            final String delay_two_time = delay_two;
+-            final Handler handle = new Handler();
+-            handle.postDelayed(new Runnable() {
+-                @Override
+-                public void run() {
+-                    new IconPackApplicator().apply(
+-                            mContext, icon_pack, delay_one_time, delay_two_time, bypass == null);
+-                }
+-            }, Integer.parseInt(main_delay));
+-        } else if (intent.getStringExtra("om-commands") != null) {
+-            if (intent.getStringExtra("om-commands").contains("pm") ||
+-                    intent.getStringExtra("om-commands").contains("om") ||
+-                    intent.getStringExtra("om-commands").contains("overlay")) {
+-                Log.d("Masquerade", "Running command: \"" +
+-                        intent.getStringExtra("om-commands") + "\"");
+-                Root.runCommand(intent.getStringExtra("om-commands"));
+-            }
+-        }
+-    }
+-
+-    private boolean isCallerAuthorized(Intent intent) {
+-        PendingIntent token = null;
+-        try {
+-            token = intent.getParcelableExtra(MASQUERADE_TOKEN);
+-        } catch (Exception e) {
+-            Log.d("Masquerade", "Attempt to start service without a token, unauthorized");
+-        }
+-        if (token == null) {
+-            return false;
+-        }
+-        // SECOND: we got a token, validate originating package
+-        // if not in our white list, return null
+-        String callingPackage = token.getCreatorPackage();
+-        boolean isValidPackage = false;
+-        for (int i = 0; i < AUTHORIZED_CALLERS.length; i++) {
+-            if (TextUtils.equals(callingPackage, AUTHORIZED_CALLERS[i])) {
+-                Log.d("Masquerade", callingPackage
+-                        + " is an authorized calling package, next validate calling package perms");
+-                isValidPackage = true;
+-                break;
+-            }
+-        }
+-        if (!isValidPackage) {
+-            Log.d("Masquerade", callingPackage
+-                    + " is not an authorized calling package");
+-            return false;
+-        }
+-        return true;
+-    }
+-}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/util/IconPackApplicator.java b/app/src/main/java/masquerade/substratum/util/IconPackApplicator.java
+deleted file mode 100644
+index fb5d682..0000000
+--- a/app/src/main/java/masquerade/substratum/util/IconPackApplicator.java
++++ /dev/null
+@@ -1,136 +0,0 @@
+-package masquerade.substratum.util;
+-
+-import android.content.Context;
+-import android.content.Intent;
+-import android.content.pm.PackageManager;
+-import android.content.res.Resources;
+-import android.os.Handler;
+-import android.util.Log;
+-import android.widget.Toast;
+-
+-import java.util.Locale;
+-
+-class IconPackApplicator {
+-
+-    private Context mContext;
+-    private String iconPackName;
+-    private String toast_text = null;
+-    private int delayOne, delayTwo;
+-    private Boolean bypass;
+-
+-    private static void grantPermission(final String packager, final String permission) {
+-        Root.runCommand("pm grant " + packager + " " + permission);
+-    }
+-
+-    void apply(Context mContext, String iconPackName, String delayOne, String delayTwo,
+-               Boolean bypass) {
+-        this.mContext = mContext;
+-        this.iconPackName = iconPackName;
+-        this.delayOne = Integer.parseInt(delayOne);
+-        this.delayTwo = Integer.parseInt(delayTwo);
+-        this.bypass = bypass;
+-        iconInjector();
+-    }
+-
+-    private boolean checkChangeConfigurationPermissions() {
+-        String permission = "android.permission.CHANGE_CONFIGURATION";
+-        int res = mContext.checkCallingOrSelfPermission(permission);
+-        return (res == PackageManager.PERMISSION_GRANTED);
+-    }
+-
+-    private void iconInjector() {
+-        if (!checkChangeConfigurationPermissions()) {
+-            Log.e("Masquerade", "Masquerade was not granted " +
+-                    "CHANGE_CONFIGURATION permissions, allowing now...");
+-            grantPermission("masquerade.substratum",
+-                    "android.permission.CHANGE_CONFIGURATION");
+-        } else {
+-            Log.d("Masquerade",
+-                    "Masquerade already granted CHANGE_CONFIGURATION permissions!");
+-        }
+-        try {
+-            // Move home, since this is where we want our config code affected
+-            Intent i = new Intent(Intent.ACTION_MAIN);
+-            i.addCategory(Intent.CATEGORY_HOME);
+-            mContext.startActivity(i);
+-
+-            // Take a fragment of memory to remember what the user's default config is
+-            final Locale current_locale = mContext.getResources().getConfiguration().locale;
+-            Locale to_be_changed = Locale.JAPAN;
+-            // There are definitely Japanese locale users using our app, so we should take
+-            // account for these people and switch to Chinese for 2 seconds.
+-            if (current_locale == Locale.JAPAN) {
+-                to_be_changed = Locale.CHINA;
+-            }
+-            final Locale changer = to_be_changed;
+-
+-            // Reflect back to framework and cause the language to change, we need this!
+-            Class<?> activityManagerNative = Class.forName("android.app.ActivityManagerNative");
+-            final Object am = activityManagerNative.getMethod("getDefault").invoke
+-                    (activityManagerNative);
+-            final Object config = am.getClass().getMethod("getConfiguration").invoke(am);
+-
+-            // Sniff Substratum's Resources
+-            try {
+-                Context otherContext = mContext.createPackageContext("projekt.substratum", 0);
+-                Resources resources = otherContext.getResources();
+-                if (bypass) {
+-                    int toast = resources.getIdentifier("studio_applied_toast", "string",
+-                            "projekt.substratum");
+-                    toast_text = String.format(resources.getString(toast), iconPackName);
+-                } else {
+-                    int toast = resources.getIdentifier("studio_configuration_changed", "string",
+-                            "projekt.substratum");
+-                    toast_text = resources.getString(toast);
+-                }
+-            } catch (Exception e) {
+-                // Suppress warning
+-            }
+-
+-            // First window refresh to kick the change on the home screen
+-            final Handler handle = new Handler();
+-            handle.postDelayed(new Runnable() {
+-                @Override
+-                public void run() {
+-                    try {
+-                        config.getClass().getDeclaredField(
+-                                "locale").set(config, changer);
+-                        config.getClass().getDeclaredField(
+-                                "userSetLocale").setBoolean(config, true);
+-
+-                        am.getClass().getMethod("updateConfiguration",
+-                                android.content.res.Configuration.class).invoke(am, config);
+-
+-                        // Change the locale back to pre-icon pack application
+-                        final Handler handler = new Handler();
+-                        handler.postDelayed(new Runnable() {
+-                            @Override
+-                            public void run() {
+-                                try {
+-                                    config.getClass().getDeclaredField("locale").set(
+-                                            config, current_locale);
+-                                    config.getClass().getDeclaredField("userSetLocale")
+-                                            .setBoolean(config, true);
+-
+-                                    am.getClass().getMethod("updateConfiguration",
+-                                            android.content.res.Configuration
+-                                                    .class).invoke(am, config);
+-
+-                                    if (toast_text != null)
+-                                        Toast.makeText(
+-                                                mContext, toast_text, Toast.LENGTH_SHORT).show();
+-                                } catch (Exception e) {
+-                                    // Suppress warning
+-                                }
+-                            }
+-                        }, delayTwo); // 2 second delay for Home refresh
+-                    } catch (Exception e) {
+-                        // Suppress warning
+-                    }
+-                }
+-            }, delayOne); // 1 second delay for Home refresh
+-        } catch (Exception e) {
+-            e.printStackTrace();
+-        }
+-    }
+-}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/util/ReadOverlaysFile.java b/app/src/main/java/masquerade/substratum/util/ReadOverlaysFile.java
+deleted file mode 100644
+index 2909ea9..0000000
+--- a/app/src/main/java/masquerade/substratum/util/ReadOverlaysFile.java
++++ /dev/null
+@@ -1,46 +0,0 @@
+-package masquerade.substratum.util;
+-
+-import android.os.Environment;
+-
+-import java.io.BufferedReader;
+-import java.io.File;
+-import java.io.FileReader;
+-import java.io.IOException;
+-import java.util.ArrayList;
+-import java.util.List;
+-
+-public class ReadOverlaysFile {
+-
+-    public static List<String> main(String argv[]) {
+-
+-        File current_overlays = new File(Environment
+-                .getExternalStorageDirectory().getAbsolutePath() +
+-                "/.substratum/current_overlays.xml");
+-        if (current_overlays.exists()) {
+-            Root.runCommand("rm " + Environment
+-                    .getExternalStorageDirectory().getAbsolutePath() +
+-                    "/.substratum/current_overlays.xml");
+-        }
+-        Root.runCommand("cp /data/system/overlays.xml " +
+-                Environment
+-                        .getExternalStorageDirectory().getAbsolutePath() +
+-                "/.substratum/current_overlays.xml");
+-
+-        File file = new File(argv[0]);
+-        int state_count = Integer.parseInt(argv[1]);
+-
+-        List<String> list = new ArrayList<>();
+-
+-        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
+-            for (String line; (line = br.readLine()) != null; ) {
+-                if (line.contains("state=\"" + state_count + "\"")) {
+-                    String[] split = line.substring(21).split("\\s+");
+-                    list.add(split[0].substring(1, split[0].length() - 1));
+-                }
+-            }
+-        } catch (IOException ioe) {
+-            // Exception
+-        }
+-        return list;
+-    }
+-}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/util/Root.java b/app/src/main/java/masquerade/substratum/util/Root.java
+deleted file mode 100644
+index 7c80720..0000000
+--- a/app/src/main/java/masquerade/substratum/util/Root.java
++++ /dev/null
+@@ -1,83 +0,0 @@
+-package masquerade.substratum.util;
+-
+-import java.io.BufferedReader;
+-import java.io.BufferedWriter;
+-import java.io.IOException;
+-import java.io.InputStreamReader;
+-import java.io.OutputStreamWriter;
+-
+-public class Root {
+-
+-    private static SU su;
+-
+-    public static boolean requestRootAccess() {
+-        SU su = getSU();
+-        su.runCommand("echo /testRoot/");
+-        return !su.denied;
+-    }
+-
+-    public static String runCommand(String command) {
+-        return getSU().runCommand(command);
+-    }
+-
+-    private static SU getSU() {
+-        if (su == null) su = new SU();
+-        else if (su.closed || su.denied) su = new SU();
+-        return su;
+-    }
+-
+-    private static class SU {
+-
+-        private Process process;
+-        private BufferedWriter bufferedWriter;
+-        private BufferedReader bufferedReader;
+-        private boolean closed;
+-        private boolean denied;
+-        private boolean firstTry;
+-
+-        SU() {
+-            try {
+-                firstTry = true;
+-                process = Runtime.getRuntime().exec("su");
+-                bufferedWriter = new BufferedWriter(new OutputStreamWriter(process
+-                        .getOutputStream()));
+-                bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream
+-                        ()));
+-            } catch (IOException e) {
+-                denied = true;
+-                closed = true;
+-            }
+-        }
+-
+-        synchronized String runCommand(final String command) {
+-            try {
+-                StringBuilder sb = new StringBuilder();
+-                String callback = "/shellCallback/";
+-                bufferedWriter.write(command + "\necho " + callback + "\n");
+-                bufferedWriter.flush();
+-
+-                int i;
+-                char[] buffer = new char[256];
+-                while (true) {
+-                    sb.append(buffer, 0, bufferedReader.read(buffer));
+-                    if ((i = sb.indexOf(callback)) > -1) {
+-                        sb.delete(i, i + callback.length());
+-                        break;
+-                    }
+-                }
+-                firstTry = false;
+-                return sb.toString().trim();
+-            } catch (IOException e) {
+-                closed = true;
+-                e.printStackTrace();
+-                if (firstTry) denied = true;
+-            } catch (ArrayIndexOutOfBoundsException e) {
+-                denied = true;
+-            } catch (Exception e) {
+-                e.printStackTrace();
+-                denied = true;
+-            }
+-            return null;
+-        }
+-    }
+-}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/util/Uninstaller.java b/app/src/main/java/masquerade/substratum/util/Uninstaller.java
+deleted file mode 100644
+index 4b1b056..0000000
+--- a/app/src/main/java/masquerade/substratum/util/Uninstaller.java
++++ /dev/null
+@@ -1,107 +0,0 @@
+-package masquerade.substratum.util;
+-
+-import android.content.Intent;
+-import android.os.AsyncTask;
+-import android.os.Environment;
+-import android.util.Log;
+-
+-import java.util.ArrayList;
+-import java.util.List;
+-
+-class Uninstaller {
+-
+-    private Intent mIntent;
+-    private String uninstallString;
+-    private boolean specific;
+-    private boolean restartSystemUI;
+-
+-    void uninstall(Intent mIntent, String uninstallString,
+-                   boolean specific, boolean restartSystemUI) {
+-        this.mIntent = mIntent;
+-        this.uninstallString = uninstallString;
+-        this.specific = specific;
+-        this.restartSystemUI = restartSystemUI;
+-        new UninstallAsync().execute("");
+-    }
+-
+-    private class UninstallAsync extends AsyncTask<String, Integer, String> {
+-
+-        @Override
+-        protected String doInBackground(String... sUrl) {
+-            uninstall_handler(mIntent, uninstallString, specific, restartSystemUI);
+-            return null;
+-        }
+-
+-        private void uninstall_handler(Intent intent, String inheritor,
+-                                       boolean specific, boolean restartSystemUI) {
+-            try {
+-                String final_commands_disable = "";
+-                String final_commands_uninstall = "";
+-
+-                Root.runCommand(
+-                        "pm grant masquerade.substratum android.permission.READ_EXTERNAL_STORAGE");
+-                Root.runCommand(
+-                        "pm grant masquerade.substratum android.permission.WRITE_EXTERNAL_STORAGE");
+-
+-                ArrayList<String> packages_to_uninstall =
+-                        new ArrayList<>(intent.getStringArrayListExtra(inheritor));
+-                String[] state5initial = {Environment.getExternalStorageDirectory()
+-                        .getAbsolutePath() +
+-                        "/.substratum/current_overlays.xml", "5"};
+-                List<String> state5overlays = ReadOverlaysFile.main(state5initial);
+-
+-                for (int i = 0; i < packages_to_uninstall.size(); i++) {
+-                    String current = packages_to_uninstall.get(i);
+-
+-                    Log.d("Masquerade", "Intent received to purge referendum package file \"" +
+-                            current + "\"");
+-                    if (state5overlays.contains(packages_to_uninstall.get(i))) {
+-                        Log.d("Masquerade", "Package file \"" + current +
+-                                "\" requires an overlay disable prior to uninstall...");
+-                        if (final_commands_disable.length() == 0) {
+-                            final_commands_disable = "om disable " + current;
+-                        } else {
+-                            final_commands_disable = final_commands_disable + " " + current;
+-                        }
+-
+-                        if (final_commands_uninstall.length() == 0) {
+-                            final_commands_uninstall = "pm uninstall " + current;
+-                        } else {
+-                            final_commands_uninstall = final_commands_uninstall +
+-                                    " && pm uninstall " + current;
+-                        }
+-                    } else {
+-                        Log.d("Masquerade", "\"" + current +
+-                                "\" has been redirected to the package manager in " +
+-                                "preparations of removal...");
+-                        Root.runCommand("pm uninstall " + current);
+-                    }
+-                }
+-
+-                if (final_commands_disable.length() > 0) {
+-                    Log.d("Masquerade", "Disable commands: " + final_commands_disable);
+-                    Root.runCommand(final_commands_disable);
+-                }
+-                if (final_commands_uninstall.length() > 0) {
+-                    Log.d("Masquerade", "Uninstall commands: " + final_commands_uninstall);
+-                    Root.runCommand(final_commands_uninstall);
+-                }
+-
+-                if (restartSystemUI) {
+-                    Root.runCommand("pkill com.android.systemui");
+-                }
+-
+-                if (!specific) {
+-                    // Clear the resource idmapping files generated by OMS
+-                    Log.d("Masquerade", "Cleaning up resource-cache directory...");
+-                    Root.runCommand("rm /data/resource-cache/*");
+-                    // Now clear the persistent overlays database
+-                    Log.d("Masquerade", "Finalizing clean up of persistent overlays database...");
+-                    Root.runCommand("rm -rf /data/system/overlays.xml");
+-                }
+-            } catch (Exception e) {
+-                e.printStackTrace();
+-            }
+-        }
+-    }
+-}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/utils/IOUtils.java b/app/src/main/java/masquerade/substratum/utils/IOUtils.java
+new file mode 100644
+index 0000000..092c58c
+--- /dev/null
++++ b/app/src/main/java/masquerade/substratum/utils/IOUtils.java
+@@ -0,0 +1,170 @@
++
++package masquerade.substratum.utils;
++
++import java.io.BufferedInputStream;
++import java.io.BufferedOutputStream;
++import java.io.File;
++import java.io.FileInputStream;
++import java.io.FileNotFoundException;
++import java.io.FileOutputStream;
++import java.io.InputStream;
++import java.io.OutputStream;
++import java.util.zip.ZipEntry;
++import java.util.zip.ZipInputStream;
++
++import android.os.FileUtils;
++
++public class IOUtils {
++    public static final String SYSTEM_THEME_PATH = "/data/system/theme";
++    public static final String SYSTEM_THEME_FONT_PATH = SYSTEM_THEME_PATH + File.separator
++            + "fonts";
++    public static final String SYSTEM_THEME_AUDIO_PATH = SYSTEM_THEME_PATH + File.separator
++            + "audio";
++    public static final String SYSTEM_THEME_RINGTONE_PATH = SYSTEM_THEME_AUDIO_PATH
++            + File.separator + "ringtones";
++    public static final String SYSTEM_THEME_NOTIFICATION_PATH = SYSTEM_THEME_AUDIO_PATH
++            + File.separator + "notifications";
++    public static final String SYSTEM_THEME_ALARM_PATH = SYSTEM_THEME_AUDIO_PATH
++            + File.separator + "alarms";
++    public static final String SYSTEM_THEME_UI_SOUNDS_PATH = SYSTEM_THEME_AUDIO_PATH
++            + File.separator + "ui";
++    public static final String SYSTEM_THEME_BOOTANIMATION_PATH = SYSTEM_THEME_PATH + File.separator
++            + "bootanimation.zip";
++
++    public static boolean dirExists(String dirPath) {
++        final File dir = new File(dirPath);
++        return dir.exists() && dir.isDirectory();
++    }
++
++    public static void createDirIfNotExists(String dirPath) {
++        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);
++            }
++        }
++    }
++
++    public static void createFontDirIfNotExists() {
++        createDirIfNotExists(SYSTEM_THEME_FONT_PATH);
++    }
++
++    public static void createAudioDirIfNotExists() {
++        createDirIfNotExists(SYSTEM_THEME_AUDIO_PATH);
++    }
++
++    public static void createRingtoneDirIfNotExists() {
++        createDirIfNotExists(SYSTEM_THEME_RINGTONE_PATH);
++    }
++
++    public static void createNotificationDirIfNotExists() {
++        createDirIfNotExists(SYSTEM_THEME_NOTIFICATION_PATH);
++    }
++
++    public static void createAlarmDirIfNotExists() {
++        createDirIfNotExists(SYSTEM_THEME_ALARM_PATH);
++    }
++
++    public static void createUiSoundsDirIfNotExists() {
++        createDirIfNotExists(SYSTEM_THEME_UI_SOUNDS_PATH);
++    }
++
++    public static void deleteThemedFonts() {
++        try {
++            deleteRecursive(new File(SYSTEM_THEME_FONT_PATH));
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++    
++    public static void deleteThemedAudio() {
++        try {
++            deleteRecursive(new File(SYSTEM_THEME_UI_SOUNDS_PATH));
++            deleteRecursive(new File(SYSTEM_THEME_RINGTONE_PATH));
++            deleteRecursive(new File(SYSTEM_THEME_NOTIFICATION_PATH));
++            deleteRecursive(new File(SYSTEM_THEME_ALARM_PATH));
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    public static void copyFolder(String source, String dest) {
++        File dir = new File(source);
++        File[] files = dir.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));
++            } catch (Exception e) {
++                e.printStackTrace();
++            }
++        }
++    }
++
++    public static void unzip(String source, String destination) {
++        try (ZipInputStream inputStream = new ZipInputStream(
++                new BufferedInputStream(new FileInputStream(source)))) {
++            ZipEntry zipEntry;
++            int count;
++            byte[] buffer = new byte[8192];
++            while ((zipEntry = inputStream.getNextEntry()) != null) {
++                File file = new File(destination, zipEntry.getName());
++                File dir = zipEntry.isDirectory() ? file : file.getParentFile();
++                if (!dir.isDirectory() && !dir.mkdirs())
++                    throw new FileNotFoundException("Failed to ensure directory: " +
++                            dir.getAbsolutePath());
++                if (zipEntry.isDirectory())
++                    continue;
++                try (FileOutputStream outputStream = new FileOutputStream(file)) {
++                    while ((count = inputStream.read(buffer)) != -1)
++                        outputStream.write(buffer, 0, count);
++                }
++            }
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    public static void bufferedCopy(String source, String dest) {
++        try {
++            bufferedCopy(new FileInputStream(source), new FileOutputStream(dest));
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    public static void bufferedCopy(File source, File dest) {
++        try {
++            bufferedCopy(new FileInputStream(source), new FileOutputStream(dest));
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    public static void bufferedCopy(InputStream source, OutputStream dest) {
++        try {
++            BufferedInputStream in = new BufferedInputStream(source);
++            BufferedOutputStream out = new BufferedOutputStream(dest);
++            byte[] buff = new byte[32 * 1024];
++            int len;
++            while ((len = in.read(buff)) > 0) {
++                out.write(buff, 0, len);
++            }
++            in.close();
++            out.close();
++        } catch (Exception e) {
++            e.printStackTrace();
++        }
++    }
++
++    public static void deleteRecursive(File fileOrDirectory) {
++        if (fileOrDirectory.isDirectory())
++            for (File child : fileOrDirectory.listFiles())
++                deleteRecursive(child);
++
++        fileOrDirectory.delete();
++    }
++
++}
+diff --git a/app/src/main/java/masquerade/substratum/utils/SoundUtils.java b/app/src/main/java/masquerade/substratum/utils/SoundUtils.java
+new file mode 100644
+index 0000000..72c6052
+--- /dev/null
++++ b/app/src/main/java/masquerade/substratum/utils/SoundUtils.java
+@@ -0,0 +1,210 @@
++
++package masquerade.substratum.utils;
++
++import java.io.File;
++import java.util.Arrays;
++
++import android.content.ContentResolver;
++import android.content.ContentValues;
++import android.content.Context;
++import android.database.Cursor;
++import android.media.RingtoneManager;
++import android.net.Uri;
++import android.os.SystemProperties;
++import android.os.UserHandle;
++import android.provider.MediaStore;
++import android.util.Log;
++import android.provider.Settings;
++
++public class SoundUtils {
++    private static final String SYSTEM_MEDIA_PATH = "/system/media/audio";
++    private static final String SYSTEM_ALARMS_PATH =
++            SYSTEM_MEDIA_PATH + File.separator + "alarms";
++    private static final String SYSTEM_RINGTONES_PATH =
++            SYSTEM_MEDIA_PATH + File.separator + "ringtones";
++    private static final String SYSTEM_NOTIFICATIONS_PATH =
++            SYSTEM_MEDIA_PATH + File.separator + "notifications";
++    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);
++    }
++
++    public static boolean setUISounds(ContentResolver resolver, String sound_name, String location) {
++        if (allowedUISound(sound_name)) {
++            updateGlobalSettings(resolver, sound_name, location);
++            return true;
++        }
++        return false;
++    }
++
++    public static void setDefaultUISounds(ContentResolver resolver, String sound_name,
++            String sound_file) {
++        updateGlobalSettings(resolver, sound_name, "/system/media/audio/ui/" + sound_file);
++    }
++
++    // This string array contains all the SystemUI acceptable sound files
++    public static Boolean allowedUISound(String targetValue) {
++        String[] allowed_themable = {
++                "lock_sound",
++                "unlock_sound",
++                "low_battery_sound"
++        };
++        return Arrays.asList(allowed_themable).contains(targetValue);
++    }
++
++    public static String getDefaultAudiblePath(int type) {
++        final String name;
++        final String path;
++        switch (type) {
++            case RingtoneManager.TYPE_ALARM:
++                name = SystemProperties.get("ro.config.alarm_alert");
++                path = name != null ? SYSTEM_ALARMS_PATH + File.separator + name : null;
++                break;
++            case RingtoneManager.TYPE_NOTIFICATION:
++                name = SystemProperties.get("ro.config.notification_sound");
++                path = name != null ? SYSTEM_NOTIFICATIONS_PATH + File.separator + name : null;
++                break;
++            case RingtoneManager.TYPE_RINGTONE:
++                name = SystemProperties.get("ro.config.ringtone");
++                path = name != null ? SYSTEM_RINGTONES_PATH + File.separator + name : null;
++                break;
++            default:
++                path = null;
++                break;
++        }
++        return path;
++    }
++
++    public static void clearAudibles(Context context, String audiblePath) {
++        final File audibleDir = new File(audiblePath);
++        if (audibleDir.exists() && audibleDir.isDirectory()) {
++            String[] files = audibleDir.list();
++            final ContentResolver resolver = context.getContentResolver();
++            for (String s : files) {
++                final String filePath = audiblePath + File.separator + s;
++                Uri uri = MediaStore.Audio.Media.getContentUriForPath(filePath);
++                resolver.delete(uri, MediaStore.MediaColumns.DATA + "=\""
++                        + filePath + "\"", null);
++                boolean deleted = (new File(filePath)).delete();
++                if (deleted)
++                    Log.e("SoundsHandler", "Database cleared");
++            }
++        }
++    }
++
++    public static boolean setAudible(Context context, File ringtone, File ringtoneCache, int type,
++            String name) {
++        final String path = ringtone.getAbsolutePath();
++        final String mimeType = name.endsWith(".ogg") ? "application/ogg" : "application/mp3";
++        ContentValues values = new ContentValues();
++        values.put(MediaStore.MediaColumns.DATA, path);
++        values.put(MediaStore.MediaColumns.TITLE, name);
++        values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
++        values.put(MediaStore.MediaColumns.SIZE, ringtoneCache.length());
++        values.put(MediaStore.Audio.Media.IS_RINGTONE, type == RingtoneManager.TYPE_RINGTONE);
++        values.put(MediaStore.Audio.Media.IS_NOTIFICATION,
++                type == RingtoneManager.TYPE_NOTIFICATION);
++        values.put(MediaStore.Audio.Media.IS_ALARM, type == RingtoneManager.TYPE_ALARM);
++        values.put(MediaStore.Audio.Media.IS_MUSIC, false);
++
++        Uri uri = MediaStore.Audio.Media.getContentUriForPath(path);
++        Uri newUri = null;
++        Cursor c = context.getContentResolver().query(uri,
++                new String[] {
++                    MediaStore.MediaColumns._ID
++                },
++                MediaStore.MediaColumns.DATA + "='" + path + "'",
++                null, null);
++        if (c != null && c.getCount() > 0) {
++            c.moveToFirst();
++            long id = c.getLong(0);
++            c.close();
++            newUri = Uri.withAppendedPath(Uri.parse(MEDIA_CONTENT_URI), "" + id);
++            context.getContentResolver().update(uri, values,
++                    MediaStore.MediaColumns._ID + "=" + id, null);
++        }
++        if (newUri == null)
++            newUri = context.getContentResolver().insert(uri, values);
++        try {
++            RingtoneManager.setActualDefaultRingtoneUri(context, type, newUri);
++        } catch (Exception e) {
++            return false;
++        }
++        return true;
++    }
++
++    public static boolean setUIAudible(Context context, File localized_ringtone,
++            File ringtone_file, int type, String name) {
++        final String path = ringtone_file.getAbsolutePath();
++
++        final String path_clone = "/system/media/audio/ui/" + name + ".ogg";
++
++        ContentValues values = new ContentValues();
++        values.put(MediaStore.MediaColumns.DATA, path);
++        values.put(MediaStore.MediaColumns.TITLE, name);
++        values.put(MediaStore.MediaColumns.MIME_TYPE, "application/ogg");
++        values.put(MediaStore.MediaColumns.SIZE, localized_ringtone.length());
++        values.put(MediaStore.Audio.Media.IS_RINGTONE, false);
++        values.put(MediaStore.Audio.Media.IS_NOTIFICATION, false);
++        values.put(MediaStore.Audio.Media.IS_ALARM, false);
++        values.put(MediaStore.Audio.Media.IS_MUSIC, true);
++
++        Uri uri = MediaStore.Audio.Media.getContentUriForPath(path);
++        Uri newUri = null;
++        Cursor c = context.getContentResolver().query(uri,
++                new String[] {
++                    MediaStore.MediaColumns._ID
++                },
++                MediaStore.MediaColumns.DATA + "='" + path_clone + "'",
++                null, null);
++        if (c != null && c.getCount() > 0) {
++            c.moveToFirst();
++            long id = c.getLong(0);
++            Log.e("ContentResolver", id + "");
++            c.close();
++            newUri = Uri.withAppendedPath(Uri.parse(MEDIA_CONTENT_URI), "" + id);
++            try {
++                context.getContentResolver().update(uri, values,
++                        MediaStore.MediaColumns._ID + "=" + id, null);
++            } catch (Exception e) {
++                Log.d("SoundsHandler", "The content provider does not need to be updated.");
++            }
++        }
++        if (newUri == null)
++            newUri = context.getContentResolver().insert(uri, values);
++        try {
++            RingtoneManager.setActualDefaultRingtoneUri(context, type, newUri);
++        } catch (Exception e) {
++            e.printStackTrace();
++            return false;
++        }
++        return true;
++    }
++
++    public static boolean setDefaultAudible(Context context, int type) {
++        final String audiblePath = getDefaultAudiblePath(type);
++        if (audiblePath != null) {
++            Uri uri = MediaStore.Audio.Media.getContentUriForPath(audiblePath);
++            Cursor c = context.getContentResolver().query(uri,
++                    new String[] {
++                        MediaStore.MediaColumns._ID
++                    },
++                    MediaStore.MediaColumns.DATA + "='" + audiblePath + "'",
++                    null, null);
++            if (c != null && c.getCount() > 0) {
++                c.moveToFirst();
++                long id = c.getLong(0);
++                c.close();
++                uri = Uri.withAppendedPath(
++                        Uri.parse(MEDIA_CONTENT_URI), "" + id);
++            }
++            if (uri != null)
++                RingtoneManager.setActualDefaultRingtoneUri(context, type, uri);
++        } else {
++            return false;
++        }
++        return true;
++    }
++
++}
+-- 
+2.11.1
+
diff --git a/patches/packages/apps/masquerade/0002-Finalize-masquerade-rootless-functionality-with-Subs.patch b/patches/packages/apps/masquerade/0002-Finalize-masquerade-rootless-functionality-with-Subs.patch
new file mode 100644 (file)
index 0000000..0388c7d
--- /dev/null
@@ -0,0 +1,952 @@
+From 30c65ea40cde9403e3a5bf7030b50b75a57061d5 Mon Sep 17 00:00:00 2001
+From: Ivan Iskandar <iiiiskandar14@gmail.com>
+Date: Mon, 6 Feb 2017 22:20:49 +0700
+Subject: [PATCH 2/3] Finalize masquerade rootless functionality with
+ Substratum [2/3]
+
+Change-Id: I69d3bfd6740dbbf8e864962aff764dd7707d1329
+---
+ .../masquerade/substratum/services/JobService.java | 605 ++++++++++++++++-----
+ .../java/masquerade/substratum/utils/IOUtils.java  |  45 +-
+ .../masquerade/substratum/utils/SoundUtils.java    |   2 +-
+ 3 files changed, 503 insertions(+), 149 deletions(-)
+
+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.IntentFilter;
+ 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.graphics.Typeface;
+ 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 class JobService extends Service {
+     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 class JobService extends Service {
+     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 @@ public class JobService extends Service {
+     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 @@ public class JobService extends Service {
+             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 @@ public class JobService extends Service {
+             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 @@ public class JobService extends Service {
+             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 @@ public class JobService extends Service {
+     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 @@ public class JobService extends Service {
+         }
+     }
+-    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 @@ public class JobService extends Service {
+     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 @@ public class JobService extends Service {
+         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 @@ public class JobService extends Service {
+             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 @@ public class JobService extends Service {
+             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 @@ public class JobService extends Service {
+             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 @@ public class JobService extends Service {
+             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 @@ public class JobService extends Service {
+                 "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 @@ public class JobService extends Service {
+             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 @@ public class JobService extends Service {
+                     synchronized (mJobQueue) {
+                         job = mJobQueue.get(0);
+                     }
+-                    if (job != null) {
++                    if (job != null && !mIsRunning) {
+                         job.run();
+                     }
+                     break;
+@@ -698,8 +792,10 @@ public class JobService extends Service {
+                     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 @@ public class JobService extends Service {
+         }
+     }
+-    private class Installer implements Runnable, IPackageInstallObserver2 {
++    private class Installer implements Runnable {
+         String mPath;
+         public Installer(String path) {
+@@ -843,27 +939,28 @@ public class JobService extends Service {
+         }
+         @Override
+-        public IBinder asBinder() {
+-            return null;
++        public void run() {
++            log("Installer - installing " + mPath);
++            PackageInstallObserver observer = new PackageInstallObserver(Installer.this);
++            install(mPath, observer);
+         }
++    }
+-        @Override
+-        public void onUserActionRequired(Intent intent) throws RemoteException {
+-            log("Installer - user action required callback with " + mPath);
++    private class PackageInstallObserver extends IPackageInstallObserver2.Stub {
++        Object mObject;
++
++        public PackageInstallObserver(Object _object) {
++            mObject = _object;
+         }
+-        @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);
++        public void onUserActionRequired(Intent intent) throws RemoteException {
++            log("Installer - user action required callback");
+         }
+-        @Override
+-        public void run() {
+-            log("Installer - installing " + mPath);
+-            install(mPath, this);
++        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 @@ public class JobService extends Service {
+         @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 @@ public class IOUtils {
+         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 @@ public class IOUtils {
+             e.printStackTrace();
+         }
+     }
+-    
++
+     public static void deleteThemedAudio() {
+         try {
+             deleteRecursive(new File(SYSTEM_THEME_UI_SOUNDS_PATH));
+@@ -89,20 +89,28 @@ public class IOUtils {
+         }
+     }
+-    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 @@ public class IOUtils {
+         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 @@ public class SoundUtils {
+     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) {
+-- 
+2.11.1
+
diff --git a/patches/packages/apps/masquerade/0003-Release-23-Introduce-the-rootless-solution-for-Masqu.patch b/patches/packages/apps/masquerade/0003-Release-23-Introduce-the-rootless-solution-for-Masqu.patch
new file mode 100644 (file)
index 0000000..7bdff15
--- /dev/null
@@ -0,0 +1,2644 @@
+From 63b419e89e730b3186fce7cb8a4b95e5b56f81fe Mon Sep 17 00:00:00 2001
+From: Nicholas Chum <nicholaschum@gmail.com>
+Date: Tue, 14 Feb 2017 00:56:09 -0500
+Subject: [PATCH 3/3] Release 23: Introduce the rootless solution for
+ Masquerade [3/3]
+
+Huge thanks to @bigrushdog, @iskandar1023 and @Surge1223 for all
+the hard work put into this.
+
+Clean-up commit by @nicholaschum
+- Introduce Java8 integration
+- Remove all the receivers
+- Remove all the unused imports, methods and permissions.
+- Reformat and optimize all Java files, drawables and XML files.
+- Make sure the project is Android Studio ready
+- Ensure that all permissions are declared Protected if in AS.
+
+To build this in Android Studio (without platform key), you must copy
+the "android.jar" built from your AOSP+OMS build environment to
+%AndroidSDK%/platforms/android-25/android.jar (back up your original
+copy). Then all you have to do is import masquerade as a gradle
+project to Android Studio and you are set!
+
+Change-Id: Ie350497ac5cfaf39c062235b90e3832f8072c7a8
+---
+ app/build.gradle                                   |  32 +-
+ app/src/main/AndroidManifest.xml                   |  49 ++-
+ .../substratum/receivers/BootReceiver.java         |  13 -
+ .../substratum/receivers/UninstallReceiver.java    |  47 ---
+ .../masquerade/substratum/services/JobService.java | 462 +++++++++------------
+ .../masquerade/substratum/services/MasqDemo.java   | 137 ------
+ .../java/masquerade/substratum/utils/IOUtils.java  |  37 +-
+ .../masquerade/substratum/utils/SoundUtils.java    |  72 ++--
+ app/src/main/res/mipmap-hdpi/ic_launcher.png       | Bin 0 -> 2240 bytes
+ .../main/res/mipmap-hdpi/substratum_masquerade.png | Bin 2246 -> 0 bytes
+ app/src/main/res/mipmap-mdpi/ic_launcher.png       | Bin 0 -> 1848 bytes
+ .../main/res/mipmap-mdpi/substratum_masquerade.png | Bin 2094 -> 0 bytes
+ app/src/main/res/mipmap-xhdpi/ic_launcher.png      | Bin 0 -> 2733 bytes
+ .../res/mipmap-xhdpi/substratum_masquerade.png     | Bin 2734 -> 0 bytes
+ app/src/main/res/mipmap-xxhdpi/ic_launcher.png     | Bin 0 -> 3874 bytes
+ .../res/mipmap-xxhdpi/substratum_masquerade.png    | Bin 3894 -> 0 bytes
+ app/src/main/res/mipmap-xxxhdpi/ic_launcher.png    | Bin 0 -> 4947 bytes
+ .../res/mipmap-xxxhdpi/substratum_masquerade.png   | Bin 4966 -> 0 bytes
+ app/src/main/res/values/styles.xml                 |   1 -
+ gradlew                                            |   0
+ gradlew.bat                                        | 180 ++++----
+ 21 files changed, 408 insertions(+), 622 deletions(-)
+ delete mode 100644 app/src/main/java/masquerade/substratum/receivers/BootReceiver.java
+ delete mode 100644 app/src/main/java/masquerade/substratum/receivers/UninstallReceiver.java
+ delete mode 100644 app/src/main/java/masquerade/substratum/services/MasqDemo.java
+ create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png
+ delete mode 100755 app/src/main/res/mipmap-hdpi/substratum_masquerade.png
+ create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png
+ delete mode 100755 app/src/main/res/mipmap-mdpi/substratum_masquerade.png
+ create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png
+ delete mode 100755 app/src/main/res/mipmap-xhdpi/substratum_masquerade.png
+ create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+ delete mode 100755 app/src/main/res/mipmap-xxhdpi/substratum_masquerade.png
+ create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+ delete mode 100755 app/src/main/res/mipmap-xxxhdpi/substratum_masquerade.png
+ mode change 100755 => 100644 gradlew
+
+diff --git a/app/build.gradle b/app/build.gradle
+index 265096a..a2694a8 100644
+--- a/app/build.gradle
++++ b/app/build.gradle
+@@ -1,7 +1,7 @@
+ apply plugin: 'com.android.application'
+ android {
+-    compileSdkVersion 23
++    compileSdkVersion 25
+     buildToolsVersion "23.0.3"
+     lintOptions {
+@@ -12,25 +12,41 @@ android {
+     applicationVariants.all { variant ->
+         variant.outputs.each { output ->
+             output.outputFile = new File(
+-                    output.outputFile.parent, "masquerade_beta_testers" + versionCode + ".apk")
++                    output.outputFile.parent, "masquerade_" + versionCode + ".apk")
+         }
+     }
+     defaultConfig {
+         applicationId "masquerade.substratum"
+-        minSdkVersion 21
+-        targetSdkVersion 23
+-        versionCode 20
+-        versionName "twenty - procyon"
++        minSdkVersion 25
++        targetSdkVersion 25
++        versionCode 23
++        versionName "twenty three"
++    }
++
++    defaultConfig {
++        jackOptions {
++            enabled true
++        }
++    }
++
++    compileOptions {
++        incremental true
++        sourceCompatibility JavaVersion.VERSION_1_8
++        targetCompatibility JavaVersion.VERSION_1_8
++    }
++
++    dexOptions {
++        javaMaxHeapSize '2048m'
+     }
+     buildTypes {
+         debug {
+-            minifyEnabled true
++            minifyEnabled false
+             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+         }
+         release {
+-            minifyEnabled true
++            minifyEnabled false
+             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+         }
+     }
+diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
+index 13db0f9..2554839 100644
+--- a/app/src/main/AndroidManifest.xml
++++ b/app/src/main/AndroidManifest.xml
+@@ -1,40 +1,39 @@
++<!--suppress AndroidUnknownAttribute -->
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+-          package="masquerade.substratum"
+-          android:versionCode="21"
+-          android:versionName="twentyone - something"
++          xmlns:tools="http://schemas.android.com/tools"
+           coreApp="true"
+-          android:sharedUserId="android.uid.system">
++          package="masquerade.substratum"
++          android:sharedUserId="android.uid.system"
++          android:versionCode="23"
++          android:versionName="twenty three"
++          tools:ignore="GradleOverrides">
+     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/>
+-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+-    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+-    <uses-permission android:name="android.permission.DELETE_PACKAGES"/>
+-    <uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
++    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
++    <uses-permission
++        android:name="android.permission.FORCE_STOP_PACKAGES"
++        tools:ignore="ProtectedPermissions"/>
++    <uses-permission
++        android:name="android.permission.DELETE_PACKAGES"
++        tools:ignore="ProtectedPermissions"/>
++    <uses-permission
++        android:name="android.permission.INSTALL_PACKAGES"
++        tools:ignore="ProtectedPermissions"/>
+     <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"/>
+     <uses-permission android:name="android.permission.MODIFY_OVERLAYS"/>
+     <application
+-        android:allowBackup="true"
+-        android:icon="@mipmap/substratum_masquerade"
++        android:allowBackup="false"
++        android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:supportsRtl="true"
+-        android:theme="@style/AppTheme">
+-        <receiver android:name=".receivers.BootReceiver">
+-            <intent-filter>
+-                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+-            </intent-filter>
+-        </receiver>
+-        <receiver android:name=".receivers.UninstallReceiver">
+-            <intent-filter>
+-                <action android:name="android.intent.action.PACKAGE_REMOVED"/>
+-                <data android:scheme="package"/>
+-            </intent-filter>
+-        </receiver>
+-        <service android:name=".services.JobService"
++        android:theme="@style/AppTheme"
++        tools:ignore="GoogleAppIndexingWarning">
++        <service
++            android:name=".services.JobService"
+             android:exported="true"
+-            android:permission="android.permission.MODIFY_OVERLAYS" />
++            android:permission="android.permission.MODIFY_OVERLAYS"/>
+     </application>
+ </manifest>
+diff --git a/app/src/main/java/masquerade/substratum/receivers/BootReceiver.java b/app/src/main/java/masquerade/substratum/receivers/BootReceiver.java
+deleted file mode 100644
+index ebc0c8b..0000000
+--- a/app/src/main/java/masquerade/substratum/receivers/BootReceiver.java
++++ /dev/null
+@@ -1,13 +0,0 @@
+-package masquerade.substratum.receivers;
+-
+-import android.content.BroadcastReceiver;
+-import android.content.Context;
+-import android.content.Intent;
+-
+-
+-public class BootReceiver extends BroadcastReceiver {
+-    @Override
+-    public void onReceive(Context context, Intent intent) {
+-        // do something
+-    }
+-}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/receivers/UninstallReceiver.java b/app/src/main/java/masquerade/substratum/receivers/UninstallReceiver.java
+deleted file mode 100644
+index b377e27..0000000
+--- a/app/src/main/java/masquerade/substratum/receivers/UninstallReceiver.java
++++ /dev/null
+@@ -1,47 +0,0 @@
+-package masquerade.substratum.receivers;
+-
+-import android.content.BroadcastReceiver;
+-import android.content.Context;
+-import android.content.Intent;
+-import android.net.Uri;
+-import android.os.AsyncTask;
+-import android.os.Environment;
+-import android.util.Log;
+-
+-import java.util.ArrayList;
+-import java.util.List;
+-
+-
+-public class UninstallReceiver extends BroadcastReceiver {
+-    @Override
+-    public void onReceive(Context context, Intent intent) {
+-        if ("android.intent.action.PACKAGE_REMOVED".equals(intent.getAction())) {
+-            Uri packageName = intent.getData();
+-            if (packageName.toString().substring(8).equals("projekt.substratum")) {
+-//                new performUninstalls().execute("");
+-            }
+-        }
+-    }
+-/*
+-    public class performUninstalls extends AsyncTask<String, Integer, String> {
+-        @Override
+-        protected String doInBackground(String... sUrl) {
+-            // Substratum was uninstalled, uninstall all remaining overlays
+-            Log.d("Masquerade",
+-                    "Substratum was uninstalled, so all remaining overlays will be removed...");
+-            String[] state4 = {Environment.getExternalStorageDirectory()
+-                    .getAbsolutePath() + "/.substratum/current_overlays.xml", "4"};
+-            String[] state5 = {Environment.getExternalStorageDirectory()
+-                    .getAbsolutePath() + "/.substratum/current_overlays.xml", "5"};
+-            List<String> state5overlays = ReadOverlaysFile.main(state5);
+-            ArrayList<String> all_overlays = new ArrayList<>(ReadOverlaysFile.main(state4));
+-            all_overlays.addAll(state5overlays);
+-            for (int i = 0; i < all_overlays.size(); i++) {
+-                Log.d("Masquerade", "Uninstalling overlay: " + all_overlays.get(i));
+-                Root.runCommand("pm uninstall " + all_overlays.get(i));
+-            }
+-            return null;
+-        }
+-    }
+-    */
+-}
+\ No newline at end of file
+diff --git a/app/src/main/java/masquerade/substratum/services/JobService.java b/app/src/main/java/masquerade/substratum/services/JobService.java
+index 20cf166..0ecd279 100644
+--- a/app/src/main/java/masquerade/substratum/services/JobService.java
++++ b/app/src/main/java/masquerade/substratum/services/JobService.java
+@@ -1,3 +1,23 @@
++/*
++ * Copyright (c) 2017 Project Substratum
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
++ * Also add information on how to contact you by electronic and paper mail.
++ *
++ */
++
+ package masquerade.substratum.services;
+ import android.app.ActivityManager;
+@@ -7,24 +27,19 @@ import android.app.Service;
+ import android.content.BroadcastReceiver;
+ import android.content.ComponentName;
+ import android.content.Context;
+-import android.content.IIntentReceiver;
+-import android.content.IIntentSender;
+ import android.content.Intent;
+ import android.content.IntentFilter;
+-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;
+ import android.content.pm.PackageManager;
+ import android.content.pm.PackageManager.NameNotFoundException;
+ import android.content.res.AssetManager;
+ import android.content.res.Configuration;
+ import android.graphics.Typeface;
+ import android.media.RingtoneManager;
+-import android.os.Binder;
+ import android.os.Bundle;
+ import android.os.Environment;
+ import android.os.FileUtils;
+@@ -42,42 +57,21 @@ import android.provider.Settings;
+ import android.text.TextUtils;
+ import android.util.Log;
+-import java.io.BufferedInputStream;
+-import java.io.BufferedOutputStream;
+ import java.io.File;
+-import java.io.FileInputStream;
+-import java.io.FileNotFoundException;
+ import java.io.FileOutputStream;
+-import java.io.IOException;
+ import java.io.InputStream;
+ import java.io.OutputStream;
+ import java.lang.reflect.Method;
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Locale;
+-import java.util.concurrent.SynchronousQueue;
+-import java.util.concurrent.TimeUnit;
+-import java.util.zip.ZipEntry;
+-import java.util.zip.ZipInputStream;
++import java.util.stream.Collectors;
+ import masquerade.substratum.utils.IOUtils;
+ import masquerade.substratum.utils.SoundUtils;
+-import com.android.internal.statusbar.IStatusBarService;
+-
+ public class JobService extends Service {
+-    private static final String TAG = JobService.class.getSimpleName();
+-    private static final boolean DEBUG = true;
+-
+-    private static final String MASQUERADE_TOKEN = "masquerade_token";
+-    private static final String SUBSTRATUM_PACKAGE = "projekt.substratum";
+-    private static final String[] AUTHORIZED_CALLERS = new String[] {
+-            SUBSTRATUM_PACKAGE,
+-            "masquerade.substratum"
+-    };
+-
+     public static final String INTENT_STATUS_CHANGED = "masquerade.substratum.STATUS_CHANGED";
+-
+     public static final String PRIMARY_COMMAND_KEY = "primary_command_key";
+     public static final String JOB_TIME_KEY = "job_time_key";
+     public static final String INSTALL_LIST_KEY = "install_list";
+@@ -109,20 +103,47 @@ public class JobService extends Service {
+     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 final String TAG = JobService.class.getSimpleName();
++    private static final boolean DEBUG = true;
++    private static final String MASQUERADE_TOKEN = "masquerade_token";
++    private static final String MASQUERADE_PACKAGE = "masquerade.substratum";
++    private static final String SUBSTRATUM_PACKAGE = "projekt.substratum";
++    private static final String[] AUTHORIZED_CALLERS = new String[]{
++            MASQUERADE_PACKAGE,
++            SUBSTRATUM_PACKAGE,
++    };
+     private static IOverlayManager mOMS;
+     private static IPackageManager mPM;
+-
+-    private HandlerThread mWorker;
++    private final List<Runnable> mJobQueue = new ArrayList<>(0);
+     private JobHandler mJobHandler;
+     private MainHandler mMainHandler;
+-    private final List<Runnable> mJobQueue = new ArrayList<>(0);
+     private long mLastJobTime;
+     private boolean mIsRunning;
++    private static IOverlayManager getOMS() {
++        if (mOMS == null) {
++            mOMS = IOverlayManager.Stub.asInterface(
++                    ServiceManager.getService("overlay"));
++        }
++        return mOMS;
++    }
++
++    private static IPackageManager getPM() {
++        if (mPM == null) {
++            mPM = IPackageManager.Stub.asInterface(
++                    ServiceManager.getService("package"));
++        }
++        return mPM;
++    }
++
++    public static void log(String msg) {
++        if (DEBUG) Log.e(TAG, msg);
++    }
++
+     @Override
+     public void onCreate() {
+-        mWorker = new HandlerThread("BackgroundWorker", Process.THREAD_PRIORITY_BACKGROUND);
++        HandlerThread mWorker = new HandlerThread("BackgroundWorker", Process
++                .THREAD_PRIORITY_BACKGROUND);
+         mWorker.start();
+         mJobHandler = new JobHandler(mWorker.getLooper());
+         mMainHandler = new MainHandler(Looper.getMainLooper());
+@@ -137,39 +158,33 @@ public class JobService extends Service {
+         }
+         // Don't run job if there is another running job
+-        mIsRunning = false;
+-        if (isProcessing()) mIsRunning = true;
++        mIsRunning = isProcessing();
+-        // filter out duplicate intents
++        // Filter out duplicate intents
+         long jobTime = intent.getLongExtra(JOB_TIME_KEY, 1);
+         if (jobTime == 1 || jobTime == mLastJobTime) {
+-            log("got empty jobtime or duplicate job time, aborting");
++            log("Received empty job time or duplicate job time, aborting");
+             return START_NOT_STICKY;
+         }
+         mLastJobTime = jobTime;
+-        // must have a primary command
++        // Must have a primary command
+         String command = intent.getStringExtra(PRIMARY_COMMAND_KEY);
+         if (TextUtils.isEmpty(command)) {
+-            log("Got empty primary command, aborting");
++            log("Received empty primary command, aborting");
+             return START_NOT_STICKY;
+         }
+-        // queue up the job
+-
++        // Queue up the job
+         List<Runnable> jobs_to_add = new ArrayList<>(0);
+-        log("Starting job with primary command " + command + " With job time " + jobTime);
++        log("Starting job with primary command \'" + command + "\', with job time: " + jobTime);
+         if (TextUtils.equals(command, COMMAND_VALUE_INSTALL)) {
+             List<String> paths = intent.getStringArrayListExtra(INSTALL_LIST_KEY);
+-            for (String path : paths) {
+-                jobs_to_add.add(new Installer(path));
+-            }
++            jobs_to_add.addAll(paths.stream().map(Installer::new).collect(Collectors.toList()));
+         } else if (TextUtils.equals(command, COMMAND_VALUE_UNINSTALL)) {
+             List<String> packages = intent.getStringArrayListExtra(UNINSTALL_LIST_KEY);
+-            for (String _package : packages) {
+-                jobs_to_add.add(new Remover(_package));
+-            }
++            jobs_to_add.addAll(packages.stream().map(Remover::new).collect(Collectors.toList()));
+             if (shouldRestartUi(packages)) jobs_to_add.add(new UiResetJob());
+         } else if (TextUtils.equals(command, COMMAND_VALUE_RESTART_UI)) {
+             jobs_to_add.add(new UiResetJob());
+@@ -194,15 +209,11 @@ public class JobService extends Service {
+             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));
+-            }
++            jobs_to_add.addAll(packages.stream().map(Enabler::new).collect(Collectors.toList()));
+             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));
+-            }
++            jobs_to_add.addAll(packages.stream().map(Disabler::new).collect(Collectors.toList()));
+             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);
+@@ -241,7 +252,6 @@ public class JobService extends Service {
+                 mJobHandler.sendEmptyMessage(JobHandler.MESSAGE_CHECK_QUEUE);
+             }
+         }
+-
+         return START_NOT_STICKY;
+     }
+@@ -252,35 +262,13 @@ public class JobService extends Service {
+     @Override
+     public void onDestroy() {
+-
+     }
+     private boolean isProcessing() {
+         return mJobQueue.size() > 0;
+     }
+-    private class LocalService extends Binder {
+-        public JobService getService() {
+-            return JobService.this;
+-        }
+-    }
+-
+-    private static IOverlayManager getOMS() {
+-        if (mOMS == null) {
+-            mOMS = IOverlayManager.Stub.asInterface(
+-                    ServiceManager.getService("overlay"));
+-        }
+-        return mOMS;
+-    }
+-
+-    private static IPackageManager getPM() {
+-        if (mPM == null) {
+-            mPM = IPackageManager.Stub.asInterface(
+-                    ServiceManager.getService("package"));
+-        }
+-        return mPM;
+-    }
+-
++    @SuppressWarnings("deprecation")
+     private void install(String path, IPackageInstallObserver2 observer) {
+         try {
+             getPM().installPackageAsUser(path, observer,
+@@ -292,6 +280,7 @@ public class JobService extends Service {
+         }
+     }
++    @SuppressWarnings("deprecation")
+     private void uninstall(String packageName, IPackageDeleteObserver observer) {
+         try {
+             getPM().deletePackageAsUser(packageName, observer, 0, UserHandle.USER_SYSTEM);
+@@ -315,7 +304,7 @@ public class JobService extends Service {
+             if (info != null) {
+                 enabled = info.isEnabled();
+             } else {
+-                log("info is null");
++                log("OverlayInfo is null.");
+             }
+         } catch (RemoteException e) {
+             e.printStackTrace();
+@@ -331,24 +320,25 @@ public class JobService extends Service {
+     }
+     private void copyFonts(String pid, String zipFileName) {
+-        // prepare local cache dir for font package assembly
++        // Prepare local cache dir for font package assembly
+         log("Copy Fonts - Package ID = " + pid + " filename = " + zipFileName);
+         File cacheDir = new File(getCacheDir(), "/FontCache/");
+         if (cacheDir.exists()) {
+             IOUtils.deleteRecursive(cacheDir);
+         }
+-        cacheDir.mkdir();
++        boolean created = cacheDir.mkdir();
++        if (!created) log("Could not create cache directory...");
+-        // copy system fonts into our cache dir
++        // Copy system fonts into our cache dir
+         IOUtils.copyFolder("/system/fonts", cacheDir.getAbsolutePath());
+-        // append zip to filename since it is probably removed
++        // Append zip to filename since it is probably removed
+         // for list presentation
+         if (!zipFileName.endsWith(".zip")) {
+             zipFileName = zipFileName + ".zip";
+         }
+-        // copy target themed fonts zip to our cache dir
++        // Copy target themed fonts zip to our cache dir
+         Context themeContext = getAppContext(pid);
+         AssetManager am = themeContext.getAssets();
+         try {
+@@ -360,12 +350,13 @@ public class JobService extends Service {
+             e.printStackTrace();
+         }
+-        // unzip new fonts and delete zip file, overwriting any system fonts
++        // Unzip new fonts and delete zip file, overwriting any system fonts
+         File fontZip = new File(getCacheDir(), "/FontCache/" + zipFileName);
+         IOUtils.unzip(fontZip.getAbsolutePath(), cacheDir.getAbsolutePath());
+-        fontZip.delete();
++        boolean deleted = fontZip.delete();
++        if (!deleted) log("Could not delete ZIP file...");
+-        // check if theme zip included a fonts.xml. If not, Substratum
++        // Check if theme zip included a fonts.xml. If not, Substratum
+         // is kind enough to provide one for us in it's assets
+         try {
+             File testConfig = new File(getCacheDir(), "/FontCache/" + "fonts.xml");
+@@ -380,12 +371,12 @@ public class JobService extends Service {
+             e.printStackTrace();
+         }
+-        // prepare system theme fonts folder and copy new fonts folder from our cache
++        // Prepare system theme fonts folder and copy new fonts folder from our cache
+         IOUtils.deleteThemedFonts();
+         IOUtils.createFontDirIfNotExists();
+         IOUtils.copyFolder(cacheDir.getAbsolutePath(), IOUtils.SYSTEM_THEME_FONT_PATH);
+-        // let system know it's time for a font change
++        // Let system know it's time for a font change
+         refreshFonts();
+     }
+@@ -395,7 +386,7 @@ public class JobService extends Service {
+     }
+     private void refreshFonts() {
+-        // set permissions on font files and config xml
++        // Set permissions on font files and config xml
+         File themeFonts = new File(IOUtils.SYSTEM_THEME_FONT_PATH);
+         if (themeFonts.exists()) {
+             // Set permissions
+@@ -404,7 +395,7 @@ public class JobService extends Service {
+                     FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH);
+         }
+-        // let system know it's time for a font change
++        // 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(
+@@ -414,21 +405,23 @@ public class JobService extends Service {
+     }
+     private void applyThemedSounds(String pid, String zipFileName) {
+-        // prepare local cache dir for font package assembly
+-        log("Copy sounds - Package ID = " + pid + " filename = " + zipFileName);
++        // Prepare local cache dir for font package assembly
++        log("CopySounds - Package ID = \'" + pid + "\'");
++        log("CopySounds - File name = \'" + zipFileName + "\'");
+         File cacheDir = new File(getCacheDir(), "/SoundsCache/");
+         if (cacheDir.exists()) {
+             IOUtils.deleteRecursive(cacheDir);
+         }
+-        cacheDir.mkdir();
++        boolean created = cacheDir.mkdir();
++        if (!created) log("Could not create cache directory...");
+-        // append zip to filename since it is probably removed
++        // Append zip to filename since it is probably removed
+         // for list presentation
+         if (!zipFileName.endsWith(".zip")) {
+             zipFileName = zipFileName + ".zip";
+         }
+-        // copy target themed sounds zip to our cache dir
++        // Copy target themed sounds zip to our cache dir
+         Context themeContext = getAppContext(pid);
+         AssetManager am = themeContext.getAssets();
+         try {
+@@ -440,10 +433,11 @@ public class JobService extends Service {
+             e.printStackTrace();
+         }
+-        // unzip new sounds and delete zip file
++        // Unzip new sounds and delete zip file
+         File soundsZip = new File(getCacheDir(), "/SoundsCache/" + zipFileName);
+         IOUtils.unzip(soundsZip.getAbsolutePath(), cacheDir.getAbsolutePath());
+-        soundsZip.delete();
++        boolean deleted = soundsZip.delete();
++        if (!deleted) log("Could not delete ZIP file...");
+         clearSounds(this);
+         IOUtils.createAudioDirIfNotExists();
+@@ -454,30 +448,38 @@ public class JobService extends Service {
+             File effect_tick_mp3 = new File(getCacheDir(), "/SoundsCache/ui/Effect_Tick.mp3");
+             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"));
++                IOUtils.bufferedCopy(effect_tick_ogg, new File(IOUtils
++                        .SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "Effect_Tick.ogg"));
+             } else if (effect_tick_mp3.exists()) {
+-                IOUtils.bufferedCopy(effect_tick_mp3, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "Effect_Tick.mp3"));
++                IOUtils.bufferedCopy(effect_tick_mp3, new File(IOUtils
++                        .SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "Effect_Tick.mp3"));
+             }
+             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"));
++                IOUtils.bufferedCopy(new_lock_ogg, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH +
++                        File.separator + "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"));
++                IOUtils.bufferedCopy(new_lock_mp3, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH +
++                        File.separator + "Lock.mp3"));
+             }
+             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"));
++                IOUtils.bufferedCopy(new_unlock_ogg, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH
++                        + File.separator + "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"));
++                IOUtils.bufferedCopy(new_unlock_mp3, new File(IOUtils.SYSTEM_THEME_UI_SOUNDS_PATH
++                        + File.separator + "Unlock.mp3"));
+             }
+             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"));
++                IOUtils.bufferedCopy(new_lowbattery_ogg, new File(IOUtils
++                        .SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "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"));
++                IOUtils.bufferedCopy(new_lowbattery_mp3, new File(IOUtils
++                        .SYSTEM_THEME_UI_SOUNDS_PATH + File.separator + "LowBattery.mp3"));
+             }
+         }
+         File alarmCache = new File(getCacheDir(), "/SoundsCache/alarms/");
+@@ -486,20 +488,26 @@ public class JobService extends Service {
+             File new_alarm_mp3 = new File(getCacheDir(), "/SoundsCache/alarms/alarm.mp3");
+             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"));
++                IOUtils.bufferedCopy(new_alarm_ogg, new File(IOUtils.SYSTEM_THEME_ALARM_PATH +
++                        File.separator + "alarm.ogg"));
+             } else if (new_alarm_mp3.exists()) {
+-                IOUtils.bufferedCopy(new_alarm_mp3, new File(IOUtils.SYSTEM_THEME_ALARM_PATH + File.separator + "alarm.mp3"));
++                IOUtils.bufferedCopy(new_alarm_mp3, new File(IOUtils.SYSTEM_THEME_ALARM_PATH +
++                        File.separator + "alarm.mp3"));
+             }
+         }
+         File notifCache = new File(getCacheDir(), "/SoundsCache/notifications/");
+         if (notifCache.exists() && notifCache.isDirectory()) {
+             IOUtils.createNotificationDirIfNotExists();
+-            File new_notif_mp3 = new File(getCacheDir(), "/SoundsCache/notifications/notification.mp3");
+-            File new_notif_ogg = new File(getCacheDir(), "/SoundsCache/notifications/notification.ogg");
++            File new_notif_mp3 = new File(getCacheDir(), "/SoundsCache/notifications/notification" +
++                    ".mp3");
++            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"));
++                IOUtils.bufferedCopy(new_notif_ogg, new File(IOUtils
++                        .SYSTEM_THEME_NOTIFICATION_PATH + File.separator + "notification.ogg"));
+             } else if (new_notif_mp3.exists()) {
+-                IOUtils.bufferedCopy(new_notif_mp3, new File(IOUtils.SYSTEM_THEME_NOTIFICATION_PATH + File.separator + "notification.mp3"));
++                IOUtils.bufferedCopy(new_notif_mp3, new File(IOUtils
++                        .SYSTEM_THEME_NOTIFICATION_PATH + File.separator + "notification.mp3"));
+             }
+         }
+         File ringtoneCache = new File(getCacheDir(), "/SoundsCache/ringtones/");
+@@ -508,28 +516,30 @@ public class JobService extends Service {
+             File new_ring_mp3 = new File(getCacheDir(), "/SoundsCache/ringtones/ringtone.mp3");
+             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"));
++                IOUtils.bufferedCopy(new_ring_ogg, new File(IOUtils.SYSTEM_THEME_RINGTONE_PATH +
++                        File.separator + "ringtone.ogg"));
+             } else if (new_ring_mp3.exists()) {
+-                IOUtils.bufferedCopy(new_ring_mp3, new File(IOUtils.SYSTEM_THEME_RINGTONE_PATH + File.separator + "ringtone.mp3"));
++                IOUtils.bufferedCopy(new_ring_mp3, new File(IOUtils.SYSTEM_THEME_RINGTONE_PATH +
++                        File.separator + "ringtone.mp3"));
+             }
+         }
+-        // let system know it's time for a sound change
++        // Let system know it's time for a sound change
+         refreshSounds();
+     }
+     private void clearSounds(Context ctx) {
+         IOUtils.deleteThemedAudio();
+-        SoundUtils.setDefaultAudible(JobService.this, RingtoneManager.TYPE_ALARM);
+-        SoundUtils.setDefaultAudible(JobService.this, RingtoneManager.TYPE_NOTIFICATION);
+-        SoundUtils.setDefaultAudible(JobService.this, RingtoneManager.TYPE_RINGTONE);
++        SoundUtils.setDefaultAudible(ctx, RingtoneManager.TYPE_ALARM);
++        SoundUtils.setDefaultAudible(ctx, RingtoneManager.TYPE_NOTIFICATION);
++        SoundUtils.setDefaultAudible(ctx, RingtoneManager.TYPE_RINGTONE);
+         SoundUtils.setDefaultUISounds(getContentResolver(), "lock_sound", "Lock.ogg");
+         SoundUtils.setDefaultUISounds(getContentResolver(), "unlock_sound", "Unlock.ogg");
+         SoundUtils.setDefaultUISounds(getContentResolver(), "low_battery_sound",
+                 "LowBattery.ogg");
+     }
+-    private void refreshSounds () {
++    private void refreshSounds() {
+         File soundsDir = new File(IOUtils.SYSTEM_THEME_AUDIO_PATH);
+         if (soundsDir.exists()) {
+             // Set permissions
+@@ -644,13 +654,15 @@ public class JobService extends Service {
+             }
+         }
+     }
++
+     private void copyBootAnimation(String fileName) {
+         try {
+             clearBootAnimation();
+             File source = new File(fileName);
+             File dest = new File(IOUtils.SYSTEM_THEME_BOOTANIMATION_PATH);
+             IOUtils.bufferedCopy(source, dest);
+-            source.delete();
++            boolean deleted = source.delete();
++            if (!deleted) log("Could not delete source file...");
+             IOUtils.setPermissions(dest,
+                     FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH);
+         } catch (Exception e) {
+@@ -662,21 +674,25 @@ public class JobService extends Service {
+         try {
+             File f = new File(IOUtils.SYSTEM_THEME_BOOTANIMATION_PATH);
+             if (f.exists()) {
+-                f.delete();
++                boolean deleted = f.delete();
++                if (!deleted) log("Could not delete themed boot animation...");
+             }
+         } catch (Exception e) {
+             e.printStackTrace();
+         }
+     }
++    @SuppressWarnings({"unchecked", "ConfusingArgumentToVarargsMethod"})
+     private void restartUi() {
+         try {
+             ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+             Class ActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
+             Method getDefault = ActivityManagerNative.getDeclaredMethod("getDefault", null);
+             Object amn = getDefault.invoke(null, null);
+-            Method killApplicationProcess = amn.getClass().getDeclaredMethod("killApplicationProcess", String.class, int.class);
+-            stopService(new Intent().setComponent(new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService")));
++            Method killApplicationProcess = amn.getClass().getDeclaredMethod
++                    ("killApplicationProcess", String.class, int.class);
++            stopService(new Intent().setComponent(new ComponentName("com.android.systemui", "com" +
++                    ".android.systemui.SystemUIService")));
+             am.killBackgroundProcesses("com.android.systemui");
+             for (ActivityManager.RunningAppProcessInfo app : am.getRunningAppProcesses()) {
+                 if ("com.android.systemui".equals(app.processName)) {
+@@ -689,15 +705,6 @@ public class JobService extends Service {
+         }
+     }
+-    private void killPackage(String packageName) {
+-        try {
+-            ActivityManagerNative.getDefault().forceStopPackage(packageName,
+-                    UserHandle.USER_SYSTEM);
+-        } catch (RemoteException e) {
+-            e.printStackTrace();
+-        }
+-    }
+-
+     private Context getSubsContext() {
+         return getAppContext(SUBSTRATUM_PACKAGE);
+     }
+@@ -713,45 +720,38 @@ public class JobService extends Service {
+         return ctx;
+     }
+-    public static void log(String msg) {
+-        if (DEBUG) {
+-            Log.e(TAG, msg);
+-        }
+-    }
+-
+     private boolean isCallerAuthorized(Intent intent) {
+         PendingIntent token = null;
+         try {
+-            token = (PendingIntent) intent.getParcelableExtra(MASQUERADE_TOKEN);
++            token = intent.getParcelableExtra(MASQUERADE_TOKEN);
+         } catch (Exception e) {
+-            log("Attempt to start serivce without a token, unauthorized");
+-        }
+-        if (token == null) {
+-            return false;
++            log("Attempting to start service without a token - unauthorized!");
+         }
+-        // SECOND: we got a token, validate originating package
+-        // if not in our whitelist, return null
++        if (token == null) return false;
++        // SECOND: We got a token, validate originating package
++        // if not in our white list, return null
+         String callingPackage = token.getCreatorPackage();
+         boolean isValidPackage = false;
+-        for (int i = 0; i < AUTHORIZED_CALLERS.length; i++) {
+-            if (TextUtils.equals(callingPackage, AUTHORIZED_CALLERS[i])) {
+-                log(callingPackage
+-                        + " is an authorized calling package, next validate calling package perms");
++        for (String AUTHORIZED_CALLER : AUTHORIZED_CALLERS) {
++            if (TextUtils.equals(callingPackage, AUTHORIZED_CALLER)) {
++                log("\'" + callingPackage
++                        + "\' is an authorized calling package, validating calling package " +
++                        "permissions...");
+                 isValidPackage = true;
+                 break;
+             }
+         }
+         if (!isValidPackage) {
+-            log(callingPackage + " is not an authorized calling package");
++            log("\'" + callingPackage + "\' is not an authorized calling package.");
+             return false;
+         }
+         return true;
+     }
+     private class MainHandler extends Handler {
+-        public static final int MSG_JOB_QUEUE_EMPTY = 1;
++        static final int MSG_JOB_QUEUE_EMPTY = 1;
+-        public MainHandler(Looper looper) {
++        MainHandler(Looper looper) {
+             super(looper);
+         }
+@@ -771,7 +771,7 @@ public class JobService extends Service {
+         private static final int MESSAGE_CHECK_QUEUE = 1;
+         private static final int MESSAGE_DEQUEUE = 2;
+-        public JobHandler(Looper looper) {
++        JobHandler(Looper looper) {
+             super(looper);
+         }
+@@ -783,9 +783,7 @@ public class JobService extends Service {
+                     synchronized (mJobQueue) {
+                         job = mJobQueue.get(0);
+                     }
+-                    if (job != null && !mIsRunning) {
+-                        job.run();
+-                    }
++                    if (job != null && !mIsRunning) job.run();
+                     break;
+                 case MESSAGE_DEQUEUE:
+                     Runnable toRemove = (Runnable) msg.obj;
+@@ -796,7 +794,7 @@ public class JobService extends Service {
+                             this.sendEmptyMessage(MESSAGE_CHECK_QUEUE);
+                         } else {
+                             mIsRunning = false;
+-                            log("Job queue empty! All done");
++                            log("Job queue is empty. All done!");
+                             mMainHandler.sendEmptyMessage(MainHandler.MSG_JOB_QUEUE_EMPTY);
+                         }
+                     }
+@@ -808,23 +806,6 @@ public class JobService extends Service {
+         }
+     }
+-    private class StopPackageJob implements Runnable {
+-        String mPackage;
+-
+-        public void StopPackageJob(String _package) {
+-            mPackage = _package;
+-        }
+-
+-        @Override
+-        public void run() {
+-            killPackage(mPackage);
+-            log("Killed package " + mPackage);
+-            Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+-                    StopPackageJob.this);
+-            mJobHandler.sendMessage(message);
+-        }
+-    }
+-
+     private class UiResetJob implements Runnable {
+         @Override
+         public void run() {
+@@ -841,7 +822,7 @@ public class JobService extends Service {
+         String mPid;
+         String mFileName;
+-        public FontsJob(String pid, String fileName) {
++        FontsJob(String pid, String fileName) {
+             if (pid == null) {
+                 mClear = true;
+             } else {
+@@ -853,10 +834,10 @@ public class JobService extends Service {
+         @Override
+         public void run() {
+             if (mClear) {
+-                log("Resetting system font");
++                log("Restoring system font...");
+                 clearFonts();
+             } else {
+-                log("Setting theme font");
++                log("Configuring theme font...");
+                 copyFonts(mPid, mFileName);
+             }
+             Intent intent = new Intent(INTENT_STATUS_CHANGED);
+@@ -873,7 +854,7 @@ public class JobService extends Service {
+         String mPid;
+         String mFileName;
+-        public SoundsJob(String pid, String fileName) {
++        SoundsJob(String pid, String fileName) {
+             if (pid == null) {
+                 mClear = true;
+             } else {
+@@ -885,10 +866,10 @@ public class JobService extends Service {
+         @Override
+         public void run() {
+             if (mClear) {
+-                log("Resetting system sounds");
++                log("Restoring system sounds...");
+                 clearSounds(JobService.this);
+             } else {
+-                log("Setting theme sounds");
++                log("Configuring theme sounds...");
+                 applyThemedSounds(mPid, mFileName);
+             }
+             Intent intent = new Intent(INTENT_STATUS_CHANGED);
+@@ -901,14 +882,14 @@ public class JobService extends Service {
+     }
+     private class BootAnimationJob implements Runnable {
+-        String mFileName;
+         final boolean mClear;
++        String mFileName;
+-        public BootAnimationJob(boolean clear) {
+-            mClear = true;
++        BootAnimationJob(boolean clear) {
++            mClear = clear;
+         }
+-        public BootAnimationJob(String fileName) {
++        BootAnimationJob(String fileName) {
+             mFileName = fileName;
+             mClear = false;
+         }
+@@ -916,10 +897,10 @@ public class JobService extends Service {
+         @Override
+         public void run() {
+             if (mClear) {
+-                log("Resetting system boot animation");
++                log("Restoring system boot animation...");
+                 clearBootAnimation();
+             } else {
+-                log("Setting themed boot animation");
++                log("Configuring themed boot animation...");
+                 copyBootAnimation(mFileName);
+             }
+             Intent intent = new Intent(INTENT_STATUS_CHANGED);
+@@ -934,13 +915,13 @@ public class JobService extends Service {
+     private class Installer implements Runnable {
+         String mPath;
+-        public Installer(String path) {
++        Installer(String path) {
+             mPath = path;
+         }
+         @Override
+         public void run() {
+-            log("Installer - installing " + mPath);
++            log("Installer - installing \'" + mPath + "\'...");
+             PackageInstallObserver observer = new PackageInstallObserver(Installer.this);
+             install(mPath, observer);
+         }
+@@ -949,7 +930,7 @@ public class JobService extends Service {
+     private class PackageInstallObserver extends IPackageInstallObserver2.Stub {
+         Object mObject;
+-        public PackageInstallObserver(Object _object) {
++        PackageInstallObserver(Object _object) {
+             mObject = _object;
+         }
+@@ -957,8 +938,9 @@ public class JobService extends Service {
+             log("Installer - user action required callback");
+         }
+-        public void onPackageInstalled(String packageName, int returnCode, String msg, Bundle extras) {
+-            log("Installer - successfully installed " + packageName);
++        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);
+         }
+@@ -967,20 +949,19 @@ public class JobService extends Service {
+     private class Remover implements Runnable {
+         String mPackage;
+-        public Remover(String _package) {
++        Remover(String _package) {
+             mPackage = _package;
+         }
+         @Override
+         public void run() {
+-
+             // TODO: Fix isOverlayEnabled function, for now it's causing NPE
+             if (isOverlayEnabled(mPackage)) {
+-                log("Remover - disabling overlay for " + mPackage);
++                log("Remover - disabling overlay for \'" + mPackage + "\'...");
+                 switchOverlay(mPackage, false);
+             }
+-            log("Remover - uninstalling " + mPackage);
++            log("Remover - uninstalling \'" + mPackage + "\'...");
+             PackageDeleteObserver observer = new PackageDeleteObserver(Remover.this);
+             uninstall(mPackage, observer);
+         }
+@@ -989,12 +970,12 @@ public class JobService extends Service {
+     private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
+         Object mObject;
+-        public PackageDeleteObserver(Object _object) {
++        PackageDeleteObserver(Object _object) {
+             mObject = _object;
+         }
+         public void packageDeleted(String packageName, int returnCode) {
+-            log("Remover - successfully removed " + packageName);
++            log("Remover - successfully removed \'" + packageName + "\'");
+             Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE, mObject);
+             mJobHandler.sendMessage(message);
+         }
+@@ -1003,13 +984,13 @@ public class JobService extends Service {
+     private class Enabler implements Runnable {
+         String mPackage;
+-        public Enabler(String _package) {
++        Enabler(String _package) {
+             mPackage = _package;
+         }
+         @Override
+         public void run() {
+-            log("Enabler - enabling overlay for " + mPackage);
++            log("Enabler - enabling overlay for \'" + mPackage + "\'...");
+             switchOverlay(mPackage, true);
+             Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                     Enabler.this);
+@@ -1020,13 +1001,13 @@ public class JobService extends Service {
+     private class Disabler implements Runnable {
+         String mPackage;
+-        public Disabler(String _package) {
++        Disabler(String _package) {
+             mPackage = _package;
+         }
+         @Override
+         public void run() {
+-            log("Disabler - disabling overlay for " + mPackage);
++            log("Disabler - disabling overlay for \'" + mPackage + "\'...");
+             switchOverlay(mPackage, false);
+             Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                     Disabler.this);
+@@ -1037,18 +1018,18 @@ public class JobService extends Service {
+     private class PriorityJob implements Runnable {
+         List<String> mPackages;
+-        public PriorityJob(List<String> _packages) {
++        PriorityJob(List<String> _packages) {
+             mPackages = _packages;
+         }
+         @Override
+         public void run() {
+-            log("PriorityJob - processing priority changes");
++            log("PriorityJob - processing priority changes...");
+             try {
+                 int size = mPackages.size();
+-                for (int i = 0; i < size-1; i++) {
++                for (int i = 0; i < size - 1; i++) {
+                     String parentName = mPackages.get(i);
+-                    String packageName = mPackages.get(i+1);
++                    String packageName = mPackages.get(i + 1);
+                     getOMS().setPriority(packageName, parentName, UserHandle.USER_SYSTEM);
+                 }
+             } catch (RemoteException e) {
+@@ -1064,14 +1045,14 @@ public class JobService extends Service {
+         String mSource;
+         String mDestination;
+-        public CopyJob(String _source, String _destination) {
++        CopyJob(String _source, String _destination) {
+             mSource = _source;
+             mDestination = _destination;
+         }
+         @Override
+         public void run() {
+-            log("CopyJob - copying " + mSource + " to " + mDestination);
++            log("CopyJob - copying \'" + mSource + "\' to \'" + mDestination + "\'...");
+             File sourceFile = new File(mSource);
+             if (sourceFile.exists()) {
+                 if (sourceFile.isFile()) {
+@@ -1080,7 +1061,7 @@ public class JobService extends Service {
+                     IOUtils.copyFolder(mSource, mDestination);
+                 }
+             } else {
+-                log("CopyJob - " + mSource + " is not exist! aborting...");
++                log("CopyJob - \'" + mSource + "\' does not exist, aborting...");
+             }
+             Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                     CopyJob.this);
+@@ -1092,14 +1073,14 @@ public class JobService extends Service {
+         String mSource;
+         String mDestination;
+-        public MoveJob(String _source, String _destination) {
++        MoveJob(String _source, String _destination) {
+             mSource = _source;
+             mDestination = _destination;
+         }
+         @Override
+         public void run() {
+-            log("MoveJob - moving " + mSource + " to " + mDestination);
++            log("MoveJob - moving \'" + mSource + "\' to \'" + mDestination + "\'...");
+             File sourceFile = new File(mSource);
+             if (sourceFile.exists()) {
+                 if (sourceFile.isFile()) {
+@@ -1109,7 +1090,7 @@ public class JobService extends Service {
+                 }
+                 IOUtils.deleteRecursive(sourceFile);
+             } else {
+-                log("MoveJob - " + mSource + " is not exist! aborting...");
++                log("MoveJob - \'" + mSource + "\' does not exist, aborting...");
+             }
+             Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                     MoveJob.this);
+@@ -1120,18 +1101,18 @@ public class JobService extends Service {
+     private class DeleteJob implements Runnable {
+         String mFileOrDirectory;
+-        public DeleteJob(String _directory) {
++        DeleteJob(String _directory) {
+             mFileOrDirectory = _directory;
+         }
+         @Override
+         public void run() {
+-            log("DeleteJob - deleting " + mFileOrDirectory);
++            log("DeleteJob - deleting \'" + mFileOrDirectory + "\'...");
+             File file = new File(mFileOrDirectory);
+             if (file.exists()) {
+                 IOUtils.deleteRecursive(file);
+             } else {
+-                log("DeleteJob - " + mFileOrDirectory + " is already deleted!");
++                log("DeleteJob - \'" + mFileOrDirectory + "\' is already deleted.");
+             }
+             Message message = mJobHandler.obtainMessage(JobHandler.MESSAGE_DEQUEUE,
+                     DeleteJob.this);
+@@ -1144,7 +1125,7 @@ public class JobService extends Service {
+         List<String> mToBeDisabled;
+         List<String> mToBeEnabled;
+-        public ProfileJob(String _name, List<String> _toBeDisabled, List<String> _toBeEnabled) {
++        ProfileJob(String _name, List<String> _toBeDisabled, List<String> _toBeEnabled) {
+             mProfileName = _name;
+             mToBeDisabled = _toBeDisabled;
+             mToBeEnabled = _toBeEnabled;
+@@ -1152,7 +1133,7 @@ public class JobService extends Service {
+         @Override
+         public void run() {
+-            boolean restartUi = false;
++            boolean restartUi;
+             log("Applying profile...");
+             // Need to restart SystemUI?
+@@ -1201,7 +1182,7 @@ public class JobService extends Service {
+             // Restart SystemUI when needed
+             if (restartUi) {
+-                synchronized(mJobQueue) {
++                synchronized (mJobQueue) {
+                     mJobQueue.add(new UiResetJob());
+                 }
+             }
+@@ -1214,7 +1195,6 @@ public class JobService extends Service {
+     private class LocaleChanger extends BroadcastReceiver implements Runnable {
+         private boolean mIsRegistered;
+-        private boolean mDoRestore;
+         private Context mContext;
+         private Handler mHandler;
+         private Locale mCurrentLocale;
+@@ -1229,12 +1209,7 @@ public class JobService extends Service {
+             Intent i = new Intent(Intent.ACTION_MAIN);
+             i.addCategory(Intent.CATEGORY_HOME);
+             mContext.startActivity(i);
+-            mHandler.postDelayed(new Runnable() {
+-                @Override
+-                public void run() {
+-                    spoofLocale();
+-                }
+-            }, 500);
++            mHandler.postDelayed(this::spoofLocale, 500);
+         }
+         private void register() {
+@@ -1253,9 +1228,10 @@ public class JobService extends Service {
+             }
+         }
++        @SuppressWarnings("deprecation")
+         private void spoofLocale() {
+             Configuration config;
+-            log("LocaleChanger - spoofing locale for configuation change shim");
++            log("LocaleChanger - spoofing locale for configuration change shim...");
+             try {
+                 register();
+                 config = ActivityManagerNative.getDefault().getConfiguration();
+@@ -1269,13 +1245,12 @@ public class JobService extends Service {
+                 ActivityManagerNative.getDefault().updateConfiguration(config);
+             } catch (RemoteException e) {
+                 e.printStackTrace();
+-                return;
+             }
+         }
+         private void restoreLocale() {
+             Configuration config;
+-            log("LocaleChanger - restoring original locale for configuation change shim");
++            log("LocaleChanger - restoring original locale for configuration change shim...");
+             try {
+                 unregister();
+                 config = ActivityManagerNative.getDefault().getConfiguration();
+@@ -1293,40 +1268,7 @@ public class JobService extends Service {
+         @Override
+         public void onReceive(Context context, Intent intent) {
+-            mHandler.postDelayed(new Runnable() {
+-                @Override
+-                public void run() {
+-                    restoreLocale();
+-                }
+-            }, 500);
+-        }
+-    }
+-
+-    private static class LocalIntentReceiver {
+-        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
+-
+-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+-            @Override
+-            public void send(int code, Intent intent, String resolvedType,
+-                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+-                try {
+-                    mResult.offer(intent, 5, TimeUnit.SECONDS);
+-                } catch (InterruptedException e) {
+-                    throw new RuntimeException(e);
+-                }
+-            }
+-        };
+-
+-        public IntentSender getIntentSender() {
+-            return new IntentSender((IIntentSender) mLocalSender);
+-        }
+-
+-        public Intent getResult() {
+-            try {
+-                return mResult.take();
+-            } catch (InterruptedException e) {
+-                throw new RuntimeException(e);
+-            }
++            mHandler.postDelayed(this::restoreLocale, 500);
+         }
+     }
+ }
+diff --git a/app/src/main/java/masquerade/substratum/services/MasqDemo.java b/app/src/main/java/masquerade/substratum/services/MasqDemo.java
+deleted file mode 100644
+index b00d6e0..0000000
+--- a/app/src/main/java/masquerade/substratum/services/MasqDemo.java
++++ /dev/null
+@@ -1,137 +0,0 @@
+-
+-package masquerade.substratum.services;
+-
+-import java.util.ArrayList;
+-import java.util.List;
+-
+-import android.app.PendingIntent;
+-import android.content.BroadcastReceiver;
+-import android.content.Context;
+-import android.content.Intent;
+-import android.text.TextUtils;
+-
+-public class MasqDemo {
+-    public static final String MASQUERADE_TOKEN = "masquerade_token";
+-    public static final String PRIMARY_COMMAND_KEY = "primary_command_key";
+-    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_PID_KEY = "bootanimation_pid";
+-    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 COMMAND_VALUE_INSTALL = "install";
+-    public static final String COMMAND_VALUE_UNINSTALL = "uninstall";
+-    public static final String COMMAND_VALUE_RESTART_UI = "restart_ui";
+-    public static final String COMMAND_VALUE_CONFIGURATION_SHIM = "configuration_shim";
+-    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 INTENT_STATUS_CHANGED = "masquerade.substratum.STATUS_CHANGED";
+-    public static final String COMMAND_VALUE_JOB_COMPLETE = "job_complete";
+-
+-    class MasqReceiver extends BroadcastReceiver {
+-        @Override
+-        public void onReceive(Context context, Intent intent) {
+-            if (TextUtils.equals(intent.getAction(), INTENT_STATUS_CHANGED)) {
+-                String command = intent.getStringExtra(PRIMARY_COMMAND_KEY);
+-                if (TextUtils.equals(command, COMMAND_VALUE_FONTS)) {
+-                    // update ui, dismiss progress dialog, etc
+-                } else if (TextUtils.equals(command, COMMAND_VALUE_BOOTANIMATION)) {
+-
+-                } else if (TextUtils.equals(command, COMMAND_VALUE_AUDIO)) {
+-
+-                } else if (TextUtils.equals(command, COMMAND_VALUE_JOB_COMPLETE)) {
+-
+-                }
+-            }
+-        }
+-    }
+-
+-    // demo code for building a base intent for JobService commands
+-    public static Intent getMasqIntent(Context ctx) {
+-        Intent intent = new Intent();
+-        intent.setClassName("masquerade.substratum", "masquerade.substratum.services.JobService");
+-        // Credit StackOverflow http://stackoverflow.com/a/28132098
+-        // Use dummy PendingIntent for service to validate caller at onBind
+-        PendingIntent pending = PendingIntent.getActivity(ctx, 0, new Intent(), 0);
+-        intent.putExtra(MASQUERADE_TOKEN, pending);
+-        intent.putExtra(JOB_TIME_KEY, System.currentTimeMillis());
+-        return intent;
+-    }
+-
+-    public static void install(Context context, ArrayList<String> overlay_apks) {
+-        // populate list however
+-        Intent masqIntent = getMasqIntent(context);
+-        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_INSTALL);
+-        masqIntent.putExtra(INSTALL_LIST_KEY, overlay_apks);
+-        context.startService(masqIntent);
+-    }
+-
+-    public static void uninstall(Context context, ArrayList<String> packages_to_remove) {
+-        Intent masqIntent = getMasqIntent(context);
+-        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_UNINSTALL);
+-        masqIntent.putExtra(UNINSTALL_LIST_KEY, packages_to_remove);
+-        // only need to set if true, will restart SystemUI when done processing packages
+-        masqIntent.putExtra(WITH_RESTART_UI_KEY, true);
+-        context.startService(masqIntent);
+-    }
+-
+-    public static void restartSystemUI(Context context) {
+-        Intent masqIntent = getMasqIntent(context);
+-        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_RESTART_UI);
+-        context.startService(masqIntent);
+-    }
+-
+-    public static void configurationChangeShim(Context context) {
+-        Intent masqIntent = getMasqIntent(context);
+-        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_CONFIGURATION_SHIM);
+-        context.startService(masqIntent);
+-    }
+-    
+-    public static void clearThemedBootAnimation(Context context) {
+-        applyThemedBootAnimation(context, null);
+-    }
+-    
+-    public static void applyThemedBootAnimation(Context context, String fileName) {
+-        Intent masqIntent = getMasqIntent(context);
+-        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_BOOTANIMATION);
+-        if (fileName != null) {
+-            masqIntent.putExtra(BOOTANIMATION_FILE_NAME, fileName);
+-        } else {
+-            // nothing. to reset to stock, just don't add PID and FILE
+-        }
+-        context.startService(masqIntent);
+-    }
+-    
+-    public static void clearThemedFont(Context context) {
+-        applyThemedFont(context, null, null);
+-    }
+-
+-    public static void applyThemedFont(Context context, String pid, String fileName) {
+-        Intent masqIntent = getMasqIntent(context);
+-        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_FONTS);
+-        if (pid != null) {
+-            masqIntent.putExtra(FONTS_PID, pid);
+-            masqIntent.putExtra(FONTS_FILENAME, fileName);
+-        }
+-        context.startService(masqIntent);
+-    }
+-
+-    public static void clearThemedSounds(Context context) {
+-        applyThemedSounds(context, null, null);
+-    }
+-
+-    public static void applyThemedSounds(Context context, String pid, String fileName) {
+-        Intent masqIntent = getMasqIntent(context);
+-        masqIntent.putExtra(PRIMARY_COMMAND_KEY, COMMAND_VALUE_AUDIO);
+-        if (pid != null) {
+-            masqIntent.putExtra(AUDIO_PID, pid);
+-            masqIntent.putExtra(AUDIO_FILENAME, fileName);
+-        }
+-        context.startService(masqIntent);
+-    }
+-}
+diff --git a/app/src/main/java/masquerade/substratum/utils/IOUtils.java b/app/src/main/java/masquerade/substratum/utils/IOUtils.java
+index 06b5260..2f697c6 100644
+--- a/app/src/main/java/masquerade/substratum/utils/IOUtils.java
++++ b/app/src/main/java/masquerade/substratum/utils/IOUtils.java
+@@ -1,6 +1,28 @@
++/*
++ * Copyright (c) 2017 Project Substratum
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
++ * Also add information on how to contact you by electronic and paper mail.
++ *
++ */
+ package masquerade.substratum.utils;
++import android.os.FileUtils;
++import android.util.Log;
++
+ import java.io.BufferedInputStream;
+ import java.io.BufferedOutputStream;
+ import java.io.File;
+@@ -12,8 +34,6 @@ import java.io.OutputStream;
+ import java.util.zip.ZipEntry;
+ import java.util.zip.ZipInputStream;
+-import android.os.FileUtils;
+-
+ public class IOUtils {
+     public static final String SYSTEM_THEME_PATH = "/data/system/theme";
+     public static final String SYSTEM_THEME_FONT_PATH = SYSTEM_THEME_PATH + File.separator
+@@ -31,12 +51,12 @@ public class IOUtils {
+     public static final String SYSTEM_THEME_BOOTANIMATION_PATH = SYSTEM_THEME_PATH + File.separator
+             + "bootanimation.zip";
+-    public static boolean dirExists(String dirPath) {
++    private static boolean dirExists(String dirPath) {
+         final File dir = new File(dirPath);
+         return dir.exists() && dir.isDirectory();
+     }
+-    public static void createDirIfNotExists(String dirPath) {
++    private static void createDirIfNotExists(String dirPath) {
+         if (!dirExists(dirPath)) {
+             File dir = new File(dirPath);
+             if (dir.mkdir()) {
+@@ -90,7 +110,10 @@ public class IOUtils {
+     }
+     public static void copyFolder(File source, File dest) {
+-        if (!dest.exists()) dest.mkdirs();
++        if (!dest.exists()) {
++            boolean created = dest.mkdirs();
++            if (!created) Log.e("CopyFolder", "Could not create destination folder...");
++        }
+         File[] files = source.listFiles();
+         for (File file : files) {
+             try {
+@@ -172,7 +195,9 @@ public class IOUtils {
+             for (File child : fileOrDirectory.listFiles())
+                 deleteRecursive(child);
+-        fileOrDirectory.delete();
++        boolean deleted = fileOrDirectory.delete();
++        if (!deleted) Log.e("DeleteRecursive", "Could not delete file or directory - \'" +
++                fileOrDirectory.getName() + "\'");
+     }
+     public static void setPermissions(File path, int permissions) {
+diff --git a/app/src/main/java/masquerade/substratum/utils/SoundUtils.java b/app/src/main/java/masquerade/substratum/utils/SoundUtils.java
+index dda38eb..73999ce 100644
+--- a/app/src/main/java/masquerade/substratum/utils/SoundUtils.java
++++ b/app/src/main/java/masquerade/substratum/utils/SoundUtils.java
+@@ -1,9 +1,25 @@
++/*
++ * Copyright (c) 2017 Project Substratum
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
++ * Also add information on how to contact you by electronic and paper mail.
++ *
++ */
+ package masquerade.substratum.utils;
+-import java.io.File;
+-import java.util.Arrays;
+-
+ import android.content.ContentResolver;
+ import android.content.ContentValues;
+ import android.content.Context;
+@@ -13,8 +29,11 @@ import android.net.Uri;
+ import android.os.SystemProperties;
+ import android.os.UserHandle;
+ import android.provider.MediaStore;
+-import android.util.Log;
+ import android.provider.Settings;
++import android.util.Log;
++
++import java.io.File;
++import java.util.Arrays;
+ public class SoundUtils {
+     private static final String SYSTEM_MEDIA_PATH = "/system/media/audio";
+@@ -26,11 +45,12 @@ public class SoundUtils {
+             SYSTEM_MEDIA_PATH + File.separator + "notifications";
+     private static final String MEDIA_CONTENT_URI = "content://media/internal/audio/media";
+-    public static void updateGlobalSettings(ContentResolver resolver, String uri, String val) {
++    private static void updateGlobalSettings(ContentResolver resolver, String uri, String val) {
+         Settings.Global.putStringForUser(resolver, uri, val, UserHandle.USER_SYSTEM);
+     }
+-    public static boolean setUISounds(ContentResolver resolver, String sound_name, String location) {
++    public static boolean setUISounds(ContentResolver resolver, String sound_name, String
++            location) {
+         if (allowedUISound(sound_name)) {
+             updateGlobalSettings(resolver, sound_name, location);
+             return true;
+@@ -39,12 +59,12 @@ public class SoundUtils {
+     }
+     public static void setDefaultUISounds(ContentResolver resolver, String sound_name,
+-            String sound_file) {
++                                          String sound_file) {
+         updateGlobalSettings(resolver, sound_name, "/system/media/audio/ui/" + sound_file);
+     }
+     // This string array contains all the SystemUI acceptable sound files
+-    public static Boolean allowedUISound(String targetValue) {
++    private static Boolean allowedUISound(String targetValue) {
+         String[] allowed_themable = {
+                 "lock_sound",
+                 "unlock_sound",
+@@ -53,7 +73,7 @@ public class SoundUtils {
+         return Arrays.asList(allowed_themable).contains(targetValue);
+     }
+-    public static String getDefaultAudiblePath(int type) {
++    private static String getDefaultAudiblePath(int type) {
+         final String name;
+         final String path;
+         switch (type) {
+@@ -76,25 +96,8 @@ public class SoundUtils {
+         return path;
+     }
+-    public static void clearAudibles(Context context, String audiblePath) {
+-        final File audibleDir = new File(audiblePath);
+-        if (audibleDir.exists() && audibleDir.isDirectory()) {
+-            String[] files = audibleDir.list();
+-            final ContentResolver resolver = context.getContentResolver();
+-            for (String s : files) {
+-                final String filePath = audiblePath + File.separator + s;
+-                Uri uri = MediaStore.Audio.Media.getContentUriForPath(filePath);
+-                resolver.delete(uri, MediaStore.MediaColumns.DATA + "=\""
+-                        + filePath + "\"", null);
+-                boolean deleted = (new File(filePath)).delete();
+-                if (deleted)
+-                    Log.e("SoundsHandler", "Database cleared");
+-            }
+-        }
+-    }
+-
+     public static boolean setAudible(Context context, File ringtone, File ringtoneCache, int type,
+-            String name) {
++                                     String name) {
+         final String path = ringtone.getAbsolutePath();
+         final String mimeType = name.endsWith(".ogg") ? "application/ogg" : "application/mp3";
+         ContentValues values = new ContentValues();
+@@ -111,8 +114,8 @@ public class SoundUtils {
+         Uri uri = MediaStore.Audio.Media.getContentUriForPath(path);
+         Uri newUri = null;
+         Cursor c = context.getContentResolver().query(uri,
+-                new String[] {
+-                    MediaStore.MediaColumns._ID
++                new String[]{
++                        MediaStore.MediaColumns._ID
+                 },
+                 MediaStore.MediaColumns.DATA + "='" + path + "'",
+                 null, null);
+@@ -135,7 +138,7 @@ public class SoundUtils {
+     }
+     public static boolean setUIAudible(Context context, File localized_ringtone,
+-            File ringtone_file, int type, String name) {
++                                       File ringtone_file, int type, String name) {
+         final String path = ringtone_file.getAbsolutePath();
+         final String path_clone = "/system/media/audio/ui/" + name + ".ogg";
+@@ -153,8 +156,8 @@ public class SoundUtils {
+         Uri uri = MediaStore.Audio.Media.getContentUriForPath(path);
+         Uri newUri = null;
+         Cursor c = context.getContentResolver().query(uri,
+-                new String[] {
+-                    MediaStore.MediaColumns._ID
++                new String[]{
++                        MediaStore.MediaColumns._ID
+                 },
+                 MediaStore.MediaColumns.DATA + "='" + path_clone + "'",
+                 null, null);
+@@ -187,8 +190,8 @@ public class SoundUtils {
+         if (audiblePath != null) {
+             Uri uri = MediaStore.Audio.Media.getContentUriForPath(audiblePath);
+             Cursor c = context.getContentResolver().query(uri,
+-                    new String[] {
+-                        MediaStore.MediaColumns._ID
++                    new String[]{
++                            MediaStore.MediaColumns._ID
+                     },
+                     MediaStore.MediaColumns.DATA + "='" + audiblePath + "'",
+                     null, null);
+@@ -206,5 +209,4 @@ public class SoundUtils {
+         }
+         return true;
+     }
+-
+ }
+diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..2588952b8172c791022bc815d725d1157d46d6f3
+GIT binary patch
+literal 2240
+zcma)-`9BkmAIIk^_5DzN3WZ@~MhUs*nqwPnwp@{7Ic7F<EHX#rOwl1K4Jpf9MI^_f
+z<eGC8a&JOPju|;R@O6CGfAD=iUhm`adc1#l|L}f1-ZvfXPfP8W-wyx)q%dfdv#_H7
+zhNPG<6ZDbc0D$PGBhJNUa&oevd4$8^2%9fozVP{cfk43Ja)r)~8#ky_YG5!8feAOW
+zNijlZ7$WHgh@9iF6o~d^4gGinM4q1Y6AnD^9XjSC2;T(uV1Ya;Ko@dBZn-Mw@^sHK
+zPPsl&w7oBfOqR9E(snFIU4CmH&pH!ZZWmsF3XL^5O98<w4}+DYl@7|n6v3{--TohN
+zlu+-W*S?otlSAvnsMYtqANRtky=K%dBl6nX+RDnxj~_plmX;P57x{Di>FH@McVcLG
+zY-ou4Wti8&`O@6l+t~cEs<x|=HCphj<Ieq>q+4~jZ?Ui4=?so)3W{oUC%<whz90rY
+zJ@1p_=uWqBNH<5{GdrDbYJ11T=C(OH6ON(7F*$I%M{w*D3mgNEeQJTrhhv#=Y=Jqp
+z2#zZ<!xo!iOU!Z4%<P_<*_E2wm6_SSFvGqu$CX*wms>hiSUFT!J60f^ULu_<ZColb
+zZWVS8WhitB4#jk{$#cgs&!HX=EpG<G?}b?1i!@42&`*xnPrPPwJrWj^sCOw%Cpzl{
+zjiC`zphhW$1eK|itJR6s5Z@{_LX|3^3hYw_@_wc4^-9UJ66jf}c(E4fS*L=3tK`$D
+z<k_O^#a8xi6H3XO4fJ9IJ=uz$Y=w)h$MBs%uWm)p9-#N2lFzV;@2C=it1Of+SB1z0
+z5xHPLt}2PAM&d!pJasY;>d(`l46Bg_Af!Ghxfe|2s1dtBgmy5YUDdZ;&9@yw>`?dX
+zfRZ{i$n9E`7KmR9n7~%?X%h<M)1XSI2NPIeLKTQyqe-gO@~<<X*1<wL5E0GJNp0SD
+zs)N!##xQt=4Lnx6G5T4Cmd_L-at;|IKwcG~Vi#@V7H#7e5my(jq6MZA0>iKctzf=J
+z03S->t5fFHDSU`OUyaOHCC`INb70au$Ztm7|C<_l3hXxl@*4&EY^9+xh1X_7bGARb
+zMo;sn(2I<q_Flo!X+pwMnKQGqQ}=}2&t&E^naqT|WKw|fQM`ezw!5AdL`nA0Kl>#m
+z#6(2^$$t?Ja__&11OOy-F(_-7DBk#pSf2nV+p)4czeL>PvV!rsejW>}!}M2v!IZ$;
+z9v-Y$tl%-at=(n=?u~voKC5vyKz)74ByGA;P>-m`&HWm=?Qr9L>dfROsaP<5nRBHb
+z2&Xq}@%&h<`B7h}y&~2tZhO^zU`ruIDqC!F6`=ii8Sk?7nx&{aF?MuyxBH=)BgB6F
+zoY{JN!JdTlh}09D-8DvU{GNld7w9LM3N;NL`{LKbr)bKD0Ahg6$=$0Fo%us=H*e%t
+zEXrqwO^@vA0qQ2$!zh_6yY;>Qe#K{TY4nlkLD$*nS3M}1jjLn#zQXz%mskt^#ZLXB
+zn09y3!n`lZ4(~juf^!M(@SA(Vk~1wg>wBbNza*cV1h|20l921<-2F(Gusc=;+u%*n
+zr*zcxdzWtNYyHR9d*WO}|A8D~P7{}Fupit~2b8yB8i+7tS<AkReYpDJs`>TqEYIgZ
+zqsGG*35K5E3Wa{h;R2j(@4G@r_jhA2_rz`I(&F9^*(VKrcHgp|UF(dQl)ToHHlMZ2
+zSou@N;7m8Un>we|>`;FC?<!=9#s9tr&YpVxqfY)?!x#-EP6;aK6}HaZEA(F~ij+*i
+z?Y7j4WsRPJ4j#5nljg+so(G^W0}eJd*OsSdhr|Q>W%dg6wO<?;xoS}|GM=MXT>(y$
+zMdXw?i{KM4(5y{E#_qmAn2voQIC)RKv{+t0yUO2z$wbl)ZY%e<C=?oPKN2&+9#W;Y
+zJ58@de!LYsfHBuqM3d!Bj7wLhiRXga6B$j=SPyA$p4;=oEeH0}Wc$P)ianS$4vc&Z
+zp6r_Z(<3n&(HngzAvB#P;+$fz3G*-|&&heki_Tzw#Qy0w9)#M8BO6a%KNUc*>ekCb
+zbxN#pOBz!mTko>(W|rs1N%1bPS~?~$R8q4S^6dZhjQ~mj3x4BsS#6{%{gV+^ZD!P{
+zpX8(j&46s0KQ;JQZQc3xG8Rz0z@k2MCniF%U1hGJmbw}4$3C-hc7vv`R${rH(q+BX
+zaI8Pt>A}R&<co`)ZpQ!-z~wih^I_f|U`825^ILzSLP?WEO~@lTqDA8D+nMSUHbpqQ
+z({+J6a>skCXXfQ{?s`plU6Anamy*Wo>wZU8Z=3?>iY!0KI)E-Jon}2tQ+T<62`Suj
+zQ{16MN4eC+kecH3l}=69{c8A(KmXIpuWO)?RydvyMThV+&_~%DjhwroMPjhXd0G45
+zf#DUw&=M5sT2ZT^6Bwz^%eiJC715Ucl`W9WFKu%oRjavIeM9RL?QF&AJ{d<8_|y_B
+zdFtJKXqDFcj~vCB|9<MjI~${wRSX>&aZUS9o?i0{*IY+_8a$?{NL2y^oQG|zd)a5L
+z+#+15p;c<*u*#WXjaA!(iqJ@G&F8janz3lY(ZYTGAFYf$Y{xfLk4O*8N?Xnc(qh&Q
+z-^XLvd=7I!UFU?qlpCE9xVfRJdh%s|0VCBD@_o7X*6oqqODJnS<u~tJhl*)+MiGxX
+zB~@}$>IZzM7771JSyC~ULAyN6_->9rq{t*wox?*6pgP&B1s&;_O4q%}u&N8A&7JSd
+z16$vqPxNfw>RX@9TvcDt>`bynpT7yqH!aEw+yTh-J>p-sg+s4{#~rCf%t@4t1|SdY
+ztZUHbvitCNwS0NkL&K_xPX|6bm3P%pqh`i#%)HQ<1@=zTv-hj9!UuMO##=Lr(|5%t
+z;&ZEAyNug3<sOKrbeKN3I)BQuel!U2U1{tv>GAFK<a2-U`3;HrQs}+m-Gd)ER)1eV
+M#>O61f$&cHA7ceLfB*mh
+
+literal 0
+HcmV?d00001
+
+diff --git a/app/src/main/res/mipmap-hdpi/substratum_masquerade.png b/app/src/main/res/mipmap-hdpi/substratum_masquerade.png
+deleted file mode 100755
+index aa94e2c00f99b9f2e2e1889c26955f85fa0932af..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 2246
+zcmV;%2s!tOP)<h;3K|Lk000e1NJLTq002k;002k`0{{R3VlzW^0008|P)t-sOlfg6
+zLYb0aoHj#{00000000000000000000000000J~0qo-k9A7ek#KM$bKN&pU3&HfI0;
+z0E8Am0002LB3sJJ%42W00002CAzLUjf8gNYt|3`FMTci`xxGkxtweHQY`1+hQ{Ug;
+z0001&E>Ip09?Csy0002r-`{g?v)S3%tx9#u%F3G{R<=!jqeOAdAzYgwSG>U2?Ck6+
+zH-``wH|px@G(w&=LzVyl030G!B`$I@L7j$%hCdS`X(2bCF;#1AbV5mWZf<VuM18^;
+zT<IZYI!dd<8(r8QV#6F>*&kx+CTZCpW9cGh>LqA9O04Q6XYECP>LX{_AY|-AeC<Yl
+zJ4>h79br34sm>i>)E;5zB4+C>ZR|C3$sAtoKzhd=U+g+|L`bLJA!STNqU}O_vLR<U
+zNT}gLcj!ZVJxilJOshUjp*=~KJV=o}OQOmoWIRiy)+A=nB4qFH@9OC4ydz@n?(V}K
+zUp-5p@$vCHM~^*Br94ci+9qeP99>sMp2HttJxZEBOrJqet~^MTvn69aOP#VLVm?l-
+z>L_YDMvSr}V#FX}!5?3;B4NHBUc((;!6jvlFM93m?Y1If#wKOCB4V{2Uc@0`N>8ig
+z<mAO7VmnKwa6pl<9$r{snY1ZpyB=ZMw#7Y3ndv5J+9YQ4^Yh3dVzMD%upM4nWUSfQ
+z-o+zhJ4>w7C1#@}X}BO<#U*6KA7I%dW<^hr<|b&_A!M?($VpeOmn(GD*4wckU&be9
+z+9hYIC2Zj$W!fib>L+R0B4u|rh~p$@!6IU_CS-s!gw1=OjFPlFN~)_SXIf06IYo<Y
+zc)w&niN3(ixHoXcENOQ)d*IgEWNe_-)!C3NVcMj;gE@szT9&}V&&EP_#Ux`zRItPz
+zU|MIfKTob}J&~U%aDOv_Rz#D-AYs-ZW4JVK&?jesGH*grui7YSLsGD_Y>k7A$HpjU
+zKu@m3V2rgaXp4%&z*2&2c)qZ<*rTY%#>(J=inx@^%X9z$07Z0CPE!DlgpCn03IYfN
+z0feHWqLGo21)>a*jf5VNqG^$hgyEiq7-%ejM&~8-gf?m?lx8@Ql9r`&E|%tv7Fvks
+zPBdJhZF*k>$<odM00lNlL_t(|0nF0_kSsYI$MN$GO7Fe1ZQCx}SGF5tTWj04cW-U;
+zY}>Z+>+Z}>x-)aux2#W+f0D!gCm9TX52L^Q(u0P-!8v$pkMBm^v47n;*XMTouG1O*
+z4hPbydhLP#Sh`as2c4i(Ime!JrO|lA5tp2y>cn)NEq5((F1|wr>90ET{cV(;J@-QM
+z!>`JHBKMI}B9Zt4y@B$iK1(|Hopg{s*EwglDNc57Zf@a270vZ<GC`Cxc+W-09ZPTM
+zvahx&sIai4<gMm<3Q8c^LA29)?@_8*>&{X;fJ*xMDk5+gNaZ|ox{h`X#{p6tL>#m*
+z`X+la+9~jBp)1r5k{kp|3(-!N7P<xuai|R;g`!8KaypNsAau-WpQFX{a#u~2%0VqH
+za}miwtdqk+a8NZWcU7sOmj1Wv_o2Z`+VQdw6gZ>@TvaItQ2%IzIC-znS5giOsU5Uc
+zqfnBA!W)1d{dP7Ogp_j3EChyjUO_I|AyByDYp2iG^WCg<U_Rw=EL2T71R5H00U}O#
+z*XM9J+>Rf--ho!XNpb`;$K9-j(mANHU54PaIUN$@cKqlydqpt@heH`4#%XNas#TzG
+z917(AKFe(7a&qFb6UYPsp&e9L*Dg8XFJ%YK_(dxp6a+2+Clf>%;t;5=xE4@{?4Uh=
+zRu0eefj|IGlNLJp17xwJaZu5_wNE_nBapMzFUI109xppVEp+_HO12TZg>j0Co_yLt
+zoXVL36Cgp7;ADhWk<D&VIVk#ahf{`}k55W3C=Q9|gNTI)gF>Ouh$ZO|sCewtml_%x
+zp52?2HY7ouA|1{`Ac%vOY!=EHj~17XJ?DSs#exDqaRjd{J;*>b0|(i9lMWzB`p{%D
+zWr>OdNS0WLX5gUiZhO)hCz2?3#`Yb2G=6xPg`iLj9AxWN9Q0b$<0*YAh8GP7!7+2z
+zG*F0P(8Pp2>7b~m^szYYa9k4$Q4AssvL_wn@zlJda;(-S22wM`LF@0O9JCAt5eI4C
+z(bf6+`CU_+D>6en>yVNTD%+u)^bl!kMa@VO<&>301i=i4KusB-HPp2UhH|E-ArS{H
+z^Bm={kc=RTA`Y@E4j{C_b1CP32KuEPyGFAj3ZgiUyE~+#3&7o;yKDQnMC^e0rKGr|
+zo#XDiz*oWP>AKZ+N8|Y~;P;)hij|zxlTk3jK^@_sha+>6>qsGHQ1PSRsntw&GMO*4
+zvp2|*Lr~Z$78PdT)c$!+29sum(~dxFKyiCA$`0zV<4m&cJFc?;R1}rVqk=(slOas9
+zZ5s@n_F6eq^cbYDlh5b7!a+Z61V;kx)ig~FGDu+u>FlIl+fHrtJq+z`pqQl+LsdTt
+z2X!;TNu_SWDJ9{^AvCV~7&J-_%If5xTX0IH<oEfZO2sNNgA`)mWV_^`2|!NYlQO8{
+zX7rdrZ-j$Pa?n`GEech<#(T^Z3W{)02M+b2L@fZtV)dS2&>Lb<m!0^t#TLcpGK$0?
+z1rC*FC;pHeDHQXvz=%fKN$cdGG1rMedt$*<b`o#cNq5LWKV4^0$Pf%72X)CoU*Ieb
+zA(a?3N(=%+J%h6(NCQK?fkWwW(XXT&+8c(91gAiVKo6G&)$8?l5~$UQa|E3UK?BnP
+z1*r*6Ff9pmA_#4{G*lM~rG=rE1p0pZWDweV<nquI8xjbN1e&~X?(Eh8wE4jB;J}p~
+z{&soo^xDrIL+1yNY}!Tywe`S}{*BA7{|F!b+~zF@HY~dXvY*ek%`0d*AD`9!1E~sy
+U8e-`#i2wiq07*qoM6N<$g6DV#^#A|>
+
+diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..bd2ae71130d8d3756ebac2cd39d3e29fcd4555b3
+GIT binary patch
+literal 1848
+zcmV-82gmq{P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm000L6Nkl<Zc-rmO
+z1C%3K6vpwjZ8gKSnT%~4k;YuxX2!N{E8Sz;c1y>+`_)ZelAe0Cm0GLL`Hk`as$P=5
+z?EmYJzb;?8bm`r@cki~~$5;I0;`i;__hMODSqaeDvuA_<q!E|z-Mjb2($dnBqM{;F
+z;Ky|I=+Ror?}6@hC0z4Y*|B5COCny#%gdvU8#lhAJK%e#=bjaH--|4BzDkPOmEWNW
+zH9n>R^}nYtn+&Ax9i~$2z~Z=83V&2f-y+r`nK!Dr!lPKonk8xKj&UuZSKp7&#k^v=
+zG(HlEEI{6addKLhWi{W<ng4K*=2`s-79X2y_OfZ#5dHYf+ti~<nCdl|M%A0mX4RU`
+zVpWAE2~`@;WR)95RZ3E^;SA>PzLd^iC{o4GoW1%y+U#&0XkX!Q_<_aG_0WuGrkHF`
+zRF<25eEKa4RvV-mAd8m|(2gHHeg#!)@B=;L=<|f`!GHDDSF`x;YsO0lusNA-8fWc7
+z-75X0KfplzD>c6%XPtiZ$Rm&3naYdzPK|ibju!)1hKt5oI#aibKc#YfbmJYh!x%7x
+z&~|$&18!eu4^f;gND*s**1&R$pB9@jO5^S)4`AsXX}G;Rb*MH}^X|L4Ev9ql3w7fw
+z)E#Af0OAucAXx-smSV_!jL?b~W~X(e51#!<GGGSPX*G-1<nE=y!a`X*!VzC@6pS%2
+zKozdG76XLU7_unK!{I`5yfDn#jat+gN!{OEMMKB#gdKQmol7-aOr|wCd+6fDi-=!I
+zj+)=b<10qvVVv;+`tg7xCFGcVv;;#Ct{(7E2bu-@2F~SA)Z*e|Dk&+!*)JqlSBz*j
+zp1ut)A!n^0$XUBzJicQ6u^ew`03r>>gDGJ(Ml8Tn5b<Kb;um{~cqY#L(iMAf&PS57
+zE@JBpMf|W!;-UTo;{%MxL$;UYV#Klxw}@w}n|`c>s|Ku!?8aRW$3mxYc+`0O8X*2R
+z4M4Dwcrk#j#tVBsMkvSU*z8<Yykh2@^*A~|Qbpwj+h7VzNBjgx#5?OxN`1f~duI|3
+z!d_b^+Gz{X77GR$h-aDbSZ6xD^PqC(RRb<uxIn(Hqm(Cr0Yecz3InFXbc9b7@x}+J
+zV1%&S8l<gej7cRPauki;qk|iFDDN~JUQke=e0@(JeK7-f|9hPMfjIZWl>smY1EwK*
+zI!ulYm~3=_I-FTUaR*_CGDIt048Ut)bw)>g@U^4<ox}9)PxI;HFK1EXHUkm=1LFI~
+z?*2#^9UV|_ETX5zMob|`y{QHUh*+(7!4h>4wwZjActE@&lHsCN7%@Mq9R-~4A-bOu
+z4uhic^%C*X0p&(uz%;`HM6_mjoH@w$nF6#v%d3hPa?&v*-9<|>J5bvyUn6=LRjM}v
+z!LoQB0OOUr57P|{K%|jy;h-r<2VirSPedoAC*)wn0vx#()x%T?XI{h$iTH8MS$Dj#
+zCm;{kkB`d^(3VWEG@b{fx!5FImrLTuO5>gNCa{zT<UQPl^6XLPZJp^1oUnGH!#d$2
+zUf7xC(})*3(udD~loSt=c;f>Qu7VMYGZ=D2FJ8#<)0PacMm!H-t*Q?d14KNOk9X9Y
+zXnX*I<*XQD!4uJm7dB;hwBp&Ahx_pR{4d0Vfe$iywAw{DrX3%5Ak$Cl(%jN`_E1Oa
+z`N|I%F-0@pQP=PQj?{)HAe=Ubq~R<xYHx;5E1u2G?j%06Wbt6&lUP4o?Tjg?882*2
+zcT3}iV1@n&o(@w*Ja9Zr`Sk-3s10{tI4wqGO5h0`fLwVz8}i8OJV1;{iZ?c(u;%;3
+zs=h-7ue?tAPxh3BOC8G!sKS$NOZQ0P;lU29evPq-5p}1Dc$jK@fFQ=?Ki*v<986Ic
+z;gCF@9Z=p@<?-x|7ru(c3)49M*AJ*sUcrm6O2d<#&I-ul!OJ$Mxn=Qes=1qpFE;`V
+z4B%L81uwnEp)wb<gY2+8p6yEa$l_V#f%a6b)+E)4SALHH1=Zh+%YUJ-G+anL1+P@D
+zJe~y{{eF)DB3N7Aqun@G%OQ>U1L<Dc^pHyy&pvqOQ&qg70U}UW<+nBB1MGOFPZcg~
+zPjk!S*=TDoRs0__0P-I1t_l}UXZdCEY<HSR7SE=bx~k$04alqhZps7lUwoBgWzJy$
+zVx{&2Vl|2AI3sVPC#$}xA7Aa=s7h~ftW;KjW2MqO+5_%a4|u8e<dg^8vaUjx&3Fp(
+zXf;{5APW~X<3V-zpQ$`-v(lEzfNLCgX1=i65!`;b%Byq+Pead1Yk*E>d!=?}xT4Y(
+z9m`q%V4H*o%6qx?6u#>()|^C7R~)qc);phiL1{~6z~yi=*x`A2;jhIFc5dRf)UL~8
+mYj6CUxW+*I|JVPQk?UuaEz`=25ZodF0000<MNUMnLSTX&GmVe{
+
+literal 0
+HcmV?d00001
+
+diff --git a/app/src/main/res/mipmap-mdpi/substratum_masquerade.png b/app/src/main/res/mipmap-mdpi/substratum_masquerade.png
+deleted file mode 100755
+index bdc8a5d44ccfd5ea26d3dab204fdad8aae4549e9..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 2094
+zcmV+}2+{Y6P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm0000PbVXQnQ*UN;
+zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU(&`Cr=RCwC#SzBlvRT%!ynb}KrlTDgU
+z+N8NP0oxL-)uz&yno5f;2!d8$q#y{Q=z|D-5etGy^+71w7f~OSst<xfMcRfHM4=T5
+zwZ_JTjW#t+8j>_=HrKso=lGwQ*~!f8IkUT|w?Ys6nK^sTocX^0cK$N~xZD2mMq;s8
+z<G{ecJ$O&=IPgP5Lwgn%7w1vf#Kgo4cM=IQI5@axVPRo@Zf=fo!Fx6`GIEHI6(*@3
+z3n-@iC#}D~e^+a3YhON}uPywR$z))5c6K-3#ykm}*!$eZosrhBw8hlJyuYFbV8s`J
+z`9L)+`GPP}-2gMeh&7*CfAGQ>*t%fvFxO=J9LCmex;kp0d2<IKcx!xhbrs_A_}Sjx
+z-cL;nFc|@LN2vMh)SuIPSzfbD5y6EcCctj44eCQ8QMgvy1S56LdS6I@*+DV~SSJCa
+z$H5<c`9AQe%mM!VPeW6OpLpabc5k6*mY=!NpFDYT^VU;WCvuC)BK0u$o2@>BYe-_K
+zz8yx8kcfaAyodk?|IV@95b%Qo{Mpz5eD>uPNX(7zynNxr6^FLn1jfe30$nTV(%@aK
+zYkm?s6O*v$55dLeZ7>vVFWcWsf&b{#WuQtB(uux_qDU947yWkY^Rq(m677rp*dVgq
+zBI4yfo$b}0;0X-?Aptml?!|f>N_bj;`&X1I4hyWJ@E7LzJ>KQaK8S+QY>{jd9v%G|
+z9=LfGer(wRH)<M+=J2+qEd+1B-cu4hA%3CtXbIaIEC~S~q9MFSD21q^KpjdD`xJ$V
+z(usL(OfJAvzn_QR@d42b;O0yU{ur6EEaV1HIRT2`i5RNt1zf0F+a?i4GGT5RqCrYy
+zkkvHlQV-r7f!3uNh;{6Ork+mN(Os_-i2X1I$KM}-W3MpSzO@m~eK!anpZaADJZhfl
+zdai^(3GmEC(8n#L5YUiN5=o?O`P)9P0j=@olMT>vWEW^SkKl8UA8doy-nszC-|L5i
+z`x@c&nIXe%Q0uze5L6W-QixOvyiD-+;O2`}BZ*pcFV*c@*s5Bh2!5XqUJo|B2K)q$
+z9PWZ-G7G2srV)4;C1`<X1@ICVs;DsX0`Im+Eq5<U9dm;%^%i)0^ZB^KmUWAOQmb1)
+z*SrpoNWqs)fCDcVyhG!ZV6aoe8rW}tcE3;=iNrGgfG)+7I$lOXYh@TA=4&d5fd;BN
+z@U0foCu%Q?FJdLY+rg=bsIE2)9bdc(H65Frioll5X5maMZfcq6Bp3;!pcPnFe+h%z
+zumI=JU7`wj3RR#%@#)tfK1xb}x5|j+G>pFSIZO|a>VWwi?n<dt3O@Yg0t{bGprTdF
+zRR~L&z<CBzJoemDSxA(Lu#Y(c0_YyqNFs$mk}R)dWgf1T@4pVi&%6s`hxR~vI|gkk
+z2bX`HgxR@_nQ>Ih3Pyw@2@*6R=GTI`;(fs5cf$DQMxhG*d_`MDBEm9Lhs5nwlUHC%
+z-$nRlU<Tqf8x5DyGY)E*695|lT!gSRx>C@L2e!^2E~HeqcA1?BgK7j(kFlfJO>KLI
+z6Hpn2NM41fZ;V1)I%RHFm@cVP`^>R6cR5Y!LbPnq0^sgB9Q+0Z6X0M3&lW5Z(kL9c
+z9*0etlnI;)021rmLd;-Z|31J3&$|E?y8M+91QoRBZdSZrB$tQ7<8fdXP~&N19b&Tv
+z(NigbZ21Iup;ku?sX-ti+>reGBE?!SGL+e=;m`7_mdQ86*5oqmpT1rayjP=AjThVk
+z7vSJS=I+YLP+6?Ie!%c_Sb_<X!-1(0XijGYM-(S`R#6Oi#Dbp~`vK2Bv_!f!EMZu$
+z82noQv57bya{W$M0yp^e1hFU%n2iX!n$H8gb-2L{<;YGhzv^0=g`T-dOqqT;c($IM
+zm`MV-kl%UwG~LRJIEPSco${E_5C^7)^*qLiPXeA)7z3<?TiIFwtq~Rln{AqE#*u7%
+z10F%U7x>8xNuXWb*$LJa1Eux_+n65cmL<e#H%x4fBv`8F)|d^JStKwycxfr2Y-u!4
+z*I0w3bHG+Iz>=Ag<p#Gz4!DR*Z=#>86)Zc%C${c|N!=0&f5;%AgV#KZId2Q7sIh9S
+zJ2%yW(iX+q%I9GTL--+vc#sSg_7jOR?nM%779x^ex}sXh6wElZX#>z`jc2^@S5<Le
+z7Z!wuX9pa+v~yn1+_=lV$U0)cBm0m@2xvn@I?l{^pdJw&C<L>BWK(h;g85ugUBqtZ
+zNLh5WM7JW*YR_Y5kWy(%;{=ERy!C;1dA4j!v>d=iXJXS5fH5H&D{OQ!z47UO*FoLB
+z#^%(5%XX|X0y0oV4>v|U1IOpe`}X*^kGbE~X|sSA_=;Ufn5Du2X+*0Ikna3Vqy=7;
+z83U!jO9^OYJZQO6C?Jh^#p4^9DlG7f){HL1KwkWnB#^@MZMnO%zBfq!$tKU=#LGnm
+zL!u92G6MOD+~Oq-Sa~Ids@!%hRa(&uvgxHu`sWFOm5XM_S7t8XU)S`Ik19<>|FESQ
+z0%FN6r(hM!1OD+#7qTWREDw3S@((Y0oU7lPvcy)``e2IL|9;~({V=cPHOM3r{nvi`
+z;H6Az>V}&Hk$lVINxp@jM?ClOf2VPqETQm!FD-*$pO$`*Gv)u#a;7l2+wQi%xBVr+
+Y0LvOWjfh)G4*&oF07*qoM6N<$g35*9H2?qr
+
+diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..c56d924e82226247414225514f6c666ec4002598
+GIT binary patch
+literal 2733
+zcma)7i#HRF7vC)Ou=efen@VhBh)NVnHY1Pbu?#&t!ZsTlNnT%%U&Bbz_bcn+oyWAy
+zBd^dTuW8;TN#0_Kd5g_A*2qu)!S9}P?>*;!&b{Z}^SSrj`}TGi@Lt8e0001NZDrxO
+z;|afYH)zN9UyR=Yq}J`RS1hqUfjg1^M-~<q!mtE7o!)@CjlK4GCq@4PbH{%F{=LaM
+zvaPMHp`qcF5u@KE;6H~=aEFh-N8j9m1Oma>*jTM`NR9RFIy?GPQ@{H9dIO8+HRp+4
+zDBrreIz<?+RNu`CM<>G!7A+#*9&wvf@!}nIAA))s*}UK>dCY7048uH;HYFn(lwlZY
+zUY*KQ_Lx)kUb2juSEKNh@x!WKBkGi4IC)rwjK=28BPhcN?~!BF`C~pUaN>xXcdHty
+z6+voM^=yF=T2zQFP{N`)4QX3~vdu?fOGY$(P&UtzHpNKWf~AX5^P0X$n<6wecSxBy
+zdLg7-$-Nnh?}T0-R>e>JuK(QFHmhHmh_q#+tRB5O>eK@zB5htQqQchJ*1oD!7te+a
+zs8e!Tjc;J?jZlwvCH#!4H_GPO7get*IJp<$<zW7_N5gwT+kbw3k)z~v&XV1MAa%fq
+z@01Aw4QjEv-?yom*<(KM5#;&fe*DJww}PLYw<@aQwC8EK4XaaH2#JC*e!E|C0;7Hy
+zfv1J#Pnk!|ThMT3_bOE=?&$O-Of);P>|=a^Gco-p>Rtx3J61p9Mrd7e>D!L3PpMf=
+zv*x#Z@~Rqq{?5@P_P4!%t4{VadDNgmehI@@sgtra{T#6Qrns_~P!ANgC=~e*OWmgq
+z?rChB*Q!o#)9`u)Cse8s3N2z@!U@aE%a66g==yOxM0=QJVy_e=oQ|$J?T50-5ekJy
+z7yoHH=3Sxek;AG-+h#8<ExCG^8{4vwn4~sM%4H&_N!9bJXHA8YyT#=SW7}sdD=QR2
+zIrCUx|G?;d$F!^FIhCqJW1CDbbjn|@Y)3q&TFcw=O2v801eA4ZyB6gk?#Zi@RHjvY
+zCBm~xgKUB;o0*w$_NuLK9yhmnl9T@dZC8rM=2UQokyei{*q2#a7n(Vin>m!DY}w?_
+ziK3m|)d_L5y9|&w1?}2%Qbq|VcNlSit)=`!Vz_4fdE@kJj@aH_)RNJ$T9A)S5mv6f
+zA=K6o5L$^x007yu))p78gpYjwbcf@1`p>r2K)bd!$E6>w?Hrd;&dr+JL0Q>~>*L#?
+z&BlYy1LQTBC^SC%mu-&P6Fb>0N+WM>qAtw+;m7Fdx~&<RJAq0Hzw&w%l-6KBD=Nmd
+z4l|=um(7pTLKl$<BF32w9LKd6pdQnG-=ch-Kc2rZlPk=gMCOH=&t)tZxS?f*HE{xl
+zvozJPE7e~sJ7)YvBz^qU33G;v&a}tT&4j^AV#%I3|M_1$uYX-ai#NiVB@lQt<^2uy
+zO?t4rNTrqHUTc3M5UptXtJ(O})b+*22gMK{^k?k>_AH<cI5n}~cH_xou9CP5p#PLc
+z0f;3zwsGr4dI9*ws+g#z^n(+B#BGwoEI=NMFJ$S!!kn!(fN+9#o4Ha7G8Ln+3I4k!
+zPSPL?$B4xjAl*PP%#^RQLcePj#?X034m$58EfL#zIZy6~3tK_&CwhEq^lD|$fDQYl
+zY#)B>VM+Hb@S+n<Au;45kO}(t{_4FXH<aHGh<4^Aa6I0%^ojD6If4g{%rAi^s}0BM
+zCYkWQ$Q~KxP<;JHJw^v!-TXZA!@s=r<F4|C&EiTHE@ybLEd4hx9e*2s(a@&;(hfA8
+z>BQidDmB((j!u|x>044*k(R>AZ8>ptbJ%^*V@6O=P}x!+O>V4k`)B(?w9Z2I`g%Mr
+zH<kD`tywRv6L(h!5|AU&JS4vfko(;9<+fzDmsNvn!V47ZLT;@Hr}qmle|-nyd%-}p
+z=>%B<fj7sx7Qa7x9Cel>*)UPw*2P2NPCp!=8p-&k18<B2sN<_&NTdQbn?LxR&3SEq
+zpGW7D;5xU`6vLO!if3mBFeH+C8K#so;VBl<?=S*(i8stXE&<m%rw1Mv2>KMKkzlUE
+zR%~pn`?Aj3esSnio$cT*eXu-Q=HzyfA%BVlFxVtRN*qClobb!(K}R~iIdy=t(jt>n
+z46fkuO>-P0ews@-pcMDOWV=O5EHo|Z=W?^opGZiHT<d0EEf@yEzQPtA7Vx<3YK4yV
+z)23i-nX{%?|6!9aR2;hQojq3}<BPo!L+c~LdA@`XX1(p)l%7=OAjzR~Wue=<`knXP
+zBTD*(fEfq!6nAfPyD9JE4F1$p*{JbK#)NV35m`PM-e<ZJxu2{FE6sJ=D9)YGMOFJR
+z@JC+V=qX2&BtL-RGC!m?gCYQLKCqsvVRdt5O70HojQ+I=YD(GsQs<NB2DLX5_5SQc
+zeW|glP%tWCCo_Pz^Iq4F66qpMWaQoDJ}`b0eV41ZJ)kWQY83(o4Pf8^*uk@eNy!8(
+zrpa^y=JHEB%_VUP_$Zzc5GP>8-Mhy-#N5u8oN{_K2rN7UF)@yG6!}boCJmxX<n~R{
+zxhG&??hI(po;~32{SO&_u#^<y1*Jd21*{g!pFrxMU$c(ZBUok`;z4|Ff^;{=_Hr$Q
+zj|gP$)y97sOlyu<W*y9AcqUa3f!=Iifv!|PhtyAzh?y{X5p(+Lgb!R#FW5Sl%SyJ)
+zWTYx;`@?FtLUTbGpM~hjzf7d}jbSi}sdG_x2V=0;hJ@&4c?SOms4hyEaHA2$4P3cq
+z+jbC#_U)sE4C&e|0vX>?fUNm%=#;U1NnT!i1vj!FM)%s<>_&*m`G}Xx@#6+^-?Y}J
+z<Jk)>*B=B!(TXuWG67Nw={gYItkpu7b%&>dzCEqD6dgzcN_!UN`aW`v%e@tHw>=D+
+zAZTqTT@bOJy=Ls^wM&VUK)pr`YBP^@O2A@nQ-O_rrqx_l;7S(;kqHagg$j~PPjauP
+zq*xkn1*=1S(DR=*ndhkRSXu=0vG9BIJvOVl+MyeR$nymlS8VM*$z4BB*KLm2pvH;{
+z{}Int7}L2(Ip*t};~y@68-qX~-sllaw04L@DWOW3`$9z3|65EwE$Rgc9l~k`V+^k~
+zwXuf01+23Imi&R7dbx-14t+9&9YuCU>OTvw);BviiK1^x44?*jP;Eh!DL1++Oo^UU
+z!KLYU(LS@qK*r9@R6~O_QQ?vL<KY(K*|=|j_M&IU*{nU?5RVdCh||tdHrX=-vNFC=
+z0f$77gxNCACk#v~TviJoh-ZOfy`!^4R3-uKiT>{sT05{IB@vT|L3Cj-{2m%Jca`cU
+zF5Fi?ziTrC^5^N_U4Ve~bq^!KwBwKxxv5LLL?iWbB?n|V)-(r)ru7(CG+jx?!%mSG
+zqT6Blk}2024(AeMj1=}czcyf#(vy5*RNm6Y6fCR)s|c=K%@eX!D!Zkbu7GpU8tDV7
+zXA;vDbc0uCJ1^ZyzcdtUh*DTfpo#*B86D(l?<f7e$w}>i%WO47jM|^}5Gfbkj{qrE
+z$1{FoLu}8CTEn3@%M4AKXK($K{(ulhxZS*q(p(<c0M;jS?UaYdMH`^bp%Z#J()*G@
+g#pMx)rC9*L*(#grgVnnJJI@zjZE0swW=4qpAGypcSpWb4
+
+literal 0
+HcmV?d00001
+
+diff --git a/app/src/main/res/mipmap-xhdpi/substratum_masquerade.png b/app/src/main/res/mipmap-xhdpi/substratum_masquerade.png
+deleted file mode 100755
+index 0f2b92b85724bc63985cfb6962e7b8bffbc71245..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 2734
+zcma)7i#ro+8y}MqzDn==iJCc6DLIv9=A6?qITT@=nX%q^%bOgs&FDa-gmh*OF_be~
+z4zuMjLNDc%L)fbvCTH2k@a_E%zUR8``@XK<?|GizeLv6rT+clZcPFL2YI^|yfRc-|
+zgQp~7{xvzE#1EWHk`U4Y4}`ZPf)F7={+}^FKOc?2P^nawQyk)Ax@3u&ZH~k*EiJKK
+z$XqU$#bTX=rVdzNQ$A#kJanuZ9xkD#r>Dop#_FshnJ#e+?$lQ{#Ky)(bB8y~vq7D(
+zD-8_|Y7k_pS%5Q=ii4Q{bRajY2Fz)Ov>Xons*Z)azHK>z;_DHHAy`Y-cO!bC!;oOU
+zZdeN#HK&DNaHQ~cLR-M-VXcr6-OynT+^{ANj>zR}hYoAwNA$z^`h-@Epb;H>n@(_>
+zc5s^(wiSYD)eLG?$NaP>S-QQ0xfQ?=??wy=FxNMht|gXk^o4U2zQGks*J3z=_7xm7
+zY8zR8B=Cbe`lGu4uoima-~HcMxxF3$2U)rm!kp7;4`1q053+Q9`x6$uzP`@W4f}aI
+za!@yvmfutl32ahFwI4yxXyIY5dEc}`zH8t<YlmF0f7PRhpFi;@pZ}9_<kA_(!Vc}=
+z4oFZJ7&EOGR-#Ls`aUzOPw3Xh@sANFo4T)F&pYc}T+L|D)e9Kb4Q<7wOpi^r6Q3lf
+zHV$i}Nzn!0?PK{4B&6NlD$US9`11v)_(F17ZxY=%=y^Eo?u(pHiDt2XL^YI@Hg|M>
+zdHR|?YaiE>Tg@We&o&4e;C46b;)vF1EInK$1YNBg{Mvwc0a0LsEUQ#U!4Sn!mJjlE
+z2@M)pE4SP>T^v_0q*en{rHLtWNT}4nEG{mlpNOWK-IiQ5%I+n?o1SbO&om~&T(ehK
+zSE1(~aP{#OU{rQ~Bit=(VPWAizTC<!-_q$3*C5m@h{4vv`e2zAM*<zZDy-b{mY0`9
+zG37b>5d(vx_dK8Z*k@O11zEYi41s6fxLoLoX4D+TJ9<}~bxel2JZ(Q3`WTr}YZ#W}
+zoK&Tat=7X?Bg<xHW_&~H8b6HNyJln;{0(<6g(I>n7{iv%>9!tajxI%Zp5=BI%3*GW
+zxQ`RXlHN6n^mO+E9JB$-$Q#NYky1RQeW36tcw@_A-Kt+`{GK85@K|eo(Xm1=u*_Vo
+zTCc~{F)`XlQ&a%}&>0s8TkjaD{@z<^0ml39^4fSKG2RWVmD=zjM)+vf)OXyKc$MXy
+z7Zy8R-zT~o&N9W8vBsJIoE0%eM<BkXgSQ<-(V}e59Pe5>_0GiXTZPx+!(?CIE^Xjy
+zPl%`&%03)R?b!9cy?d=XL-ldnBo|jT!H(M!XPTz|1T&-i`6c7QT}TDWqm?svR0IR}
+zd&6EIM-N%bvy5*luslm__Vs5ISQ_G@9_`Ux{X<Q7k>H8i+8AOjO=@yZs(C_PaMp1P
+zz_$IR`Dv&=hJh+1UjYaN3(C&ut_Ac};o~`bmM593ZnLj<=V5YdA6!RP<qh&bHfwX2
+zWDVj42VH8mOdyT7Hb*!Zk4~PojPVZd)mLij`Wz4gxWbWva)d_K)&Lj<;BCN>S+!4&
+zmsj1do32m70`yK+gEV&1)q0f+{GFau4U<sl$}Nvt=rJ$QBpjZz8P^ZZA~t<g2f)4K
+z&nv;QDkuuz-k48GPts0a2h=c)b>n9z>wq=v_~B-d0s8GX_~5pKpB3O^+TDmlM*h$K
+z3LH)eA9V>0A596LCmsfmy6Z7q{b5^q^sQIlDD13g#&M({d$-Z*{>of(j9r;uh1j2O
+zqnL_BFOEP=uT3?86onIjA#!u#49$3lCOmM%W{vPq>%E~3KRED=344>biSFv0+5Yt*
+zLtoxd96+9~T5;md1Jj?u&tvV>IX2V&KbWcNRyViS<K)xsx(P~*&?ilH3`k@EN}&3@
+z^|l2yL5ZGv6AX>KdELHcaVlPA`vi&1d0&srcp)e5e(01BOqvEyeCxedQn1H5yuS$6
+zLsK=$5h^0G8;>7~`N3G3n;mpgQZm&}T_s!htl7&((kz;Vu-rl5>{OS?l=dF^McNuC
+zQKP$O)gRxyJ^tq}!rBiuvaW3EpE#fpxt_3}Ce^w58RGl9Vn6`7u1NPEk5{xe`<zh%
+zf>`*nySw{5^A?b@*s7wjdvC2BVt5D*dH%!4$2|1!y)E-_GK(-kh~OlNZ&`Kju6=}z
+zC$3B>%J`U<`xc4tn9h>eQuw#|$ij}p&evIcrW!PX!K{*LLvONd8L9rK$Nc=nd+6rD
+zC(|b^9xc)Nz;rrgyX2w)9O!3GBUcjD#f>2l*7a!<&@xzKNK-5XCbrXmeNqVCKI&I%
+zKF_#V9@wxU=9@MxK{!JkGzZxa>J87nyD!vEi0AmnESS8Yy>}9ECs?C~I(%S7CA{=%
+zHgRiQe%vnYlG~)|q*1rCp${lBTQ9{gXlwdzPy8R6F}#WTA10^lAr&`xUYYBJ%l|z%
+zvArR;lNh`6UBuK{RP+Qzs!L9Il@NAukf$&kKSI$-SBnPt%&fGh5EvxWl_uT^wr0Yf
+z8yZhl;6A)pc{{zieoKX_G!ji&+1CT~8%k(KOd|spUX}_?L-}YkfBgsT5N_g+zAqQP
+z=#xA_o?)W&`}Zp)9aLIDQj5{)fmCekM}QD+$0p5bccOMU3Hw&D+Z72BQ`{599x-Zl
+z9jk=m43%F;`6^Ksr~a;Kl!kL59~0N-k~4<Jk@qc6x%bD#-dR^d3I{o5OJHKx5a^Wd
+zQtg}1H${cgUG$bmrhSmGwH6TP{O@1RJT$1>MzJ1MYKR|8MSiAkayNF84?GBG4+T>4
+zY0l`~ytdh8)Wh_4I+SG0kvEh>L-xO)JUX;d%vt>;Z<;OB1kYK;f0!mzuFoa3T#fX6
+zRlaW)s-)VS_(QN{M24pHB(B&?<dOK?hjeA0mmvO=H03J?aEE?Q$e}~KmMAjWd6Ycp
+zGXAgC^{UP~HRMyI?$e3w8>YQO!cCj1VmH;pPu=sKhWz0JzxK+`CM*d;q}VM|SZUN|
+zzfSD2190#LNDhvp%04S_9`85UhLdl~(V|o*44*`uqAm`Elzs9d$MgEIygnewi$HiS
+zMZ8eZ)|V5&^GaBrlZmXc0CIm<m`#M36`?l0L6Dl7s~$beQhZOs%9G<Umkqbe0(X_f
+zy;6}_TcNiR=~#uhEER}`R9x!|_~><#<dIs>p&#S{rO%v4+NzlQQBvCykkOI}vw7Zl
+z(YJaDASrq>Nh$f_WeRhQN|%=8F~uIUc`;e+BGe(>Y!UK+8}f<zAG`u5`woboL5hBZ
+znajd6IiM^~yeMIAIWHi_xYs92r7k}~@^Ul3;m%v(_C&}I76C`iD4Xk@Lg1WYhn+0S
+zC-PXeEPoHuXr%TJGa0OgV$1yL3|WD4(i-tjRDkq<Y?vJp5%&-!#JVM^oh6s7&oZ(O
+zoLfAfz!~7xv8L0H85}QQ6jNXzH+9u}(ztxzo6JY0vSTtcECu#?kdO?$_6~7M6K+D5
+ztM9uIS8S2Rc>NHwTeAg)`CQHdB$t0}KbV&ja3jU1%eK{;Y6OryYm*~@?6D5&KHa9g
+k%R)j7pLcCf=X$&cp00MSL1V2}B)>1f#nIiN%np<IKY)He6951J
+
+diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..69011b70ae30ed7fd659f01e182e8029baaa1e56
+GIT binary patch
+literal 3874
+zcmb7nX*3&J)OLtAC_-<Q5|PAQV+B<bB&LWdMQ25bxwI5bs1|7}Xo*gQBBr7grEbk7
+zW*tNgHMe4pF->*QYG3#J{=NU+z1BWwt!MAE*R#)g_K%Z9aIzH_l@kR30OEEyE0?{U
+z{+|m2_f~f1!DIkHaEIXFX6+Cdwuk&*Eh;K%YisL&ooaKqseSxfb93{^$VfH*+F`K#
+z@bGXmKGx06ZE$eVTO9+}3{23lK4zYckVGIc%q(4(UUU%4)c+YS{3$MMW@aW$+p)gB
+zp2y=kxTUUH#j@nwyH$Nn@Z}xy7ssI99T1OBs2AG4kgFOvuIRmj@HNF(OsM*ERRTH`
+zJjNA$RuF#c);GAy{wVvW9SWXYCI4}R?}Uo~IE;inSv3wPb*Yfd9P-DN{4AU+C)5Ha
+zl>NGteYk3YYnInLVMKH1$}WU=my%B>oQQUOZfajqt4hKUDp3v<6Y7E03LZ6ZuXURn
+zFQ6VTU|ug2J!_P`x#~gPYJR5pvRW0N8YS-<1QCtTH?=QWf%~HEi^dd))sTy<Bkrd7
+zXIPgiltYP`0~3WWLOT?j*xWU9C@^<^j<U-@;qIdx%B@_hQTSYIw`vpXBs4zT)RvCI
+zJ?d8XH?>XefO-v?U#@~&G{ZB@9EwnOS?y3yltUR?=>kjfY?ZuQUSYGjL!Oy^&i3~9
+z(CE^qnNN0Ss|FOjSWwSi1<y63h(<+^R;WiG!Y34!7JK~8Je>3iPHKYt3?fLa%0AB_
+z?(6FthL%NW+}(NAfb&?!hyrm9O)gRie5M?jt9F?jU(Z(YTT=F4)d{Ur^i0&d;b>Dp
+z*NLuypC5sdrWAc%DS1yT`MEk2*J=8<w6RIpOjq{|V_X?a)z=DF@>wNd!IEO-L|<B3
+z3b<0lI2u)t45dG+>+?(N=^wj+3>{Gm7@!tiBDYQyHm%#<%+QUrCzOvNd`DFLKC1c$
+zo5tZzl_exKG+UBKKaBR91t;nFW)-j>YhA@TmYwo>`3VzMR?*gK6*1Vrs=0Kp>{Qyu
+z#)fxjGoR1j+}v~zXiV|Q-xJj;mpd`V<#0Hj!A-ASZ?_GH9NAMaGQ!2l6(FsF1qzA^
+zffWxN7uYX$P+J)`EC)v+jeevxgho2yhqW&%Mn_MY@$d5|FSvh6%;1dQq8IN?&VeF3
+zD~ua?Vr_)F3BkyA^SrP5ha!>Y7m{87D*B$COm>~TCpQunVpI1Vca0d*WcO*Zf$NxC
+zX3%xKTP3e~U_G?<FD1w2Mi>hwj9nOr6wI$X9Pg6*#CEy9vQqEE4cap5Byn)A2Q2Ta
+zj9uu6<**8tz=~!R_Z(@;=cI#RYlQYgaLl<h>3#hvEi*}S_XxRbzQK7V9Rt7T{SOB$
+zQhpSS)f*k4TKw*OY4ny~?T8m$h$gP6C*M486FyKLr6OCj%ycx|z6?c?x9&#IuNkhD
+z724`DN}h>%(okXL$!{`t7Uj?;Etj`>O0^W^gYH;&+Y7IYLK_;FxV!m}-ZYdLydHxQ
+zN*x`$PNuwo1^w8%xH8g%e9Nzct!ZcmK;z9L)g6L2;DisE(}x%KZ`Z~agP`hcNA`n<
+z9+5)iC$O~!*9$WRW6OZw)LUf@!x`Xj#{$;IUYMRy6acYc%jcSl_04a;hUC95-%J6M
+zFr!~1Z^D-u$P&x=IYVs@$9}2<Y$aasp<LgyQT8_a!N6erXH#779s9Npi{VPNZ7ccM
+zT3>?Db-UH(JB5ldTk)m&mWeaQe|%Gj11t)EnkiV47+;!GKIRR$wSm06vt%*W0Vc`&
+z*}d<2iXC9B@JnK$^WUzptF`)CWM@)PDO1dYZ~7>0@v`dn;o3&ho7TAvPIN5+C>b_M
+zm!E{B%(nC*4<x9=T@PF;amtyfCQifGh)lTUjLUf$ZjcgDwx{Y@zfwgwcX)cbOeSMa
+z)c!QnSG=_8|J(O>%2crjq{W;2UN7y!`WC16%N)p>E-%0e-|FTn2&HVpMyaE37d*6N
+z4Vuna-?kSscs%Ohc!~kGI^UEKC+ocRR-PJjsvHVuk9mX3h6-~0fcbk3zmM$|6LS6=
+zS*9`-b4qPi3cy1bS(~27MF~ImI4LArKodOK*RpJ;lfAE(PBnpNib)u`1y^o}a0TY?
+z&~ECID=d88p|kp~zzjmxAEQ7T$&>3;TJ-OR7!`Cl)6a&vG2OHY@zayE7&<&Kh|1G-
+zuiwIjb<AZ8N^P@a+JUKhr3?m0dyJ$<pYZ$raZ{Ia&+lF90{<t(NXBX_&NYv3JS(-&
+zuy%{tnqm|N!a6zzdumg5ZC1A#*{)B=+$GzAP8%Lh@&b-h0K?jEfZ+_f&9XEltMzSk
+z;rA~Kzs8S;08Ukyo^nm2g#Qw{)afH0<hO>|2gPzEUM^YA00%u}R$l2ndq)utNLHZ@
+z%*GJ%4E&j0GM{APU?v_IE9tt=pFOOhd0M?w)DPBBefBCAJRyIlcXj*pt%8%}gU{5M
+zG0K=lXdL0XE3ZAv={3=!hYSA_9V>Ar@_w{;qwV3&=&^X*N<sYLJb%pPZy!H4HcKQ<
+zDwN|?+xlXPFgZe~-5sJbbh&uu{@nH~{p0NELtRUQ-;$)#P_%_Dw7Mf|=W^D**fCGV
+z%6le<R1U6ej-rln!+PUPM92U{mn~s{<CYFmaLpUY4|;X5{d;TE*xX@I<ozwCN8t3;
+zKe6ec^{!uNNV4jqKS6l01r&?v<Og-*tc++PpNlYus>b8ZrVxsd!60WVnV(;vnrzAE
+zSC>Ys28;?JaB#A7aARybk2!o&;b8<9p}0*;2=FrWSv>ZQ+2~Uhru$7M;u(H-o@Vpl
+z$D8kJ8pBEluos&=F60@0CrFy)oOcPYZm})j%I>xRs0a!fob98i_Qf{_T}&+Nq@Epk
+z%^SLAruS=V+_TtIEcce0g@t5$p^=lI6?&MoBB0LydX8%eeK|kmLH+`yp!Oa*eIp$Z
+zsr@#!^0^ws#~EnB2pJeCN{wY#Zfitx)p^T63LOcMkk**<qa`%fKtJqMyhx1WFuwj<
+z+w<4tfy-o!D1RO$*Siuifp9BQM+#xCO9Z{rd-E4lI4UlFRJ~<=re?>tCvDU7-%Ce~
+zvuuCuYp1$5stcw`mX=F_ph5>f0okt`g+0r+-_ZLtc=h230ZG8(o|0ljt^9d)=c%lt
+zpxmM;y%LFn0s*9-7+rvOqLh!V1i=vf7H${Hy@Nz-e+j6Rg2ta(2@BiBuU&V36*^t%
+zmx2OFC+SbVx3N_3A)<^BaxhR0|HKyka1He6QJ_Zdcm(i}BQ*m20o5y$4jg}nF1`A1
+zr={(V%+s&;f{Fs$h-gW1NznK?i5}n}>mXR6OsptLv-D0h;XSoE0oDWqg$%fW?OC%o
+zrj9r+L%1S`D9sWA^ql{+{x~|b7gw&4I42;Cph)Hc!69(elYZQV6tqA$FkQJcrx=8=
+zl+5y(4Kr4@Sg$xy8Zx<V1AXabKLey|=PhCqm{D3i?2dmPEu5b-;?d%|>5p|iZ;)j&
+zsIn4`S%zPJ|Cn>Xy~Mzx3Se?>Pa)*`f*LG4P4mzQ^=z5}?N)IGT5`ANJJWDi+9wTF
+zYMTa?mq0>BsU}edTg--C>2BohXc7h;@a7sL{PRlvbQ|@w1!<Y_HD}v|eMvc)W&FqD
+zK&GZ0qo~`c_)xWxS8Xy@D5AVh7rBVYvBP8s^x=XEOCMd-ifd(=b>1jbI?mu1AOE=c
+zSIGQR%M}!?K<dLUD6JlZ=IBpx$ECiWlm3)Mi(zYjXSQe5ZocP-bj*PSERBVgyQRSR
+z*?i`#;PL(2KQL~!T$${iN5{Y>^(Uyb7#4;X{lJ+;%pDrK8zXV2wpbYI#uG~51oiPv
+zbB7e)0@>|D<1U@3T>zoN{{DRG)z27>Z6<^E(BGf%_C!F<2vvVEJqhNLtAIJSP!a71
+zO`?zx!)Kt1IISmhR-g62jm1z#>E!wuln4Ec5`vYkzlcGS39mUKf?*OSW;*{;%u+K!
+zj<e^YT4qH+DtD=(+@CZT4yMEUjMe*p#PG(pR;1ilG|&iWI`X7B<RKw-l@U$AqX4n%
+zCh2_l7z?2ev@nE&Il>=a{q8i6GGni<u2TFjy~%Z^st!~4DmiPii6INT`1dNM`2Mh*
+zC-V=f`s<jEE>Y!zRe)sjrL^!)s^}s{gU4*|2d0y;76I<bcf#0D$OPmN^lJ|VmY^nk
+zYcH!vWVUU{Aqk}Y1XOU`SzBy4^p%qR2^S3d-F_Z3p8TZruwan=9CQwMckfEtv)gNR
+zo~?@oRmnWMrL@pVwdte+CIZw5*`!xjQd3hGJO3l0oA{`i`R%dRY#TM4gDLyQTp`vG
+z=>LRCWCcse9ZRE(eZ-zTbgmzBg2%i|zRS4%2VRMhJh>MI<5xR<_DehKsh=+=K_33U
+zY{qF6kr`0ylolq*H;_BR%S;q^7FI{xI|7n+<OCK?P!llTv*)fvJbyw4ctfvx@_twh
+zHbr_b2G%WRQ8Rm{jGbj^{vsgh393HlnDhI8ggM&=poIylOBW`MGcMD65R8Dty#$?b
+zR;S4XOB{U&x|i9b)mf`cANh~KUPbVL8AgF6fQup6pceP#?#2^D?xkJ<=r@*UPvm0$
+z?aJyX#ES>Y?cZ7FM|2-@lN?Y1UPOmR-}oa_TB|kqBqb$*tYh2xkLb?oAoD(1C;XiN
+zja&(3Stz39NYaUMI!A<|U#oJOj7nj&0=?#q4Jf*&f830H)CkybQQkH|ma@JglW1yc
+zJosDsx)P6aAd7%WHG7>O%)I(GUJrzY2sNrl_{%Dv7>pQn(G!086Y>Sd))9DlJliJK
+zK+J`Fd8)2TuTu}if(XYjKjs#u_h@#&#-nyG6*lF2?d;*u<+?;bqw!JwJfk$PvQ(YS
+zIqPeaA6Rn2S7B@M9>%rl8p|&QMC@yRj@f7Z><=Fof91@L8-|w^_NZDvARlwi8LM`q
+zer(Z_`dh5bkC<WVUg|ZX-7<Ld)?D|EivVgFL`;|OYTW|=44X%Qolb|?Uj2EqcLZ|b
+zk-6cTx8`3L^E+P~R+P`}WRO;Uz$sncFE#*J?Bxr%83^IO538NElT{_wE8%|tlX4T;
+
+literal 0
+HcmV?d00001
+
+diff --git a/app/src/main/res/mipmap-xxhdpi/substratum_masquerade.png b/app/src/main/res/mipmap-xxhdpi/substratum_masquerade.png
+deleted file mode 100755
+index 42ceea8993a7d808ef8de18fa11e13bec5423b14..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 3894
+zcmb7HcR1S%_qVm`idjWOmfadvqxFr77(qy`@%GlN9kdih#4RN%Xo=MtH4=N2TD=s}
+zmKs;9LQ$fqJ!-|M+x@!lpTGZp=XuUK&+|F+k8_^$oMeKn#cx8gLR?&2zgc2U?T&T&
+zzkGuCSh8}(Q@FTzz7lW_W;p+lW90u!5fKr)ySx7<>IE+LoPK0yXXp6%c!O2sX^`C5
+z*jT64Z3hR3k&zKk6*NrUKVH>L`|=~WC|n0k&)2jYKm~Lg`qf}VE3hH6v$M2wHf?Qf
+z91aKPkhW=hyIa<!Ps!(!Rc())>m<ap2kiC=at&o&x}oGhrQo>^_c643%vSQ-P$c%q
+zyG<#0t;2n{%wjf>et>mFkG%T^!fy)h!&da0f_j_${$vW~-K*$rgkwx0e2ww-Y-J)F
+z>D!C++EDi2#QgaR>Twxg-wXHbMR>h}d7y0S46PqGD|w>{^#JZMTgAUY-mM9CZOc5S
+z5#rVez1FDU-h}krPzmT$_BFJsZdUYaLU=a8Jy2E*L+i?Qm=DUjY*OB%0qoi><6>x4
+zV`BFNz*QRI=zvuj3Rix~{Js&c<TAbvu*?ClnE<ZV)V=|*Dl&6uxMY@$vU+4_kq%%V
+z^r`q6TBP+ruD!b)`~>W3WR+)xD+4U^UqajgTr~^f+^z7(6FG<C($34cVk7Iq!^6XO
+z6RUHxbC!QR8J54+4RIfkci+^z)vn;y1#$Zu?iB>kZtEv5!Mx{S-W@Qn5x933(yI>a
+zvbD9Xiz!24?=LA4olNq^<vli0q%uYS8l-=baxf{bjiu<jiuC)W5%g5SovIaMV_uT3
+zL2iOMjYGXZD0sCZJZBKT_PFw=>VC`@EN_!sdzTyoY<0Jik14kDqatw`6J=_fzPh?f
+zyjhl~7SW~?l>Xr9-@df|!O0k%pmAm5Fs1A|sf%6Ov1JjPqZw{ZsGWrSj4S%SSMm!q
+zyn}VDj!%Bpi6KplP7E3aCTsZQm#`k54a3@0J9@Rup(Cmvzvwc(HS(;x>3V9lBW-(o
+z+cT(he}8{xXUB!ue$S0@JXD`HHrO9F78VxV13RAE-+eK5L*{sbb#B?&+H*;%n(*=n
+z@qrXh>2nJRi=RV6$7ErEj^01CXF=h%R%7Q}70Bdiqy5bNX=j%`YR<w`LVEe}=A3G?
+zG(|g*xHrdpBPG$I{UP4I<OuhpS2J0NpkMqk-7`NDC5|Md3*?SF+Ww%Y{J}&2o^v?4
+zfn2S$Jr}mLzoBn9U0<gpziM;OOE%GCuOdvIauSyNGD_kiNMbfS`=<4U-^2>~g=i|8
+zN0rm3b)xLsX!^iH>72q@e0GV_$Hp=X0r*Rj{iJnN_P|FQ&nNCBx2PPYrkom!B&}cj
+z>W6u-^!{I}qa!zbWd&;c6K<yW&y9QO)JJ=qe7!f}oR*4mZ?_nMiU<y2t$uvop6LJ)
+zjHj<O9p&>BXDGuGyF^Kyg^Q3`nJvArik(-9_g42po=FXT>xik*mijG1ZbNSmA9%$J
+ze~Va#iNnn!dke3e;g7Gdo!Obb*(e-c1A+tE9$V)Y7<zf{_@j|ny-#;c*3n)L;|J-2
+z=S>CHs|Q+tg6Bsl*x}lxNs){+tt927i<Xh*i!Xl)3>BfG=MuvX;;^FgSG#nK#xSvA
+zxo~9oh1*bmyNV4XlY~k--7j}Vcc?QXvO!ScL_XJ-i5$mp?WE6VySN46bLcm=9T<}6
+z33lwJ%eXWGCH`t4r{#pD;jdPzk?4h}y(K^C+Riics^`=uDBUd(zWC2|m8UV#1tYU5
+zM{Y65E$FzU*{H@tOR`n-z2+LOQL+{>1-UL=dc-qqhf=W$aRG%u`7)6+o_PmuA63WV
+zH&xged92?0RM3#;G4eM8E!vDytWm#Ge|@I?i|`hjZkd%<h&cUvZ0kYJ6-ChtGh0Fp
+z4T9Ye;;%$2>DO=mh4m`~=<Cln5Ls$laSnnJv<w)2<p;F!3gf`x<b@x>wCOu_egnMK
+zeKdvV^A|6PlFT74&CF4z(3SW~D|&JE1b@zguN;+wcXX}Q36G2nUcYMA7jcVvQA<(E
+zJmAH2tNhi-|559y(5wsde@A70lKB4im(s~)NVQ*Aw-<%DU?5nPZ!ViOxTE+j;hVp4
+z5tv-0RV|_f`RWXei_=fQ51G4b#-5zLT&3YS;@Z+xJ*qH5JUPdd^l^a6E`4cyM2d5}
+zeK`K5m?X?cB<}pe4G-Oz{;N<6PFkRdCHJ>WLkzmmT%D+c9bsCW(9qJ=%k0REOP4^e
+z7CD6HT5#Z|_`_9xAO?Lz1P&sGg_n)9akgEoNKWQdugy0>(6+RBf?(Q*Q~2j_V#8@Z
+ziyiI*JtUCXlhrvMVtFa{XTa*um^&Ml*NL`Tw5NC}S%Az!%|e<`e3lnkC4qkljx5}4
+zR0#CXUgl1)IAs?$1|h50o~erb<|yYNT34r;vSIOZ-c1A#K(UK3>_ZEIc4E|<)9>La
+zy`G1<)sjWR5aSBvGuTCL({-7*6h(cv(6|YtXs;)S&{oY{@|U1N%J$QHFjxDHna(qG
+zYio(p?nZ)&xPNW%@QX%N9NM6zD00>VDYe!))~ffrv&1Pg;hrRmq1)_N5|T?RRjBM<
+zd!8GSl=^eapB@Ow^bKU}nB(xye@h7Qklj9siI!hhk0N(~Ax`$B%kM9k-Ou<wM>9Z)
+z{i3h(lGaimlm2oGClR&hj@bDQ9h@LoY3f;8u&7Wl=t0yh2|-n!>fn?_(*p%lVh!wU
+zMl#?j(-8SJMDsc43frN%#U&z;4ao=EW|kTC<2(VeXw#7CMG54~_I?ztlIk?!vgD@0
+z%Kt6(?sIUpgCXsE2V5La5M_SfE2@r4wJ>gJtbMY1?cNpry+^)K<GdR98m4FOyQuI%
+z)07*|2ND|n6B`WN8hSd+C{GR52J%$k#z{huuS%j=z;l$$wQO6w-tJ5J8g#bL3nZBe
+z@1|e@a=HlyG*VRE&Dm?=863LUQ5`s>yYHmmuJ>4j>`hl;68I~T=euO;`Be=_Wz|ud
+zv^%5EHVcLVV`s)3i5!8IMnGxpuC&{leiQQi{dpsFfCy@)Ff!^oi&9s|Pl$M%p`EN9
+z_+CR^$5$Bq&g*CVF5wST1m_<f=4S6pO!^qaI5sIMwl-PY;bV4G_g`2L51m73W$!DD
+z`2MWZv%e8Xsbn%;fjienqI;;%D+SjL4m?{2c?TqyMN?%RU))y<eWO6=+vAeGe?pX>
+z2UYgDb8k?+V_yUWrK;%rh1DcpPkZ+cBvy2dPIUlt2$6Xfrc&vn&t$&Qb7jRrd5{#E
+zumE{r8o|ufJQ~2pUK`vFgxmA>Z=ny0Vp*6X@HC~W;{yWWX#6#%fOr;k6_WCW54Dtq
+z3ETI9z(U$ARw~acrjcsEMSlzkuEBIIwiunH4^kHSD&TTKQ8)@aEJhov+a;JaPTArg
+zyr!r?*Zk`QGnA8lR&o+dxQ+3jE*^~8hcSVJowL)tRmFwtFch{tZgCmCw9K#+OSKUE
+zS~EG>=ygSd5IWGyn2FLHM$o?Br~)Xxi_#9BGd@g>P_SjUL5hN!D^h0c4O3><H@7nX
+z**EZ0ypM<PVMW!*(3_Te^*6AUr~}o(!JW$@)TPMy;`l}8M-nYLL6Ehb`mBqZt<3GM
+zZ5IM6F^OJVIST6ms_DVckB?P!uQo0Gd)wy&=Pxn!E8Gc4`Gh{FSiNk@>OWrdje4T~
+zpu&5168f+`sH7HZ5J%^``*0FH*mWpe!XM=N9^&-#=W5_fULs3EFis2{UuHmh45snb
+zbuH3hsF3urgxS~gC#424I_j4Z;zfMv3#^$ZsilAg?KE4b`72$s8>5SdS6vN$-wZ1%
+zr9BX2z3D0Tw~ew@x|84K+7VF~C;}NQr$TZEqRa(!b*b9{px+sQo@r8npDDpU)MPlj
+z-wq-NzqAf$r#Bm<C8w!_Tyr3DB?>VHSNBcPzo#bi^{Zu5As-o8x_CA0P2#VgR%GIE
+z#UAT6@S~m&e+e7-k)Vi*R*{A{-9Dlu$1BUXScl<|iL1{Os1*-m&qx;H1h+;DSUT$r
+z7ZTTxB*mooRVy%3s~dAOfhx3|m(QLQ+ah_=x3;$CI-K{JQ@M%TR-ol>42ZW%M+VSl
+z?VPSDT1gO*S5u@4?J&k8xxGu&PeKP*{1p2E2i1Q)U%#Xb0IE>-hH7faTJkQTb{Fzh
+z9Jr9IEMvOgoar?q1qSL|NNcXUtfdDrS^o6f-#+{7XL>n~zRN!iahm1i63_Fu472kc
+z!PX&F|BF@12g|esH#}B0_>U^kq6&D^Sy=k*lBiw=Gy2F#45HRabqfzLLkpyrU}iDp
+zyM%4jt>^Zd^4>#%A}9`+?NHP=6p~7KcwFXRD-1~Oqm1d6=_X)9*OHG2M$15p%RZ9Q
+z6h}_Ml&`WXj#xkPm4z~8YYl4+%7Az8?(VaO(0Fkbi5sdSj_2m{-ZH6~Xy1{c9?nzi
+zdF~}t%kTTIjHg)bHp~94#Uzm^P#qs0j@8#RaEJm)y~Thi%2H*I4#8?nYG^;hc|}H=
+z=tjcc<cb32`2o-XvWVkb%%fS%&Kn&KR_7{V{o--PIKKjV{YS1nh}v$O+q{fI5}EjC
+zJa5%}N9XyAzN|l;5BX^7!AaYdaYnCznR6#=5>`uiT)TWkKiTkLiC?$&3iemk=byEy
+z9&be~Tz2E+@R!jr$+fwNZMH_dN=o-X!5?nSl!iYpQ01%jgd;1tz+~g;$s~B&J&>Pd
+z)W|ore`sY#o12?1IlOgC#45xRl&CJ&(tK7#)-T@0>vL`$Ln8wNqw<K{2{KH4B$7G9
+z_J+U(&~pZoB(9b$E_R`9Kz)IT9e=Fk-4NkKu+T+Ga@CWfEQV&)I<%J)KVg1wrsvuK
+zgN0!z3$&xwTNWZ)N%_~4CWsK!-K&g56Op@+jVv8`r;-d^sK@Ix!jT+hYS2@xytQ}?
+zZAY~%?9|;BmW^>8D41-}wb%OoAB?A6>9SmW9<zaAfE0gJ_waD%sgq3xuq((Is2%6c
+zK%<!}3_-K}#f_pDP<*7kjp~KXg({p=8cgbut(~`$YD5>cf008PHZciyes-c9`R@~J
+MX=ZC$Z*ndEzb5@DPXGV_
+
+diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..4448e0b97e8333ae8b1ac5915a22ea9b33303ce4
+GIT binary patch
+literal 4947
+zcmaKwc{J4D|Ho%BQDkH-VkTp&ltTDe<~?>HqZMVBT~ZP;W`-8Bru0d|Oeurx3Z=|g
+zvV_oNC;Q%5!;IN}KIixU_nv!S_uTt>oO562p6BzNd+uX<JInq1WcPtUp#5j9%r5NC
+zwEu<>bT@iKvd(}&kZpV0OXjxzw{~~_X9NGwEa>OYpZ^a81O%j~zo@o}YHDg~HuO_D
+zc|A(m_%x99)dsJwm$baRTyn-m2lFV)B=D_Sa7jr?o|co{mDCTH6INDM78VwEBbiJ-
+zV?s7HHJzWIS5R~RV;0>Y>)fp3t#AFlQO;#R!L#wO>mb5&LD73r#oxfXs8QZ^QQ2=u
+z#di?lwV>$3Fvl+{`3@=j4(<Xd-@zl-2a(qs<=wt1U&mle22nmks(wRCK4{z5e@t=T
+zl)Rdd9*e5}0Jh{C%FD<m_nV@pflZOgh0>-Y9!(1FMmBGoRedT|t{dBzG$D4EZwzhT
+z4XN#(TTpSt-Q;{J!vbHf;98DwE0cFMv@J5QE-Xj7S15TJohz<X_NqX6Rw#Nbss)%`
+zEL%9@jXqaoU_%*@_b{}jG#qj+JM3a)^V-n*E!vihwj~+a<dhzEHnPqHu*C*ePiYFS
+z29_yC)>#JT3ICYK8`>5&BHRqm{;O|IF+7`LXqB#Sk&L#@L0e~hM+X|7B^y{iF|bGq
+z!%-R)+*?tO1&7anMqK`aaJiq<+@;|0M=y+`99WC+{EqUPn3!slcbio9OFtQ*ZIWwo
+zxqRk`_neAfkFqyW`@wIN&(f)odwTKX$m_jGuTLn?9M#}DC9hwHJ^YQHM#OxoQgCTd
+z@n$SB@|CWdT2Ypc1@)r5Yk|8pnnB|x;eXKgTre+f&EGg!lFhMCJq@Er5MI5ieuawH
+z?QPyY){G7@NI8owSTK#E+7Z70nEq7TK2qD3OQ~;e{gv|U!=S2P#S!0__)>-?;i^w%
+z*JH|0%uRQzY!ClzU$cxWrpe_<9~;Zewj0l9+xtffsh3=`>FqsjMDnh*Ha9od*VhYO
+z{`Cs}vMZ$MkgCjpocN%Uxw*NKk&(dgdhc6xzM<5`#U=kcw9MC?xsujFyQ<Z`bHVN+
+zNJLm=4@v?G-fO67c2EF*NLoihUE?NTqVy!sVLl>keyXOorp(S&uXt|qSAK8o?3tRN
+z&O*2%2n6pxYi4{YV(3pFET!p`$is<qDupE2mS*C=FP90q;wQAU%pINRD+2uEK5zTw
+zL_zBPRLx=RdOYYA>j^D27If+!YjA0D@=>HTM18CFn|jyA=s&(U!I+pZeT|BuAK|m<
+zQ1Bl1(x3C?x)gPRTrNCerXQxmc9Q|A?1mal*$|(sXZ?j1#J}%pntKzhuMBMYZr*8l
+z&>FdzVzmlv`l=>+xr*H*?i=;E3v9XND5U&jJPrOH)i2dMJ;&qL=kJ>owO<k*_I9db
+z+@fc3mloeA3e8aq3Kw@;c&xSheBm0($HO5?$qi~lRX3tEBs@CuZ&=6<C&~5<nEdj&
+z1$%zAZG=?H{Cy%@D(e}Wi{#LwhQ2N>$;rv=?{hVvGQ-FANxunENjj2fqtX(TBAf@a
+zq1)Klw5M@}_f__)zGc18iR{<dqnW!gwJAfoHmM<8qqKjV6L`GvSm^2rAC7JVw_egQ
+zim|>1HZ=n+xjEQpyX5VNPR}gxWbPl}=~q@}D3%CAo`R=&=CIzE-c2B<1tSlh2Y7t`
+zV|!YWC2>Qeqo|{e23jXRP0G6am?{Jc4Mc=L+Y)N!y6yjU<rMEA`9rvoA+mznoJPko
+zCBV1&22m$2Kc877oHqpjliUP;3<`N5KPf!UUHS6o$0alksd4;kO|}s-vA(|kqehB=
+z0=R*{H5C&yt1BdP>C+D;bfR=)2(b-2%}d>`_?DXgH?~4Imh%#9d&(3C4&c8KQZvJ3
+zY9ezD)!ZK`fNbsT^rP1VPVqFhD%yy)ePc@9h%dZsS^iRL4e^eE73Uo^qllYw8p9w+
+z@V48CleUkkk)Tuh0V-wSGF1-ZC%AkkPB3F{+a^_ZO6wGAgY=w<qS=l^O44}MOkyjK
+z15yF1UJ?WQtq7V(s^PWymMIO0g@vm@Ja`v*(F;b(fMslXF(iOWARXAn-34%Wjzb~{
+zI!HmVv?5Ht@-&>hMv|)a@IRI?rN=92);bN!P4CP1OP1}qVw~<Srf#wLFQYyfs#PVK
+zACjWze84H=kYl&RWvc>dPqCiLXNrfB5blpp{NW!Z+anbEiWIJHn_#jrBf$9yVs_PR
+zzL10rKw?C*8b2by9|EyA;33V$)N3b@n8p(+wMHPueDR@IQ1#1{5mqk*(h!nxt6z}F
+zpYKU)d4tg6IIg>Tb=H$6cs14-_r~SaS~O@l`Os-{n<eoPL>K>1SfHq{C#HXT8FVnE
+zTO9Ncoakornl@M(0)9-|A0D!bBHmph@ohuTO4IgbaqFNE9q_>)<CCC)0)x`4%(J%J
+z6md1bo%-r^OAbun4|gC%J{(@FM=Q8UakZmq%(tZyh4zUMsBL35_-(K&cVP6EDgY_#
+z*V5<r?_3MwDlj84Su#)fxJ1Af4}ifh@KtM#c7n9kBpTJZxZj~HGCF{(ztN(&CcZC&
+zMBqsiGC04X5CzV_azZ1xj81X0Q~z3A%E>GK_1@;OnFx4P&GClF0?vle5lE~=NN^m#
+zO@JunaTmwsuDQAvxa~=Sk=@l&CpHp!@e|vUps$tyM9mlvnjIUPY{B=xm<)P%VIk_J
+zh5>S?ae7-)Y;}$zz=Af+j>|yp?X!=DHKuNQS9XUINNp?1Nb-2gR?_g{@x;N@;vyOA
+z2;)&TwGe!HDT|BZbM|h6R$Z9efBw-uYDDWFn*{|ioWM-IYk7`MbH2O}%Pp-dXiRVF
+zs8Nvos^1j2?D@Cb2uNg^;{s$V6j0geg?8ZYfv%Jr(qs~$?i;MzF)*dF2sew>77;21
+z6bAhJN|GzNTHla*VtQlHB#X7?b)uOwk<3f?#Kg$}RpB?iy=b~;Y2R>n$-f$znR)Lr
+z#{d=PS6Dv#^kL(_UZK+Xpe;pwq*SfMNNVgUxoTnjJEACnFM?1Hu3nZUN=TwcNVX~$
+z$6s-*!MD+zh7P=XxY=LeYWb?yMkMXwgJnzbhS!_1Xkw<D2<@)jB7TGF(F1&pSTlsr
+z+br-(PIgW^2+iAXtc*B$;OpYTmY_v=UC6$p%PrbHrDE<a;aezByZjM;`ZVlBmNzy(
+z0t_^2%5rd-<J$*A?8n$L8UY`N{B|$sTr8Du>N4r(^2d^fNpnFy>l4GkrnjY#qDNX+
+zS6URfoGWo-J8rZFErBwbCdi(sk0&!N@Vy|}V{dUx2J}Z~aqP`#;xYEk>n~wtW3L5Y
+z!CF2@^Ur=Giain{@Itre3fsl`I6xEwUWMmHJ(DEH{vrqq_59AQe^1)iR|F}n+;J;U
+zl1aC~=(TY(RG9Drfsoqn@y=AD^YYKQ=EaIZ>Xq#1zA?gYJrHE|LF8tvS1~M35@M1E
+zyKk%=v4%p1FLc6{;&}xDQ=?8(;3Gs%B>1mq%q)PsKGBkAIfD<Sor2f=9q%&9{j6`!
+z=?(DOLh(cj`Lf1>R+LY`VLOGuqeLp*0b|wBGH}RwC+-$Rz!n{W*5>E-<a;Jtc=5ad
+zG_c`2F(=p!ZE$p!5Y}ecQ%J-o$s}=SCOia?n%+Rf{q{)g*5ch`E`Sbk#JahT)sD1X
+zBbkWeii(SGME>d_5ZyrFOHZhNBDhF?Gt^p+7hH~^>l^LCto*?4y+A#Pfz9ATXEga7
+z^JfTfF)aHx#3Y#YVJN?N%=t;DPrLVzuGlUrz&&EVS}kohk@{IQWcHbxRoNyA1mj3~
+zz;oatm>+ALC0(vRGZ|Elluf>C(F=*y!3VLcj_cr$wU)2Ndfp;VJuYRRt^^c%)Ib-7
+zLZ&OvrGDplf*-|VWfI=%GVgb;q#nW_i$4a#tTd<gPU9ur8H{uyB<b(N26q)NMvG_W
+zRiYM&b4Q@k?P*9{Pgt~%n8a_m3KNx<A-#zZyEEOWQQ{*}cGD5-HwSA%OnZ^sCM}IA
+zT}1NCG%jekYQg9NU;!(dIJ4UP^ES{&r!Sz8YE#4P^793V+~XqEH@}aHbH0vXwO9wb
+zr%of)uDz;6ydPv1#2_XlraRzYixD<zf;Dqq^eTK&cIo;*oqdxnW&n(gF(P+<M9770
+zd7((7JLk#L-9BBq?DOtZmLSH}f%?=mAkALr$?6QR^OdgKEi_2yK=7KECNQf3itHj0
+zw61mbq+ZT>FuHRZ0lsme?ki&WYUp1tR9jg#V1Z8st1_FLn@e23F1&;TjH*fkJx{JJ
+zUJSVw1s97GNn068g;)Yxk}yp|66Y9L+pK1?QA1jfRi1WIa)8|nofmgS15$rNg=oQ?
+z;~}f0OTq&WlU>5tEdOi42CQ<5OIyZsz-Rq9j5E_Isy7_psFy;03Wn02e6KipIWS<D
+z_5d?LOL`6U(TRpf35UzQcpC82%o|x>k>q}cDC@3qeJsgxr)zYhedY&)y$?-AZ)?2u
+z+T6q9Vvi0j&Y?W(?-Cw^v&kDdlX3V295w9I)v{3%f!IC#fOwK4=?_FZ6-f)=tyzH7
+z!S1$_d+63BiWPeyXfv?GW=2DkJk_hDqK4*$rAja;E2^sM1CwLkn3huiY`}qs<V4cu
+zp7q8$%u+-MJa8s8g+gZaMI`d;?+0ERXA1A{j%24O2vaxJpW^q7*xmp~TC>~Xem=eJ
+zGlVVmH2i11Bhq@jpU})fd4(K620uj?o}p-5<ox}<O+9HyURZb7kE>((7jf@Gq-%Jw
+z(9BG%(L%K%V@{yTosK<Dhyjl)kLhnSnf=N?sc7T=wH1AGfxgJ(5h3as9jX@1uA8CH
+z<3Pm%@SnlB1&Y-UL%_wFZ3FMd$qLmy7oui^(9w>p6P$N~en#>Lc-0Of`8nxiXIE@G
+z^)INC60-%ov0^Ulp*~-Kp+I!qaYFJr{0sQc%Xu;x)M=t=TQzJ5o3={7ynQ8xIzlw<
+zbgS-|ovL<K<8#@GA;$BBYeBTv({b|tz`3`4p40YSx!qy}T-ORpx#>*&j^=*pFQOcl
+zASX|E<DOyGS^SmgHcI&_J?zvta&-=|>N9GX<BuPBrixmiG;Px_j{|y+Y++II)#gWD
+zEm)hG|JcbYQiq!-=;KsMpByPgi6lOTeGx<Yok{HgC5+8XUc|DuQDa!$*QDQ*8U{2K
+zvJ5^D$z`)OZ~#|G@Cj>4S#b5B_L%-{7W3#j_b;9EN_cT*&xb1?eV^Xkd_a<|BL(Yl
+z|8}`~Vgg;%S$*PlbN^N1x%*HRhWkD2^o_l_2_vc0kxs5r3T0%wzZdHK!s+YF?gf-f
+zNF}S3;V_Jib~=uu#F1p-GZn6XY$x{p>q$4j$+GYPYW&~*_dE89hzV<x1W4i&V+QXz
+zkdIf3Sla7c$-VsFqyWKRJgGon7iHqFrbjm=+}y8BvOtr>`?3Ar{ABAdXD;Fv+4ux!
+zAp0G6M(~$TAoU{dY9uZ|+Zl+qW7&x2d+Rvh7LOB9x47EpSc&j_*0%)gsytv`LaK}7
+zH9!NdWxke4b_UKl11{+b_U*mSYY6loHcp(kD%Bs3uy1z?RAYX)!%dIC??^D;%VHRL
+zE0nY)O6w+Fd)46#(UXLYeu0&kaHCE}zJum3Y_x4ZTcX%(IG~YREbZIezqAedL)LKx
+zOv_2q&PkGZ7uI366i@4(+of8k=)M~cfcx@>|H9f0TFx`pdj*Wv3oNP&E76Y?-c2EO
+z>I}#e55^c#T<U(m#_*+De8bAtCLo$&Bw4?`lfmTC6E8sP7_^(!aSeR@*mfeA@)GJQ
+zBS+qp6y~$vt?&=Bc=XEeg5+vvbh2UH!4PYQ%{!#q@R~{}$>$^qoI_Gc8Iudq*e2Fe
+zweh*HTPVD-t!0F+$H9cD2zpb!ogaglngxp$6Y2Q6H?Flc;Xw^l;^Oe@(c39mM2V2+
+z=)LcxDsq<WI>)>A)=|S_;-+(<DY~R!e(<4P@7`Zr1KX#-^_Y7O3oaAshJGM#=a+-Z
+zH3~RARvkDFk%JG%)SgPv+F=uugh!n<V%C<4TqA8n4VOuD*?t4-YS{Dkb|}?-DQ*_}
+z0YT={7Tnr@9U`LN98K&kbC&~k3fiI3<MD96TJQ5u$#E%n8fvWHjb^dib;L4v4L&}N
+zg5ISB-|#NfdnF)3cDiuj#0@P2H+CIdzRZ%$1u+CoqlR-n2cNFK@a$Ndnurjyp&oYn
+zr`@aGKZmD#r$(nbbJ<mnKPXpZ6>By+M7eD*gx`33EJf-=!fpr`JZ?1wB<N?iT0M-9
+zCZ;`6{u?^|QvNgM-B8ADfux=SlbtnKl7mUw9m#lcDPJpqcTH>J9xR9F1iPKX*{o(V
+z41zVqle`H$b-OshZ>Uv~eoOYPJC_vRs3q)n#eNy{te3j+r)LiQ_px=>+|I1T#69-^
+E0A=T)LjV8(
+
+literal 0
+HcmV?d00001
+
+diff --git a/app/src/main/res/mipmap-xxxhdpi/substratum_masquerade.png b/app/src/main/res/mipmap-xxxhdpi/substratum_masquerade.png
+deleted file mode 100755
+index 53658674bdc6209cf4ada657cfa150bc21338d60..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+HcmV?d00001
+
+literal 4966
+zcmaJ_c{J4B|DOeemeFDhGo(-{DqCS@kQ8b}8)Xm4GK!G&nMp+@OY($Lqoj#EmTX1L
+z*vrmXBFoq^#x`SO#;m`2&hP*4J@>xvx$paWujh5{ANPsVS-UOryW~M2(3aDuPB=?;
+z%7051Dv^GWY-11z!f|puZ|fL%Q=<8w1N=X4L4AFF{|~rv<3@UVMwP?erlzJAi|aav
+zuHD^hbr_vp>j3DRJzHL0E;4pE!Ng}<2YomZT2xf@@u=(Biz($7p02E{%+JqDWKK?w
+zv2~7(jSZbnN9cJip19Yr>uQUR-*NlTjcV?{5I&9S9>YkV`Q3gDoj`N@!bS}b#@_2A
+zIswB--}&ACOSS-GPr%6DfME$hJ78Gr+OX!eMh(yAz1J|<qG4_S5#8$}d;C$3?-p%h
+zoA>xOX?ins1JT%`W^G?fhur4fKIRUE*3QLETHZ|vFH46HExP_?I@he6ikgs;<9iE-
+zyb(P~-25+EUe@P|mrequ2#->vXNiV~g=3+)eL<<F*Oxs$mS_Gg+w1#9+vm$}Z-(BD
+z6D}q5T7IZAh2{>VUmD&Pj-&?Ft0n60mJaVM>_4C!b5M>%ONY0`>Q^o8U!k%8nx9Ie
+zAUw?Nk}d7C&266^vwdvgSkQ>{v^f3pxIM|@bf(3r^y4R!P>yd=_L*&{AdAyE=60#(
+zC!gJpB{d?verR9LS3g&cyzm|A{@__lH^O_->^5m{Pz}<jP1|>Ja{8x+=hWWo>4&0?
+ztaDFZD4o^vo71`8yVs9k^l)6;f59;9j@e_H=CuJ$-*4JJZ*@cK_W1Uzdk0#kMc@Bc
+ziEwYw@mpG0`n1Q*<`ijRfAD~|Uky6)>ycoZb<`s2jyopH(f0i%yBu3=nvcc3QKavH
+z?)8G**PI;ko*cOsW}bW+n?G-JmwXoA);063rhT;LXD+F}<wt+=i}GRJ>tD12?msSG
+zvctRimvuiO^<i*cr`~u6z6m&ydC?}RRMX$V?$ytbfBv)&jTVs4yT75f_x>d0NT{{8
+zwzj&uTHyZDH}t!NNcX}jU)^~7IJjtTZf<mRG$^Xx?`B;<IGMp%2#lb-de@n|(>_?j
+zTB8W(vo4^`n{;HfcR;~%7DrB~NWoQ=Oc448IJEVi)Q^|w(YNW-UkARHob@pKcW$cx
+z(?HxG<FCP;1@PS<5S(%Pgw^?I@Z#X3&}PHUk0$pfF?{b^`uAqe3nxB}Ze7V#-CbBX
+zwjw8@el$E{^cM{(xhif5M*oCDg8$e+(6SK9%xpk$Vxk(uRkq~UiW2+1UgTVMtu#n_
+zaQ=M<`{S=Hm#tDNi?~(T?07b66#7~SZ=21KHsO0JfpjF|E5>j{oMT~MOSvI_aLXna
+z2aef0u|C%s(f0613?t<fb7E~yH__KaVFRkjYh0{bLS`e9k6F#Y(>X;31@jys|K}(9
+zDIjN1x$<)Xqk6j1l24rtP~O&scOF_Bs1mXzZ29b$q-doe)x%O99dlo{etg$RZ8B2E
+z<oET{si(sd)T79tTib8kjxD$-k`t{I#f)tf<|qh;2jh#|+dqFUl^q%U&6*G<cg1ab
+zU=n+{B{!mZzBg;o^_7R4n_KvLcgJ?tHu;UY3-{cURkP&6>uw9!-W*-&XVc(McvtV6
+z&!upOo5gL*a7XaSARlf?Ns#FgwjXt9ie1!jN-q&-s*a&M7WJ=6FQ-WTrKbwYjyiB-
+z8>D4}KX*<jfxZXVl0Sw^hl%y;PED00T>d%xL8b`_+1(Kc;{>{bNAY=Og{d<Jo9={U
+zRDzVn3I;fQCK^HD^N}|a6Xls?y<oz5TNO=9Q1r2{*dv7H6?i3+4uL+U{4sKZ>BScm
+zjIe_CA0Y4zV8?CSOl5UG(bJ~jbdh(Z$T?GU6wTCZLB0PW_2iNXeg@)0G2Ww0;KQ^u
+zH$ha42nP(MJ5mimnc3L_Qs<$}-}ElM<P25vvv`t=4t6xd54$U^G5da;qjT{6+QELq
+zBk4h()hjjpB_lm?>-YNbsFd8GJDm02&=^$x3b}jBv4kS7yaLz!CfJqIng^Wa_~Jd-
+zVwSF(9~fy%sK;4=I0?piG6F1KUi8e6mWVXvZ-LSLyZz&gKr#2l%2OabU`dd}4Nho^
+zjWKm3<WcT6ij*rRw}<!=7QK-Nqsc_tLw2b>0=y{!WZvo3dmC#6*D_IT&td?f0~tWL
+zLp(`25o&2-_G^NCiro$;fP&|@g=6G^Knh8Pa#&iUsU7aMLfOT&gwk;7yfOg#)#=mb
+zIR(BdL3pQ%1xj+phm;T$60Yc{AqcO_rXEj%l*84oiM+sCA!fE@E{NHk#ZGurjZ|a#
+zqTTsSn=)kVY>JeeY|=0fV1fHzB6^|nu~L`yQn}EpnlqJOj(pb{1{<nd!JW9A`KtGN
+z3fx&`1>Qgy>;Vxx`gBOt(f|}d${`OXqA))c2$P%RpkBsM*iVv1xS)5xk{Q4p6L{mn
+zT4l()b8qVB<DmWh{py!(&})CSH9H`)xc#d`)D>+Ey;X*P29WVeNH!DTQRc~(S2oq{
+zP$=C*$EnTY69F_>>j`mz7>XiTT4*!zuiNJmN@9V=GV*@$!>~Pxkl8A<SWkTAZw5kP
+zJMSAjrW59L<vW-1DzTMqBc6(1{g!y)!VU$e#JI_Xv8@vWXe)Kib>COWg|*E&$lujg
+ztYd;IUs08P0%&Ih$uJfeLn2u1&`%kBRf<#!v<DrSj^tkw`J6cTx^sM~JmdO+5ZCMJ
+zjQK$%%8kQi&E?1lLfq){;Ddt8HoL*KOE{J34A?^mxAZB{2o6=`m%a;_*Y|KbDBov-
+zraEy&lS85c8kn|8ps09o!2%w}_lyB?Cf^z{w^c;zJm@YK9BxhNMH*q2TV+T!uwb>j
+zpkR7z02IO~ksIFnx>%68oB@*+#)Arn)<9KPw-Vmi-~M<3qo@@4I7lQXji-RV5gm%C
+z!p&c2rdrREoS#IT*kHAeNX_Dd3emd^a~v6SEh#G+YGJL~IS2l^-Z{9h&d;^CfkVvM
+zfJ$?jzGiXDt!Jybr90fY)B`S?KIAFQa$xRkn7kf)x;18U2pW1<h9;F&veY_pE|;dv
+zhf1k%|Ato+9lH92zvHh8e(lrE$!%lh5^$cJb|{3WN~x{sVzGxxf++E9_mD`8O+U-v
+z>UG5ps8qT%o6HX_Uj6<1RTP@S9D7cP=wJ7JxeQnElC(#bl2%5apt0i4nts+zpQjML
+zZ?><O5k$_=kH^hx@Zz++4m@is;jiONUH7)T+=OmZtsLXrLkcObn=xQv9ptZ;l&VPh
+ztCAnJ%B+zIHmik!8PfTL^_U>707-udHji|XKscuTr#Ev9p6Vigws;=QSm{}M6!&dV
+zPuvZWN%wusoWr@|k8miB_yReY7f7&F$|D{DN%xv6Id#3UwaEvRV(Ch$=97Y*K{L|L
+z3QQ$=TC091x)TrDaC<P?O}Msdnt|Ag)?3GCnwOID^OIms;ArbLJukvV!!ro4Kfaz~
+z-q5h-TGgI&VT*|E5#spK#-p9ua9QwQbLpN1>6c={EoF3NUN_A^o?Z$C%2;R;@=v^J
+zDmwda^wf>;qk?_hJA}&}>*TdT?EL_+kR2<XUc)+;ZAD+Hms2PSogQCy;UG{Q-f$$!
+z;&z1`(Q{)5(;QX;9dFqE(!<M@`e#E<&y<^`$;6fl^-tv$MH1_~4p?$Sm~7BZffkKh
+z>Q;-{aDeNXGQX|F&Y54y^pYSA2q2{o)n?6e{5fa?m|i@vNk7Qr3Am?S;K*DL0_)Qq
+z6$gg{@uFwT0R#^a{xo>j8CCV6n{IIE)Sm5c-k6}lC^A|2ApIOro3mYHf0OVSi>_*-
+z+kxa4FFgzy{XHW8EvbqO)FX3=V0{Al%PkMT5<;<>;8#kEu!|O8<=lDb$2B(%i;YU;
+zfpxnEC8!}1>{B=L5``sSB)p@=*!iEp0_J2ilpbK^mjj~j1%jVcA*Fx8@js+?di9g2
+zORTRo$k$%dvZ>7Zxos_6%k45d$9h|TAFe~5WhXT5D6|)YJxy2VhpL(sn7&e)^?@py
+z<gvFr0Rq_B!Bzzoa@pb2Eg|{LzrOlTTM~A@O}xV|t**X<g4Bi5)C8A_B;>SW48X0J
+zk?-`x@sD+O6RNjELB`BZNhbB(GJM$~txEts2`JnjM#xSHIm|oEEFFJ+*m$h?qvS1*
+zqk$*3@k>!<M1JF$e57<eq|W!x0-&TKkfn(PPfcs=Z>iyO4eQcy%3F^%o@q2ysaEy>
+zGhE-J^noyXA#x<tJYwKod;x8Z;-_>3OFMJ&BGK1XeCw^E;OrCqJ6&7{DA(e-A%^`(
+z{NuTHmL;gS;v3Vh4f_JF5c4Hg5YfAl8y*yXTiEXfy4bA@90w$dvWf0v;w&cCg!WqZ
+zcFkC1jp;rl-bj6AqAY;LHznAOOn70MHg^t*E_(^fyCAdZ9il(b-6v)1n5~c%QkiE7
+zh5rK9^jraGCTU$aCnymORHnsRA(YSkAsHLv*w*-ILAj`z6JWARRAuRIi8j8$VenJV
+zeH8W%8(cOVCmPZQoX0AsGlc1Z`0~He_0f1C*bTXZHpF)MBa8?=sAR!$X%^cpft`^{
+zjCBTY9~*Fs7T`Km=WvFY$w?PS*}A{f=MDV8wHMgShAp0WLVaUOb=jKW;RM`_FDK+h
+za`PEPFNzt^|MuTT(xi%beF^S+pQ*dqiD#CQtb&iQC<9~!73!OMTn30JP*_FeK{0Fk
+z(HeD1w+S+dRmoW^kHSR_avxwxZ-`w7r%6>6;;|NJ<UY}hm{&+(R}5YqqI3%12(Gap
+zjtnCG#*qmG>0BUY3TFuVIBn-%uRC+@oiNWP%z9%1t`J>MS}tVSH8K;yH)YReD~Nbd
+zg~3@nOYR^xYpqqw%<2Mu71T|7J8Acb)v86nqAfT(L^EIyY{J&H79I!whWN6fJFWnI
+zn=t3n&F8rI3;rsLJ~6Nwgz=4hUGAjr94SG!U=x$HmC2(?5c`e-)v>$ZOUhM<sVA8$
+z0^D3`7y0&r+a0T6ai9?FVZe=`@aGHGaPE@Swn>_gFxcp40sG4(wPq-Nzp^@nWWw*2
+zd`aUJV_Qhcv(z5$ymugFIgKnLz&*;-Jc~O?xpuHY?4xz+Y#QElQ^Q0UsKV#LWCC&7
+z480|ZI4D{mNy6uB2rd-EQ5q$&64x*70iKayA5>5LQHeM*PS&OZyw8gyEK3*uSnk~b
+zz;G2OYmh34gY8f~!Id-_rvn@ToiH;^a^%lj4I?GxZ8771-B@^utVkkO2BL+=43e(s
+zA-E@ZbT!wU`43TkiBxe8J&CiRd42n5As%^qGqLCf`rrjYd0sE*`+(cMZW0tG(}I2U
+zmpWTO)X@m*>92|vToa@Rq7Po$F!~8ft-MNqfQ2rRLb}NFZmtMp)g0w$lqyQGg&gS3
+zu*(@}vn0xlV6#}b7^5-pL7)4a7wc3LN!0$tU##4^St^E|p9;ie#;8?r>DALNcKm%W
+z0Jl#(Dy;nEYjF!}K6#2-8PbOhI3Q{Q|GVYN%~*z+xYC*zc&CYYBqy(3gD8bW`}JWz
+z`{Aiz+P$Hqw=#X$OL;`47qIdh6;oXEs7NE?2clA&=!rxTS%Tw6*zxk6fSB{^*g#O{
+zN*}Q6)PMHve&Tc)r=pGi=dsoMh2?JR*dWkSUu++c2Dr(K`(ZP$u+VwZQX4jLlDG;w
+zzUM#S{&eD@1UwJQdxh=8W=&CtnOHpK=^v^_Gs%&+Pe4UrhV~L=+Wt#tumLRuf19CV
+zR^`aSx7!`m4B?)G&w&Pmn)U6ls}q57#k|rU5mEVt%;;MJ8a-huT8X?)R%No|q2p&%
+zG}qGA#8FY@;#NmK-XoI-`ukksinY%LsQ#Wef~<fEa1fOHrPy71Xm>@xm^QJ#{JZ0Z
+z=F=&&Ri%^Db!+2xnM>Caojxf{G$wk)C2YpR3oweo2k29ifZgV8L4PjQi+?n6;@O0;
+z`eScnAsaIXe<VN=z}izz)bB2+Nem}u7hyFJ#DGbZt+0;4H!?GidLlz8YoiXCSSSXa
+zMW2I?ocV0~Tzn6szpL!#aauGAG`k}m8;wTv)S^t19*@bKU_4eTiI$q((T{aPdkgRG
+z_kq&1UKNZA>u62jHeEd;6EMoTSL=R}mbR@X{(`JV{-fMG=xj6V4T()^w@gIY(A|i3
+zhp*ZF9c;7IC+9Y)1+l4hEAFsSuoHSx7>pUJEE_5|9a2s|eNCZ8y(KR(@{G@bb!};l
+zNz`g|162;I^IhZ}ez(@cDdHI%QRZv9(Wi5`;Ogb8h24ba=10z1A5OFUd?I%o+BqF(
+z|4+cff))UrJX|k*{K(+N078XMUk=hh?_qGn_BV1*LO<w*p}+pC`uKcuktm=)^(oC0
+zmx!-TR?FUIyX}Ekxt)yELqUs@u*h}ajjUVEY>RHmoJK)yv$IV9+5i0Be}7%4ZO@)4
+Ivi6GmKTIFPX#fBK
+
+diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
+index b960a27..975087e 100644
+--- a/app/src/main/res/values/styles.xml
++++ b/app/src/main/res/values/styles.xml
+@@ -1,4 +1,3 @@
+ <resources>
+-
+     <style name="AppTheme" parent="@android:style/Theme.NoDisplay"/>
+ </resources>
+diff --git a/gradlew b/gradlew
+old mode 100755
+new mode 100644
+diff --git a/gradlew.bat b/gradlew.bat
+index aec9973..8a0b282 100644
+--- a/gradlew.bat
++++ b/gradlew.bat
+@@ -1,90 +1,90 @@
+-@if "%DEBUG%" == "" @echo off\r
+-@rem ##########################################################################\r
+-@rem\r
+-@rem  Gradle startup script for Windows\r
+-@rem\r
+-@rem ##########################################################################\r
+-\r
+-@rem Set local scope for the variables with windows NT shell\r
+-if "%OS%"=="Windows_NT" setlocal\r
+-\r
+-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r
+-set DEFAULT_JVM_OPTS=\r
+-\r
+-set DIRNAME=%~dp0\r
+-if "%DIRNAME%" == "" set DIRNAME=.\r
+-set APP_BASE_NAME=%~n0\r
+-set APP_HOME=%DIRNAME%\r
+-\r
+-@rem Find java.exe\r
+-if defined JAVA_HOME goto findJavaFromJavaHome\r
+-\r
+-set JAVA_EXE=java.exe\r
+-%JAVA_EXE% -version >NUL 2>&1\r
+-if "%ERRORLEVEL%" == "0" goto init\r
+-\r
+-echo.\r
+-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r
+-echo.\r
+-echo Please set the JAVA_HOME variable in your environment to match the\r
+-echo location of your Java installation.\r
+-\r
+-goto fail\r
+-\r
+-:findJavaFromJavaHome\r
+-set JAVA_HOME=%JAVA_HOME:"=%\r
+-set JAVA_EXE=%JAVA_HOME%/bin/java.exe\r
+-\r
+-if exist "%JAVA_EXE%" goto init\r
+-\r
+-echo.\r
+-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r
+-echo.\r
+-echo Please set the JAVA_HOME variable in your environment to match the\r
+-echo location of your Java installation.\r
+-\r
+-goto fail\r
+-\r
+-:init\r
+-@rem Get command-line arguments, handling Windowz variants\r
+-\r
+-if not "%OS%" == "Windows_NT" goto win9xME_args\r
+-if "%@eval[2+2]" == "4" goto 4NT_args\r
+-\r
+-:win9xME_args\r
+-@rem Slurp the command line arguments.\r
+-set CMD_LINE_ARGS=\r
+-set _SKIP=2\r
+-\r
+-:win9xME_args_slurp\r
+-if "x%~1" == "x" goto execute\r
+-\r
+-set CMD_LINE_ARGS=%*\r
+-goto execute\r
+-\r
+-:4NT_args\r
+-@rem Get arguments from the 4NT Shell from JP Software\r
+-set CMD_LINE_ARGS=%$\r
+-\r
+-:execute\r
+-@rem Setup the command line\r
+-\r
+-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar\r
+-\r
+-@rem Execute Gradle\r
+-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r
+-\r
+-:end\r
+-@rem End local scope for the variables with windows NT shell\r
+-if "%ERRORLEVEL%"=="0" goto mainEnd\r
+-\r
+-:fail\r
+-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r
+-rem the _cmd.exe /c_ return code!\r
+-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1\r
+-exit /b 1\r
+-\r
+-:mainEnd\r
+-if "%OS%"=="Windows_NT" endlocal\r
+-\r
+-:omega\r
++@if "%DEBUG%" == "" @echo off
++@rem ##########################################################################
++@rem
++@rem  Gradle startup script for Windows
++@rem
++@rem ##########################################################################
++
++@rem Set local scope for the variables with windows NT shell
++if "%OS%"=="Windows_NT" setlocal
++
++@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
++set DEFAULT_JVM_OPTS=
++
++set DIRNAME=%~dp0
++if "%DIRNAME%" == "" set DIRNAME=.
++set APP_BASE_NAME=%~n0
++set APP_HOME=%DIRNAME%
++
++@rem Find java.exe
++if defined JAVA_HOME goto findJavaFromJavaHome
++
++set JAVA_EXE=java.exe
++%JAVA_EXE% -version >NUL 2>&1
++if "%ERRORLEVEL%" == "0" goto init
++
++echo.
++echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
++echo.
++echo Please set the JAVA_HOME variable in your environment to match the
++echo location of your Java installation.
++
++goto fail
++
++:findJavaFromJavaHome
++set JAVA_HOME=%JAVA_HOME:"=%
++set JAVA_EXE=%JAVA_HOME%/bin/java.exe
++
++if exist "%JAVA_EXE%" goto init
++
++echo.
++echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
++echo.
++echo Please set the JAVA_HOME variable in your environment to match the
++echo location of your Java installation.
++
++goto fail
++
++:init
++@rem Get command-line arguments, handling Windowz variants
++
++if not "%OS%" == "Windows_NT" goto win9xME_args
++if "%@eval[2+2]" == "4" goto 4NT_args
++
++:win9xME_args
++@rem Slurp the command line arguments.
++set CMD_LINE_ARGS=
++set _SKIP=2
++
++:win9xME_args_slurp
++if "x%~1" == "x" goto execute
++
++set CMD_LINE_ARGS=%*
++goto execute
++
++:4NT_args
++@rem Get arguments from the 4NT Shell from JP Software
++set CMD_LINE_ARGS=%$
++
++:execute
++@rem Setup the command line
++
++set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
++
++@rem Execute Gradle
++"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
++
++:end
++@rem End local scope for the variables with windows NT shell
++if "%ERRORLEVEL%"=="0" goto mainEnd
++
++:fail
++rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
++rem the _cmd.exe /c_ return code!
++if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
++exit /b 1
++
++:mainEnd
++if "%OS%"=="Windows_NT" endlocal
++
++:omega
+-- 
+2.11.1
+
diff --git a/patches/system/core/0001-Create-theme-extras-directory.patch b/patches/system/core/0001-Create-theme-extras-directory.patch
new file mode 100644 (file)
index 0000000..d5c9e6d
--- /dev/null
@@ -0,0 +1,30 @@
+From 3ce2b97222bd612e3fec28fded4f8161d7d39394 Mon Sep 17 00:00:00 2001
+From: bigrushdog <randall.rushing@gmail.com>
+Date: Mon, 9 Jan 2017 01:55:29 -0800
+Subject: [PATCH] Create "theme extras" directory
+
+This creates /data/system/theme in init.rc. Historically, this
+was done in ThemeService in CMTE. However, in a OMS/Subs
+environment, OverlayManagerService is strictly dedicated to
+handling overlays and nothing more.
+
+Change-Id: I775e41310019695c4db82ccc9916dd14c8f690cd
+---
+ rootdir/init.rc | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/rootdir/init.rc b/rootdir/init.rc
+index c146ccf33..b76c0cc91 100644
+--- a/rootdir/init.rc
++++ b/rootdir/init.rc
+@@ -467,6 +467,7 @@ on post-fs-data
+     mkdir /data/system 0775 system system
+     mkdir /data/system/heapdump 0700 system system
+     mkdir /data/system/users 0775 system system
++    mkdir /data/system/theme 0755 system system
+     mkdir /data/system_de 0770 system system
+     mkdir /data/system_ce 0770 system system
+-- 
+2.11.1
+
index de0c72138ce6d5edf547c3cc613c0c72521d92b8..8e3dc2b86f4dba644127f551370630b12817ae67 100644 (file)
@@ -1,7 +1,7 @@
-From 1563cb90a0c8268564552bdd068410cd56079599 Mon Sep 17 00:00:00 2001
+From 08077a058b0fd201349658f91bcb101e81f7753c Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= <marten.kongstad@sonymobile.com>
 Date: Mon, 22 Jun 2015 09:31:25 +0200
-Subject: [PATCH] OMS-N: Add service 'overlay' to service_contexts
+Subject: [PATCH 1/4] OMS-N: Add service 'overlay' to service_contexts
 
 The 'overlay' service is the Overlay Manager Service, which tracks
 packages and their Runtime Resource Overlay overlay packages.
@@ -55,5 +55,5 @@ index 03a7ef3..3ca8182 100644
  allow system_server system_server_service:service_manager { add find };
  allow system_server surfaceflinger_service:service_manager find;
 -- 
-2.9.3
+2.11.1
 
diff --git a/patches/system/sepolicy/0002-Introduce-sepolicy-exceptions-for-theme-assets.patch b/patches/system/sepolicy/0002-Introduce-sepolicy-exceptions-for-theme-assets.patch
new file mode 100644 (file)
index 0000000..c5605fa
--- /dev/null
@@ -0,0 +1,104 @@
+From b836271c4a20b21b087aad4b320cc2c3ac0a37c2 Mon Sep 17 00:00:00 2001
+From: d34d <clark@cyngn.com>
+Date: Wed, 4 Jan 2017 10:29:34 -0800
+Subject: [PATCH 2/4] Introduce sepolicy exceptions for theme assets
+
+Assets such as composed icons and ringtones need to be accessed
+by apps. This patch adds the policy needed to facilitate this.
+
+Change-Id: I0420de579aed0cff5add181cd0a8bf0f2b05d723
+---
+ app.te         | 4 ++++
+ bootanim.te    | 4 ++++
+ file.te        | 3 +++
+ file_contexts  | 3 +++
+ mediaserver.te | 4 ++++
+ system_app.te  | 4 ++++
+ zygote.te      | 4 ++++
+ 7 files changed, 26 insertions(+)
+
+diff --git a/app.te b/app.te
+index 19a7dac..3a2878d 100644
+--- a/app.te
++++ b/app.te
+@@ -468,3 +468,7 @@ neverallow {
+ # Foreign dex profiles are just markers. Prevent apps to do anything but touch them.
+ neverallow appdomain user_profile_foreign_dex_data_file:file rw_file_perms;
+ neverallow appdomain user_profile_foreign_dex_data_file:dir { open getattr read ioctl remove_name };
++
++# Themed resources (i.e. composed icons)
++allow appdomain theme_data_file:dir r_dir_perms;
++allow appdomain theme_data_file:file r_file_perms;
+diff --git a/bootanim.te b/bootanim.te
+index c3091ab..3ae9478 100644
+--- a/bootanim.te
++++ b/bootanim.te
+@@ -32,3 +32,7 @@ r_dir_file(bootanim, cgroup)
+ # System file accesses.
+ allow bootanim system_file:dir r_dir_perms;
++
++# Themed resources (bootanimation)
++allow bootanim theme_data_file:dir search;
++allow bootanim theme_data_file:file r_file_perms;
+diff --git a/file.te b/file.te
+index dfa3c9b..1e18b89 100644
+--- a/file.te
++++ b/file.te
+@@ -266,3 +266,6 @@ allow postinstall_file self:filesystem associate;
+ # Should be:
+ #   type apk_data_file, file_type, data_file_type;
+ neverallow fs_type file_type:filesystem associate;
++
++# Themes
++type theme_data_file, file_type, data_file_type;
+diff --git a/file_contexts b/file_contexts
+index 3d91e2b..dc8bddf 100644
+--- a/file_contexts
++++ b/file_contexts
+@@ -400,3 +400,6 @@
+ /mnt/user(/.*)?             u:object_r:mnt_user_file:s0
+ /mnt/runtime(/.*)?          u:object_r:storage_file:s0
+ /storage(/.*)?              u:object_r:storage_file:s0
++
++# Themes
++/data/system/theme(/.*)?  u:object_r:theme_data_file:s0
+diff --git a/mediaserver.te b/mediaserver.te
+index dc05e14..661bdee 100644
+--- a/mediaserver.te
++++ b/mediaserver.te
+@@ -139,3 +139,7 @@ neverallow mediaserver { file_type fs_type }:file execute_no_trans;
+ # do not allow privileged socket ioctl commands
+ neverallowxperm mediaserver domain:{ rawip_socket tcp_socket udp_socket } ioctl priv_sock_ioctls;
++
++# Themed resources (i.e. composed icons)
++allow mediaserver theme_data_file:dir r_dir_perms;
++allow mediaserver theme_data_file:file r_file_perms;
+diff --git a/system_app.te b/system_app.te
+index 50320c5..afd65d7 100644
+--- a/system_app.te
++++ b/system_app.te
+@@ -75,3 +75,7 @@ allow system_app sysfs_zram:dir search;
+ allow system_app sysfs_zram:file r_file_perms;
+ control_logd(system_app)
++
++# Themes
++allow system_app theme_data_file:dir create_dir_perms;
++allow system_app theme_data_file:file create_file_perms;
+diff --git a/zygote.te b/zygote.te
+index c6b343c..c650c17 100644
+--- a/zygote.te
++++ b/zygote.te
+@@ -104,3 +104,7 @@ neverallow zygote {
+   data_file_type
+   -dalvikcache_data_file # map PROT_EXEC
+ }:file no_x_file_perms;
++
++# Themes
++allow zygote theme_data_file:file r_file_perms;
++allow zygote theme_data_file:dir r_dir_perms;
+-- 
+2.11.1
+
diff --git a/patches/system/sepolicy/0003-sepolicy-fix-themed-boot-animation.patch b/patches/system/sepolicy/0003-sepolicy-fix-themed-boot-animation.patch
new file mode 100644 (file)
index 0000000..9ea35c9
--- /dev/null
@@ -0,0 +1,28 @@
+From cc9b51faf3c84e06991c75527de291fd6dca5298 Mon Sep 17 00:00:00 2001
+From: bigrushdog <randall.rushing@gmail.com>
+Date: Wed, 4 Jan 2017 10:31:29 -0800
+Subject: [PATCH 3/4] sepolicy: fix themed boot animation
+
+W BootAnimation: type=1400 audit(0.0:42): avc: denied { open } for uid=1003 path="/data/system/theme/bootanimation.zip" dev="mmcblk0p42" ino=1657697 scontext=u:r:bootanim:s0 tcontext=u:object_r:system_data_file:s0 tclass=file permissive=0
+
+W         : Unable to open '/data/system/theme/bootanimation.zip': Permission denied
+
+W zipro   : Error opening archive /data/system/theme/bootanimation.zip: I/O Error
+
+Change-Id: I1440bd967d7a06ee64ea861a2544b54caf909f23
+---
+ bootanim.te | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/bootanim.te b/bootanim.te
+index 3ae9478..2356d81 100644
+--- a/bootanim.te
++++ b/bootanim.te
+@@ -36,3 +36,4 @@ allow bootanim system_file:dir r_dir_perms;
+ # Themed resources (bootanimation)
+ allow bootanim theme_data_file:dir search;
+ allow bootanim theme_data_file:file r_file_perms;
++allow bootanim system_data_file:file open;
+-- 
+2.11.1
+
diff --git a/patches/system/sepolicy/0004-sepolicy-fix-themed-sounds.patch b/patches/system/sepolicy/0004-sepolicy-fix-themed-sounds.patch
new file mode 100644 (file)
index 0000000..169f697
--- /dev/null
@@ -0,0 +1,27 @@
+From 79f278bf75d1196b1e332889eeddd3755b7cbfac Mon Sep 17 00:00:00 2001
+From: George G <kreach3r@users.noreply.github.com>
+Date: Wed, 8 Feb 2017 17:22:44 +0200
+Subject: [PATCH 4/4] sepolicy: fix themed sounds
+
+02-08 17:26:48.011 18259-18259/? W/SoundPoolThread: type=1400 audit(0.0:31): avc: denied { read } for path="/data/system/theme/audio/ui/Lock.ogg" dev="dm-0" ino=1006317 scontext=u:r:drmserver:s0 tcontext=u:object_r:theme_data_file:s0 tclass=file permissive=0
+
+Change-Id: If96d784d4a79e7c7f7d21d191c2e0795c366e03a
+---
+ drmserver.te | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/drmserver.te b/drmserver.te
+index 9130e0b..6d3883f 100644
+--- a/drmserver.te
++++ b/drmserver.te
+@@ -54,3 +54,7 @@ allow drmserver drmserver_service:service_manager { add find };
+ allow drmserver permission_service:service_manager find;
+ selinux_check_access(drmserver)
++
++# Themed resources (i.e. composed icons)
++allow drmserver theme_data_file:dir r_dir_perms;
++allow drmserver theme_data_file:file r_file_perms;
+-- 
+2.11.1
+
diff --git a/patches/vendor/cm/0001-Remove-custom-CMTE-rules.patch b/patches/vendor/cm/0001-Remove-custom-CMTE-rules.patch
new file mode 100644 (file)
index 0000000..dc4a7af
--- /dev/null
@@ -0,0 +1,26 @@
+From a43a125904e00740761f3ac5cc4aa564e8f8fd10 Mon Sep 17 00:00:00 2001
+From: LuK1337 <priv.luk@gmail.com>
+Date: Thu, 16 Feb 2017 17:05:43 +0100
+Subject: [PATCH] Remove custom CMTE rules
+
+Change-Id: Ie7829f38fbb3af3eef19c1a73c27d500797ce43f
+---
+ sepolicy/file_contexts | 3 ---
+ 1 file changed, 3 deletions(-)
+
+diff --git a/sepolicy/file_contexts b/sepolicy/file_contexts
+index 1e58f145..d6fe27c5 100644
+--- a/sepolicy/file_contexts
++++ b/sepolicy/file_contexts
+@@ -1,8 +1,5 @@
+ /cache/dalvik-cache(/.*)? u:object_r:dalvikcache_data_file:s0
+-# Themes
+-/data/system/theme(/.*)?  u:object_r:themeservice_app_data_file:s0
+-
+ /system/bin/sysinit       u:object_r:sysinit_exec:s0
+ /system/etc/init\.d/90userinit          u:object_r:userinit_exec:s0
+-- 
+2.11.1
+