'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
-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
-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
-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.
-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
-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
-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.
-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
-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
-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
-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
-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
-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>
-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
-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.
-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
-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:
-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
---
-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
---
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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.
-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.
-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
-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.
-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
---
-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
-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
-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)
-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.
-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
-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
---
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+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
+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
+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
+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
+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
+
+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
+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
+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
+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
+
+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
+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
+
+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_
+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
+
+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
+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
+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
+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
+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
+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
+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
+
--- /dev/null
+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
+
-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.
allow system_server system_server_service:service_manager { add find };
allow system_server surfaceflinger_service:service_manager find;
--
-2.9.3
+2.11.1
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+