Last active
April 12, 2019 17:44
-
-
Save simrat39/7ffc1a9f511aa3a023c0851a75f57ea5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From f5c234b1278cfb477cfbca4438d3dc13e571c5fc Mon Sep 17 00:00:00 2001 | |
From: Nicholas Chum <[email protected]> | |
Date: Sun, 17 Jul 2016 17:56:40 -0400 | |
Subject: [PATCH 1/1] [squashed] base: Add substratum service support | |
This commit checks whether there is a preexisting file in the themed | |
directory "/data/system/theme/audio/ui/" and if so, change the base | |
file paths for the sound. If the file does not exist in the theme | |
directory, then use the default sounds. | |
At the current moment, this will require a soft reboot to work. | |
Change-Id: I7666c2bd259443ccec442bf6059786bea3dc069e | |
[[email protected]: Rewrite for Pie] | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
Extras: Add dynamic theme shutdown and boot animation support | |
Note: Custom boot animation will not work on encrypted device | |
Change-Id: I3b01953d0a69c033a98c0af49ee986b21652b725 | |
Signed-off-by: Ivan Iskandar <[email protected]> | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
ApplicationsState: add filter for Substratum overlays [2/2] | |
This commit allows the framework to handle the filtering of the | |
overlays found for OMS. | |
Change-Id: I7646115e8f73494d726728fac58cc47aafd69d5d | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
ThemeSafety: Introduce App Crash Intent | |
The intent received by substratum and it will disable all enabled | |
overlays. | |
Change-Id: Ifabd57c2ea71ca93ecc2959ce09ccde3e91782dd | |
Signed-off-by: Ivan Iskandar <[email protected]> | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
Hold "volume up" during boot to disable all overlays | |
Following the way "safe mode" was coded, you can now long press | |
"volume up" during boot to automatically disable all overlays | |
(from the current system/owner user). | |
This should come in handy as a global "reset" mechanism. | |
Example output: | |
03-12 03:22:07.090 678 678 D TEST : Disabling overlay android.GalaxyEvolution | |
03-12 03:22:07.176 678 678 D TEST : Disabling overlay com.android.launcher3.GalaxyEvolution | |
03-12 03:22:07.267 678 678 D TEST : Disabling overlay com.android.server.telecom.GalaxyEvolution | |
Caveats: | |
SystemServer seems to have already loaded a context based on the | |
overlays by the time "disableOverlays" is finished, so SystemUI | |
turns up themed, even if the overlay is correctly disabled. | |
In the case of a user using this reset mode to "fix" a SystemUI FC, | |
this means that the user will experience one more FC after boot | |
(which then would trigger a SystemUI restart, loading the default | |
theme this time, and thus ending the FC loop). | |
Change-Id: I64fb769ea175d37a90a0804f916926b63e5b93ca | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
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 | |
to StrictMode and SystemUI is crashing. | |
So we need that StrictMode authorize files under /system OR /data/system/theme | |
Logcat of the issue: | |
E AndroidRuntime: Caused by: android.os.FileUriExposedException: file:///data/system/theme/audio/ui/LowBattery.ogg exposed beyond app through Notification.sound | |
E AndroidRuntime: at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799) | |
E AndroidRuntime: at android.net.Uri.checkFileUriExposed(Uri.java:2346) | |
E AndroidRuntime: at android.app.NotificationManager.notifyAsUser(NotificationManager.java:300) | |
Change-Id: I154dc4280de8eaf891772a9632283e9f547f5718 | |
(cherry picked from commit 838f6466d39a100f9709ac253a6d7358ca66829f) | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
base: Introduce SubstratumService | |
Change-Id: I8c9f331ca7e4c68c35ea13bed587775ee4abeb30 | |
Signed-off-by: Ivan Iskandar <[email protected]> | |
Signed-off-by: Nicholas Chum <[email protected]> | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
SubstratumService: allow CHANGE_OVERLAY_PACKAGES permission | |
Change-Id: I226a42c711164139578825f573d7beb88fdfa8a2 | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
SubstratumService: unify permissions | |
Change-Id: Ib4a16b1321d9e797ddc71401a80b5433c64cbfc2 | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
base: modify/define custom overlay management | |
Change-Id: I37376416fdd847e354075d31ee3ecd1f83df0cc7 | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
SubstratumService: Refactor & cleanup according to AOSP conventions | |
Change-Id: I588ae0c6ea89d05c824b94c7560d1f8a69423b48 | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
Pie OMS: allow non-system overlays from Substratum | |
Change-Id: I6132f396c60f8020a650ebe37850cb662192438e | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
Unbreak public api by hiding substratum interfaces | |
Change-Id: I5857ad50124267e99c4d7c9c790648f76a9bef66 | |
Signed-off-by: Alex Naidis <[email protected]> | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
SubstratumService: Rewrite installation method for Pie InstallSession API | |
Change-Id: I378da3e891e02933cff24ec289b73b06de840942 | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
OMS: try harder not to update assets if nothing changed | |
To ensure framework overlays [target="android"] are applied correctly | |
when an app is installed, OMSImpl.updateAllOverlaysForTarget checks for | |
the existance of enabled framework overlays. But this check leads to | |
unnecessary asset updates [defined as no change to the set of overlay | |
paths] if the OMS was triggered by e.g. the package manager enabling a | |
component in the android package. On the other hand, when the target | |
package is "android", the other checks in | |
OMSImpl.updateAllOverlaysForTarget are sufficient to determine if an | |
asset update should be scheduled. | |
Exclude the enabled framework overlays check if (and only if) the target | |
package happens to be "android". | |
Test: atest OverlayHostTests OverlayDeviceTests | |
Change-Id: I7334cbda5686587c0db97c458c09c3656b7eacc4 | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
OMS: handle target or overlay package disabled | |
Update what happens when the package manager disables a package: | |
- if a target is disabled, update its overlays to STATE_MISSING_TARGET | |
- if an overlay is disabled, update its state to the new state | |
STATE_OVERLAY_NOT_AVAILABLE | |
In both cases, OverlayInfo.isEnabled will return false. | |
Note: if the package manager only disables a single component, nothing | |
happens to the overlays: OMS only looks at ApplicationInfo.enabled, not | |
the individual ComponentInfo.enabled fields. | |
Test: atest OverlayHostTests | |
Change-Id: I8c417acc0175739002a0350eed1913c32cc73337 | |
Signed-off-by: Harsh Shandilya <[email protected]> | |
--- | |
Android.bp | 2 + | |
cmds/bootanimation/BootAnimation.cpp | 6 +- | |
core/java/android/content/om/OverlayInfo.java | 11 + | |
.../substratum/ISubstratumService.aidl | 170 +++ | |
core/java/android/net/Uri.java | 3 +- | |
core/java/android/provider/Settings.java | 7 + | |
.../substratum/ISubstratumHelperService.aidl | 25 + | |
.../java/com/android/server/SystemConfig.java | 16 + | |
core/jni/fd_utils.cpp | 4 +- | |
core/res/AndroidManifest.xml | 3 + | |
.../om/hosttest/InstallOverlayTests.java | 10 + | |
data/etc/Android.mk | 17 + | |
data/etc/substratum-sysconfig.xml | 22 + | |
data/etc/substratum-theme-feature.xml | 25 + | |
libs/androidfw/AssetManager.cpp | 3 +- | |
.../include/androidfw/AssetManager.h | 1 + | |
.../applications/ApplicationsState.java | 18 +- | |
packages/SubstratumHelperService/Android.mk | 32 + | |
.../AndroidManifest.xml | 47 + | |
.../helper/SubstratumHelperService.java | 228 ++++ | |
packages/SystemUI/AndroidManifest.xml | 10 + | |
.../systemui/SoundRefreshReceiver.java | 34 + | |
.../systemui/SysuiRestartReceiver.java | 9 +- | |
.../keyguard/KeyguardViewMediator.java | 18 + | |
.../server/am/ActivityManagerService.java | 30 + | |
.../java/com/android/server/am/AppErrors.java | 8 + | |
.../android/server/audio/AudioService.java | 11 +- | |
.../server/om/OverlayManagerService.java | 2 - | |
.../server/om/OverlayManagerServiceImpl.java | 13 +- | |
.../server/pm/PackageManagerService.java | 39 +- | |
.../android/server/power/ShutdownThread.java | 30 +- | |
.../android/server/substratum/SoundUtils.java | 229 ++++ | |
.../server/substratum/SubstratumService.java | 1101 +++++++++++++++++ | |
.../server/wm/WindowManagerService.java | 22 + | |
.../java/com/android/server/SystemServer.java | 13 + | |
35 files changed, 2194 insertions(+), 25 deletions(-) | |
create mode 100644 core/java/android/content/substratum/ISubstratumService.aidl | |
create mode 100644 core/java/com/android/internal/substratum/ISubstratumHelperService.aidl | |
create mode 100644 data/etc/substratum-sysconfig.xml | |
create mode 100644 data/etc/substratum-theme-feature.xml | |
create mode 100644 packages/SubstratumHelperService/Android.mk | |
create mode 100644 packages/SubstratumHelperService/AndroidManifest.xml | |
create mode 100644 packages/SubstratumHelperService/src/projekt/substratum/helper/SubstratumHelperService.java | |
create mode 100644 packages/SystemUI/src/com/android/systemui/SoundRefreshReceiver.java | |
create mode 100644 services/core/java/com/android/server/substratum/SoundUtils.java | |
create mode 100644 services/core/java/com/android/server/substratum/SubstratumService.java | |
diff --git a/Android.bp b/Android.bp | |
index 06d1177922b..667604c66ce 100644 | |
--- a/Android.bp | |
+++ b/Android.bp | |
@@ -144,6 +144,7 @@ java_library { | |
"core/java/android/content/pm/dex/IArtManager.aidl", | |
"core/java/android/content/pm/dex/ISnapshotRuntimeProfileCallback.aidl", | |
"core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl", | |
+ "core/java/android/content/substratum/ISubstratumService.aidl", | |
"core/java/android/database/IContentObserver.aidl", | |
":libcamera_client_aidl", | |
":libcamera_client_framework_aidl", | |
@@ -390,6 +391,7 @@ java_library { | |
"core/java/com/android/internal/os/IShellCallback.aidl", | |
"core/java/com/android/internal/statusbar/IStatusBar.aidl", | |
"core/java/com/android/internal/statusbar/IStatusBarService.aidl", | |
+ "core/java/com/android/internal/substratum/ISubstratumHelperService.aidl", | |
"core/java/com/android/internal/textservice/ISpellCheckerService.aidl", | |
"core/java/com/android/internal/textservice/ISpellCheckerServiceCallback.aidl", | |
"core/java/com/android/internal/textservice/ISpellCheckerSession.aidl", | |
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp | |
index 07de385ea4f..9f80398b518 100644 | |
--- a/cmds/bootanimation/BootAnimation.cpp | |
+++ b/cmds/bootanimation/BootAnimation.cpp | |
@@ -72,9 +72,11 @@ static const char SYSTEM_BOOTANIMATION_FILE2[] = "/system/media/bootanimation2.z | |
static const char SYSTEM_BOOTANIMATION_FILE3[] = "/system/media/bootanimation3.zip"; | |
static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip"; | |
static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"; | |
+static const char THEME_BOOTANIMATION_FILE[] = "/data/system/theme/bootanimation.zip"; | |
static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip"; | |
static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip"; | |
static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip"; | |
+static const char THEME_SHUTDOWNANIMATION_FILE[] = "/data/system/theme/shutdownanimation.zip"; | |
static const char SYSTEM_DATA_DIR_PATH[] = "/data/system"; | |
static const char SYSTEM_TIME_DIR_NAME[] = "time"; | |
@@ -327,9 +329,9 @@ status_t BootAnimation::readyToRun() { | |
static const char* systemBootFiles[] = | |
{SYSTEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE2, SYSTEM_BOOTANIMATION_FILE3}; | |
static const char* bootFiles[] = | |
- {PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; | |
+ {THEME_BOOTANIMATION_FILE, PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; | |
static const char* shutdownFiles[] = | |
- {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; | |
+ {THEME_SHUTDOWNANIMATION_FILE, PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; | |
if (!mShuttingDown) { | |
srand (time(NULL)); | |
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java | |
index edacbb0bb2b..9f7abf679d5 100644 | |
--- a/core/java/android/content/om/OverlayInfo.java | |
+++ b/core/java/android/content/om/OverlayInfo.java | |
@@ -41,6 +41,7 @@ public final class OverlayInfo implements Parcelable { | |
STATE_ENABLED_STATIC, | |
STATE_TARGET_UPGRADING, | |
STATE_OVERLAY_UPGRADING, | |
+ STATE_OVERLAY_NOT_AVAILABLE, | |
}) | |
@Retention(RetentionPolicy.SOURCE) | |
public @interface State {} | |
@@ -97,6 +98,13 @@ public final class OverlayInfo implements Parcelable { | |
*/ | |
public static final int STATE_ENABLED_STATIC = 6; | |
+ /** | |
+ * The overlay package is currently disabled by the package manager. For | |
+ * all intents and purposes, outside the package manager, it is like the | |
+ * overlay package simply was not installed. | |
+ */ | |
+ public static final int STATE_OVERLAY_NOT_AVAILABLE = 7; | |
+ | |
/** | |
* Overlay category: theme. | |
* <p> | |
@@ -207,6 +215,7 @@ public final class OverlayInfo implements Parcelable { | |
case STATE_ENABLED_STATIC: | |
case STATE_TARGET_UPGRADING: | |
case STATE_OVERLAY_UPGRADING: | |
+ case STATE_OVERLAY_NOT_AVAILABLE: | |
break; | |
default: | |
throw new IllegalArgumentException("State " + state + " is not a valid state"); | |
@@ -285,6 +294,8 @@ public final class OverlayInfo implements Parcelable { | |
return "STATE_TARGET_UPGRADING"; | |
case STATE_OVERLAY_UPGRADING: | |
return "STATE_OVERLAY_UPGRADING"; | |
+ case STATE_OVERLAY_NOT_AVAILABLE: | |
+ return "STATE_OVERLAY_NOT_AVAILABLE"; | |
default: | |
return "<unknown state>"; | |
} | |
diff --git a/core/java/android/content/substratum/ISubstratumService.aidl b/core/java/android/content/substratum/ISubstratumService.aidl | |
new file mode 100644 | |
index 00000000000..13bd2113e9c | |
--- /dev/null | |
+++ b/core/java/android/content/substratum/ISubstratumService.aidl | |
@@ -0,0 +1,170 @@ | |
+/* | |
+ * Copyright (C) 2018 Projekt Substratum | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); | |
+ * you may not use this file except in compliance with the License. | |
+ * You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software | |
+ * distributed under the License is distributed on an "AS IS" BASIS, | |
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ * See the License for the specific language governing permissions and | |
+ * limitations under the License. | |
+ */ | |
+ | |
+package android.content.substratum; | |
+ | |
+/** {@hide} */ | |
+interface ISubstratumService { | |
+ | |
+ /** | |
+ * Install a list of specified overlay packages | |
+ * | |
+ * @param paths Filled in with a list of path names for packages to be installed from. | |
+ */ | |
+ void installOverlay(in List<String> paths); | |
+ | |
+ /** | |
+ * Uninstall a list of specified overlay packages | |
+ * | |
+ * @param packages Filled in with a list of path names for packages to be installed from. | |
+ * @param restartUi Flag to automatically restart the SystemUI. | |
+ */ | |
+ void uninstallOverlay(in List<String> packages, boolean restartUi); | |
+ | |
+ /** | |
+ * Switch the state of specified overlay packages | |
+ * | |
+ * @param packages Filled in with a list of package names to be switched. | |
+ * @param enable Whether to enable the specified overlays. | |
+ * @param restartUi Flag to automatically restart the SystemUI. | |
+ */ | |
+ void switchOverlay(in List<String> packages, boolean enable, boolean restartUi); | |
+ | |
+ /** | |
+ * Change the priority of a specified list of overlays | |
+ * | |
+ * @param packages Filled in with a list of package names to be reordered. | |
+ * @param restartUi Flag to automatically restart the SystemUI. | |
+ */ | |
+ void setPriority(in List<String> packages, boolean restartUi); | |
+ | |
+ /** | |
+ * Restart SystemUI | |
+ */ | |
+ void restartSystemUI(); | |
+ | |
+ /** | |
+ * Copy Method | |
+ * | |
+ * @param source Path of the source file. | |
+ * @param destination Path of the source file to be copied to. | |
+ */ | |
+ void copy(String source, String destination); | |
+ | |
+ /** | |
+ * Move Method | |
+ * | |
+ * @param source Path of the source file. | |
+ * @param destination Path of the source file to be moved to. | |
+ */ | |
+ void move(String source, String destination); | |
+ | |
+ /** | |
+ * Create Directory Method | |
+ * | |
+ * @param destination Path of the created destination folder. | |
+ */ | |
+ void mkdir(String destination); | |
+ | |
+ /** | |
+ * Delete Directory Method | |
+ * | |
+ * @param destination Path of the directory to be deleted. | |
+ * @param withParent Flag to automatically delete the folder encompassing the folder. | |
+ */ | |
+ void deleteDirectory(String directory, boolean withParent); | |
+ | |
+ /** | |
+ * Apply a specified bootanimation | |
+ * | |
+ * @param name Path to extract the bootanimation archive from. | |
+ */ | |
+ void applyBootanimation(String name); | |
+ | |
+ /** | |
+ * Apply a specified font pack | |
+ * | |
+ * @param name Path to extract the font archive from. | |
+ */ | |
+ void applyFonts(String pid, String fileName); | |
+ | |
+ /** | |
+ * Apply a specified sound pack | |
+ * | |
+ * @param name Path to extract the sounds archive from. | |
+ */ | |
+ void applySounds(String pid, String fileName); | |
+ | |
+ /** | |
+ * Profile Applicator | |
+ * | |
+ * @param enable Filled in with a list of package names to be enabled. | |
+ * @param disable Filled in with a list of package names to be disabled. | |
+ * @param name Name of the profile to be applied. | |
+ * @param restartUi Flag to automatically restart the SystemUI. | |
+ */ | |
+ void applyProfile(in List<String> enable, in List<String> disable, String name, | |
+ boolean restartUi); | |
+ | |
+ /** | |
+ * Apply a specified shutdownanimation | |
+ * | |
+ * @param name Path to extract the shutdown archive from. | |
+ * Use null to clear applied custom shutdown | |
+ */ | |
+ void applyShutdownAnimation(String name); | |
+ | |
+ /** | |
+ * Returns information about all installed overlay packages for the | |
+ * specified user. If there are no installed overlay packages for this user, | |
+ * an empty map is returned (i.e. null is never returned). The returned map is a | |
+ * mapping of target package names to lists of overlays. Each list for a | |
+ * given target package is sorted in priority order, with the overlay with | |
+ * the highest priority at the end of the list. | |
+ * | |
+ * @param uid The user to get the OverlayInfos for. | |
+ * @return A Map<String, List<OverlayInfo>> with target package names | |
+ * mapped to lists of overlays; if no overlays exist for the | |
+ * requested user, an empty map is returned. | |
+ */ | |
+ Map getAllOverlays(in int uid); | |
+ | |
+ /** | |
+ * Request that an overlay package be enabled or disabled when possible to | |
+ * do so. | |
+ * | |
+ * It is always possible to disable an overlay, but due to technical and | |
+ * security reasons it may not always be possible to enable an overlay. An | |
+ * example of the latter is when the related target package is not | |
+ * installed. If the technical obstacle is later overcome, the overlay is | |
+ * automatically enabled at that point in time. | |
+ * | |
+ * An enabled overlay is a part of target package's resources, i.e. it will | |
+ * be part of any lookups performed via {@link android.content.res.Resources} | |
+ * and {@link android.content.res.AssetManager}. A disabled overlay will no | |
+ * longer affect the resources of the target package. If the target is | |
+ * currently running, its outdated resources will be replaced by new ones. | |
+ * This happens the same way as when an application enters or exits split | |
+ * window mode. | |
+ * | |
+ * @param packageName The name of the overlay package. | |
+ * @param enable true to enable the overlay, false to disable it. | |
+ * @param userId The user for which to change the overlay. | |
+ * @return true if the system successfully registered the request, false otherwise. | |
+ */ | |
+ boolean setEnabled(in String packageName, in boolean enable, in int userId); | |
+} | |
+ | |
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java | |
index bd23ae4c4ce..ac65791c6ff 100644 | |
--- a/core/java/android/net/Uri.java | |
+++ b/core/java/android/net/Uri.java | |
@@ -2367,7 +2367,8 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { | |
*/ | |
public void checkFileUriExposed(String location) { | |
if ("file".equals(getScheme()) | |
- && (getPath() != null) && !getPath().startsWith("/system/")) { | |
+ && (getPath() != null) && !(getPath().startsWith("/system/") || | |
+ getPath().startsWith("/data/system/theme/"))) { | |
StrictMode.onFileUriExposed(this, location); | |
} | |
} | |
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java | |
index bfcd54135bb..6bade91d675 100644 | |
--- a/core/java/android/provider/Settings.java | |
+++ b/core/java/android/provider/Settings.java | |
@@ -10250,6 +10250,13 @@ public final class Settings { | |
*/ | |
public static final String LONG_SQUEEZE_SELECTION_SMART_ACTIONS = "long_squeeze_selection_smart_actions"; | |
+ /** | |
+ * Force authorize Substratum (or equivalent) frontend calling packages by ThemeInterfacer | |
+ * The value is boolean (1 or 0). | |
+ * @hide | |
+ */ | |
+ public static final String FORCE_AUTHORIZE_SUBSTRATUM_PACKAGES = "force_authorize_substratum_packages"; | |
+ | |
/** | |
* This are the settings to be backed up. | |
* | |
diff --git a/core/java/com/android/internal/substratum/ISubstratumHelperService.aidl b/core/java/com/android/internal/substratum/ISubstratumHelperService.aidl | |
new file mode 100644 | |
index 00000000000..48dc20b5c5f | |
--- /dev/null | |
+++ b/core/java/com/android/internal/substratum/ISubstratumHelperService.aidl | |
@@ -0,0 +1,25 @@ | |
+/* | |
+ * Copyright (C) 2018 Projekt Substratum | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); | |
+ * you may not use this file except in compliance with the License. | |
+ * You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software | |
+ * distributed under the License is distributed on an "AS IS" BASIS, | |
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ * See the License for the specific language governing permissions and | |
+ * limitations under the License. | |
+ */ | |
+ | |
+package com.android.internal.substratum; | |
+ | |
+/** {@hide} */ | |
+oneway interface ISubstratumHelperService { | |
+ void applyBootAnimation(); | |
+ void applyShutdownAnimation(); | |
+ void applyProfile(in String name); | |
+ void installOverlay(in List<String> paths); | |
+} | |
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java | |
index c5be8e4a3ab..f6450b6ef86 100644 | |
--- a/core/java/com/android/server/SystemConfig.java | |
+++ b/core/java/com/android/server/SystemConfig.java | |
@@ -142,6 +142,9 @@ public class SystemConfig { | |
// Package names that are exempted from private API blacklisting | |
final ArraySet<String> mHiddenApiPackageWhitelist = new ArraySet<>(); | |
+ // These are the packages that are whitelisted to be the signature check for a theme system | |
+ final ArraySet<String> mThemeSystemSignatureWhitelistedApps = new ArraySet<>(); | |
+ | |
// The list of carrier applications which should be disabled until used. | |
// This function suppresses update notifications for these pre-installed apps. | |
// In SubscriptionInfoUpdater, the listed applications are disabled until used when all of the | |
@@ -244,6 +247,10 @@ public class SystemConfig { | |
return mBackupTransportWhitelist; | |
} | |
+ public ArraySet<String> getThemeSystemSignatureWhitelistedApps() { | |
+ return mThemeSystemSignatureWhitelistedApps; | |
+ } | |
+ | |
public ArraySet<String> getDisabledUntilUsedPreinstalledCarrierApps() { | |
return mDisabledUntilUsedPreinstalledCarrierApps; | |
} | |
@@ -626,6 +633,15 @@ public class SystemConfig { | |
} | |
} | |
XmlUtils.skipCurrentTag(parser); | |
+ } else if ("theme-system-signature-whitelisted-app".equals(name) && allowAppConfigs) { | |
+ String pkgname = parser.getAttributeValue(null, "package"); | |
+ if (pkgname == null) { | |
+ Slog.w(TAG, "<theme-system-signature-whitelisted-app> without package in " + permFile | |
+ + " at " + parser.getPositionDescription()); | |
+ } else { | |
+ mThemeSystemSignatureWhitelistedApps.add(pkgname); | |
+ } | |
+ XmlUtils.skipCurrentTag(parser); | |
} else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name) | |
&& allowAppConfigs) { | |
String pkgname = parser.getAttributeValue(null, "package"); | |
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp | |
index c5904e0e9e5..ec07daa4c8c 100644 | |
--- a/core/jni/fd_utils.cpp | |
+++ b/core/jni/fd_utils.cpp | |
@@ -87,13 +87,15 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { | |
static const char* kOverlaySubdir = "/system/vendor/overlay-subdir/"; | |
static const char* kSystemProductOverlayDir = "/system/product/overlay/"; | |
static const char* kProductOverlayDir = "/product/overlay"; | |
+ static const char* kThemeOverlayDir = "/data/system/theme"; | |
static const char* kApkSuffix = ".apk"; | |
if ((android::base::StartsWith(path, kOverlayDir) | |
|| android::base::StartsWith(path, kOverlaySubdir) | |
|| android::base::StartsWith(path, kVendorOverlayDir) | |
|| android::base::StartsWith(path, kSystemProductOverlayDir) | |
- || android::base::StartsWith(path, kProductOverlayDir)) | |
+ || android::base::StartsWith(path, kProductOverlayDir) | |
+ || android::base::StartsWith(path, kThemeOverlayDir)) | |
&& android::base::EndsWith(path, kApkSuffix) | |
&& path.find("/../") == std::string::npos) { | |
return true; | |
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml | |
index 0923ce98f3e..024b9d5e40e 100644 | |
--- a/core/res/AndroidManifest.xml | |
+++ b/core/res/AndroidManifest.xml | |
@@ -605,6 +605,9 @@ | |
<!-- For IdleController --> | |
<protected-broadcast android:name="android.intent.action.DOCK_IDLE" /> | |
<protected-broadcast android:name="android.intent.action.DOCK_ACTIVE" /> | |
+ <protected-broadcast android:name="projekt.substratum.APP_CRASHED" /> | |
+ <protected-broadcast android:name="com.android.systemui.action.RESTART_THEME" /> | |
+ <protected-broadcast android:name="com.android.systemui.action.REFRESH_SOUND" /> | |
<!-- Used for long press power torch feature - automatic turn off on timeout --> | |
<protected-broadcast android:name="com.android.server.policy.PhoneWindowManager.ACTION_TORCH_OFF" /> | |
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java | |
index 6d5276f2423..9b1f95ce2a6 100644 | |
--- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java | |
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java | |
@@ -94,6 +94,16 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { | |
assertTrue(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME)); | |
} | |
+ @Test | |
+ public void installAndDisableOverlay() throws Exception { | |
+ final String pkgName = "com.android.server.om.hosttest.app_overlay"; | |
+ installPackage("OverlayHostTests_AppOverlayV1.apk"); | |
+ assertTrue(overlayManagerContainsPackage(pkgName)); | |
+ assertFalse(overlayManagerContainsPackage("--- " + pkgName)); | |
+ setPackageEnabled(pkgName, false); | |
+ assertTrue(overlayManagerContainsPackage("--- " + pkgName)); | |
+ } | |
+ | |
@Test | |
public void installPlatformSignedAppOverlayAndUpdate() throws Exception { | |
assertTrue(runDeviceTests(DEVICE_TEST_PKG, DEVICE_TEST_CLS, "expectAppResource")); | |
diff --git a/data/etc/Android.mk b/data/etc/Android.mk | |
index 936ad22d4fc..f9ddefe178b 100644 | |
--- a/data/etc/Android.mk | |
+++ b/data/etc/Android.mk | |
@@ -47,3 +47,20 @@ LOCAL_MODULE_CLASS := ETC | |
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig | |
LOCAL_SRC_FILES := $(LOCAL_MODULE) | |
include $(BUILD_PREBUILT) | |
+ | |
+######################## | |
+include $(CLEAR_VARS) | |
+LOCAL_MODULE := substratum-theme-feature.xml | |
+LOCAL_MODULE_CLASS := ETC | |
+LOCAL_MODULE_TAGS := optional | |
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig | |
+LOCAL_SRC_FILES := $(LOCAL_MODULE) | |
+include $(BUILD_PREBUILT) | |
+ | |
+######################## | |
+include $(CLEAR_VARS) | |
+LOCAL_MODULE := substratum-sysconfig.xml | |
+LOCAL_MODULE_CLASS := ETC | |
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig | |
+LOCAL_SRC_FILES := $(LOCAL_MODULE) | |
+include $(BUILD_PREBUILT) | |
diff --git a/data/etc/substratum-sysconfig.xml b/data/etc/substratum-sysconfig.xml | |
new file mode 100644 | |
index 00000000000..1d6a161b000 | |
--- /dev/null | |
+++ b/data/etc/substratum-sysconfig.xml | |
@@ -0,0 +1,22 @@ | |
+<?xml version="1.0" encoding="utf-8"?> | |
+<!-- Copyright (C) 2014 The Android Open Source Project | |
+ | |
+ Licensed under the Apache License, Version 2.0 (the "License"); | |
+ you may not use this file except in compliance with the License. | |
+ You may obtain a copy of the License at | |
+ | |
+ http://www.apache.org/licenses/LICENSE-2.0 | |
+ | |
+ Unless required by applicable law or agreed to in writing, software | |
+ distributed under the License is distributed on an "AS IS" BASIS, | |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ See the License for the specific language governing permissions and | |
+ limitations under the License. | |
+--> | |
+ | |
+<config> | |
+ | |
+ <!-- These are the packages that are white-listed to be a signature check for a theme system --> | |
+ <theme-system-signature-whitelisted-app package="projekt.substratum.signature" /> | |
+ | |
+</config> | |
\ No newline at end of file | |
diff --git a/data/etc/substratum-theme-feature.xml b/data/etc/substratum-theme-feature.xml | |
new file mode 100644 | |
index 00000000000..b7d97fcd586 | |
--- /dev/null | |
+++ b/data/etc/substratum-theme-feature.xml | |
@@ -0,0 +1,25 @@ | |
+<?xml version="1.0" encoding="utf-8"?> | |
+<!-- | |
+ Copyright (c) 2018 Project Substratum | |
+ | |
+ Licensed under the Apache License, Version 2.0 (the "License"); | |
+ you may not use this file except in compliance with the License. | |
+ You may obtain a copy of the License at | |
+ | |
+ http://www.apache.org/licenses/LICENSE-2.0 | |
+ | |
+ Unless required by applicable law or agreed to in writing, software | |
+ distributed under the License is distributed on an "AS IS" BASIS, | |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ See the License for the specific language governing permissions and | |
+ limitations under the License. | |
+--> | |
+ | |
+<config> | |
+ <!-- Limitation permission to block out themes from being visible on stock | |
+ AOSP, or non-OMS devices. --> | |
+ | |
+ <!-- This is an alias for projekt.substratum.theme --> | |
+ <feature name="projekt.substratum.theme" /> | |
+ | |
+</config> | |
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp | |
index fc625bbaf72..061e6c2a038 100644 | |
--- a/libs/androidfw/AssetManager.cpp | |
+++ b/libs/androidfw/AssetManager.cpp | |
@@ -73,8 +73,9 @@ static volatile int32_t gCount = 0; | |
const char* AssetManager::RESOURCES_FILENAME = "resources.arsc"; | |
const char* AssetManager::IDMAP_BIN = "/system/bin/idmap"; | |
const char* AssetManager::OVERLAY_DIR = "/vendor/overlay"; | |
+const char* AssetManager::THEME_OVERLAY_DIR = "/data/system/theme"; | |
const char* AssetManager::PRODUCT_OVERLAY_DIR = "/product/overlay"; | |
-const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme"; | |
+const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "../../data/system/theme"; | |
const char* AssetManager::TARGET_PACKAGE_NAME = "android"; | |
const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk"; | |
const char* AssetManager::IDMAP_DIR = "/data/resource-cache"; | |
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h | |
index 08da7319de8..115ae0b735c 100644 | |
--- a/libs/androidfw/include/androidfw/AssetManager.h | |
+++ b/libs/androidfw/include/androidfw/AssetManager.h | |
@@ -60,6 +60,7 @@ public: | |
static const char* RESOURCES_FILENAME; | |
static const char* IDMAP_BIN; | |
static const char* OVERLAY_DIR; | |
+ static const char* THEME_OVERLAY_DIR; | |
static const char* PRODUCT_OVERLAY_DIR; | |
/* | |
* If OVERLAY_THEME_DIR_PROPERTY is set, search for runtime resource overlay | |
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java | |
index 15cb9bd8fcd..9c94a574742 100644 | |
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java | |
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java | |
@@ -190,9 +190,11 @@ public class ApplicationsState { | |
// Only the owner can see all apps. | |
mAdminRetrieveFlags = PackageManager.MATCH_ANY_USER | | |
PackageManager.MATCH_DISABLED_COMPONENTS | | |
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; | |
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS | | |
+ PackageManager.GET_META_DATA; | |
mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS | | |
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; | |
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS | | |
+ PackageManager.GET_META_DATA; | |
/** | |
* This is a trick to prevent the foreground thread from being delayed. | |
@@ -1547,6 +1549,17 @@ public class ApplicationsState { | |
} | |
}; | |
+ public static final AppFilter FILTER_SUBSTRATUM = new AppFilter() { | |
+ public void init() { | |
+ } | |
+ | |
+ @Override | |
+ public boolean filterApp(AppEntry entry) { | |
+ return !((entry.info.metaData != null) && | |
+ (entry.info.metaData.getString("Substratum_Parent") != null)); | |
+ } | |
+ }; | |
+ | |
public static final AppFilter FILTER_WORK = new AppFilter() { | |
private int mCurrentUser; | |
@@ -1618,7 +1631,6 @@ public class ApplicationsState { | |
return false; | |
} | |
}; | |
- | |
public static final AppFilter FILTER_DISABLED = new AppFilter() { | |
@Override | |
public void init() { | |
diff --git a/packages/SubstratumHelperService/Android.mk b/packages/SubstratumHelperService/Android.mk | |
new file mode 100644 | |
index 00000000000..78908d9312d | |
--- /dev/null | |
+++ b/packages/SubstratumHelperService/Android.mk | |
@@ -0,0 +1,32 @@ | |
+# | |
+# Copyright (C) 2018 Projekt Substratum | |
+# | |
+# Licensed under the Apache License, Version 2.0 (the "License"); | |
+# you may not use this file except in compliance with the License. | |
+# You may obtain a copy of the License at | |
+# | |
+# http://www.apache.org/licenses/LICENSE-2.0 | |
+# | |
+# Unless required by applicable law or agreed to in writing, software | |
+# distributed under the License is distributed on an "AS IS" BASIS, | |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+# See the License for the specific language governing permissions and | |
+# limitations under the License. | |
+# | |
+ | |
+LOCAL_PATH := $(call my-dir) | |
+include $(CLEAR_VARS) | |
+ | |
+LOCAL_MODULE_TAGS := optional | |
+ | |
+LOCAL_SRC_FILES := $(call all-java-files-under, src) | |
+LOCAL_PROGUARD_ENABLED := disabled | |
+LOCAL_REQUIRED_MODULES := \ | |
+ substratum-theme-feature.xml | |
+ | |
+LOCAL_PACKAGE_NAME := SubstratumHelperService | |
+LOCAL_PRIVATE_PLATFORM_APIS := true | |
+LOCAL_CERTIFICATE := platform | |
+LOCAL_PRIVILEGED_MODULE := true | |
+ | |
+include $(BUILD_PACKAGE) | |
\ No newline at end of file | |
diff --git a/packages/SubstratumHelperService/AndroidManifest.xml b/packages/SubstratumHelperService/AndroidManifest.xml | |
new file mode 100644 | |
index 00000000000..d28dbc68c2f | |
--- /dev/null | |
+++ b/packages/SubstratumHelperService/AndroidManifest.xml | |
@@ -0,0 +1,47 @@ | |
+<?xml version="1.0" encoding="utf-8"?> | |
+<!-- | |
+/* | |
+ * Copyright (c) 2018 Projekt Substratum | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); | |
+ * you may not use this file except in compliance with the License. | |
+ * You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software | |
+ * distributed under the License is distributed on an "AS IS" BASIS, | |
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ * See the License for the specific language governing permissions and | |
+ * limitations under the License. | |
+ */ | |
+--> | |
+ | |
+ | |
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
+ package="projekt.substratum.helper" | |
+ android:sharedUserId="android.uid.system" | |
+ android:versionCode="2" | |
+ android:versionName="two" > | |
+ | |
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> | |
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | |
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | |
+ | |
+ <uses-feature name="projekt.substratum.theme" required="true"/> | |
+ | |
+ <application | |
+ android:allowBackup="false" | |
+ android:label="Substratum Helper Service"> | |
+ | |
+ <service android:name="projekt.substratum.helper.SubstratumHelperService" | |
+ android:enabled="true" | |
+ android:exported="true"> | |
+ <intent-filter> | |
+ <action android:name="projekt.substratum.helper.SubstratumHelperService" /> | |
+ </intent-filter> | |
+ </service> | |
+ | |
+ </application> | |
+ | |
+</manifest> | |
diff --git a/packages/SubstratumHelperService/src/projekt/substratum/helper/SubstratumHelperService.java b/packages/SubstratumHelperService/src/projekt/substratum/helper/SubstratumHelperService.java | |
new file mode 100644 | |
index 00000000000..1f31abe9f7c | |
--- /dev/null | |
+++ b/packages/SubstratumHelperService/src/projekt/substratum/helper/SubstratumHelperService.java | |
@@ -0,0 +1,228 @@ | |
+/* | |
+ * Copyright (C) 2018 Projekt Substratum | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); | |
+ * you may not use this file except in compliance with the License. | |
+ * You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software | |
+ * distributed under the License is distributed on an "AS IS" BASIS, | |
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ * See the License for the specific language governing permissions and | |
+ * limitations under the License. | |
+ */ | |
+package projekt.substratum.helper; | |
+ | |
+import android.app.Service; | |
+import android.content.Intent; | |
+import android.content.IIntentReceiver; | |
+import android.content.IIntentSender; | |
+import android.content.IntentSender; | |
+import android.content.pm.PackageInstaller; | |
+import android.content.pm.PackageInstaller.Session; | |
+import android.content.pm.PackageInstaller.SessionParams; | |
+import android.os.Binder; | |
+import android.os.Bundle; | |
+import android.os.Environment; | |
+import android.os.FileUtils; | |
+import android.os.IBinder; | |
+import android.os.Process; | |
+import android.os.SELinux; | |
+import android.util.Log; | |
+ | |
+import com.android.internal.substratum.ISubstratumHelperService; | |
+ | |
+import java.io.File; | |
+import java.io.FileInputStream; | |
+import java.io.FileOutputStream; | |
+import java.io.InputStream; | |
+import java.io.IOException; | |
+import java.io.OutputStream; | |
+import java.util.List; | |
+import java.util.concurrent.SynchronousQueue; | |
+import java.util.concurrent.TimeUnit; | |
+ | |
+public class SubstratumHelperService extends Service { | |
+ private static final String TAG = "SubstratumHelperService"; | |
+ | |
+ private final File EXTERNAL_CACHE_DIR = | |
+ new File(Environment.getExternalStorageDirectory(), ".substratum"); | |
+ private final File SYSTEM_THEME_DIR = new File(Environment.getDataSystemDirectory(), "theme"); | |
+ | |
+ ISubstratumHelperService mISubstratumHelperService = new ISubstratumHelperService.Stub() { | |
+ @Override | |
+ public void applyBootAnimation() { | |
+ if (!isAuthorized(Binder.getCallingUid())) return; | |
+ | |
+ File src = new File(EXTERNAL_CACHE_DIR, "bootanimation.zip"); | |
+ File dst = new File(SYSTEM_THEME_DIR, "bootanimation.zip"); | |
+ int perms = FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH; | |
+ | |
+ if (dst.exists()) dst.delete(); | |
+ FileUtils.copyFile(src, dst); | |
+ FileUtils.setPermissions(dst, perms, -1, -1); | |
+ SELinux.restorecon(dst); | |
+ src.delete(); | |
+ } | |
+ | |
+ @Override | |
+ public void applyShutdownAnimation() { | |
+ if (!isAuthorized(Binder.getCallingUid())) return; | |
+ | |
+ File src = new File(EXTERNAL_CACHE_DIR, "shutdownanimation.zip"); | |
+ File dst = new File(SYSTEM_THEME_DIR, "shutdownanimation.zip"); | |
+ int perms = FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH; | |
+ | |
+ if (dst.exists()) dst.delete(); | |
+ FileUtils.copyFile(src, dst); | |
+ FileUtils.setPermissions(dst, perms, -1, -1); | |
+ SELinux.restorecon(dst); | |
+ src.delete(); | |
+ } | |
+ | |
+ @Override | |
+ public void applyProfile(String name) { | |
+ if (!isAuthorized(Binder.getCallingUid())) return; | |
+ | |
+ FileUtils.deleteContents(SYSTEM_THEME_DIR); | |
+ | |
+ File profileDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + | |
+ "/substratum/profiles/" + name + "/theme"); | |
+ if (profileDir.exists()) { | |
+ File profileFonts = new File(profileDir, "fonts"); | |
+ if (profileFonts.exists()) { | |
+ File dst = new File(SYSTEM_THEME_DIR, "fonts"); | |
+ copyDir(profileFonts, dst); | |
+ } | |
+ | |
+ File profileSounds = new File(profileDir, "audio"); | |
+ if (profileSounds.exists()) { | |
+ File dst = new File(SYSTEM_THEME_DIR, "audio"); | |
+ copyDir(profileSounds, dst); | |
+ } | |
+ | |
+ File profileBootAnimation = new File(profileDir, "bootanimation.zip"); | |
+ if (profileBootAnimation.exists()) { | |
+ File dst = new File(SYSTEM_THEME_DIR, "bootanimation.zip"); | |
+ FileUtils.copyFile(profileBootAnimation, dst); | |
+ int perms = FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH; | |
+ FileUtils.setPermissions(dst, perms, -1, -1); | |
+ } | |
+ | |
+ File profileShutdownAnimation = new File(profileDir, "shutdownanimation.zip"); | |
+ if (profileShutdownAnimation.exists()) { | |
+ File dst = new File(SYSTEM_THEME_DIR, "shutdownanimation.zip"); | |
+ FileUtils.copyFile(profileShutdownAnimation, dst); | |
+ int perms = FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IROTH; | |
+ FileUtils.setPermissions(dst, perms, -1, -1); | |
+ } | |
+ | |
+ SELinux.restorecon(SYSTEM_THEME_DIR); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void installOverlay(List<String> paths) { | |
+ if (!isAuthorized(Binder.getCallingUid())) return; | |
+ LocalIntentReceiver receiver = new LocalIntentReceiver(); | |
+ for (String path : paths) { | |
+ //Settings.Global.putInt(mContext.getContentResolver(), | |
+ // Settings.Global.PACKAGE_VERIFIER_ENABLE, 0); | |
+ File apkFile = new File(path); | |
+ try { | |
+ //PackageParser.PackageLite pkg = PackageParser.parsePackageLite(apkFile, 0); | |
+ PackageInstaller installer = getPackageManager().getPackageInstaller(); | |
+ SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); | |
+ int sessionId = installer.createSession(params); | |
+ try (PackageInstaller.Session session = installer.openSession(sessionId)) { | |
+ try (InputStream in = new FileInputStream(apkFile); | |
+ OutputStream apkStream = session.openWrite( | |
+ "base.apk", 0, apkFile.length())) { | |
+ byte[] buffer = new byte[32 * 1024]; | |
+ long size = apkFile.length(); | |
+ while (size > 0) { | |
+ long toRead = (buffer.length < size) ? buffer.length : size; | |
+ int didRead = in.read(buffer, 0, (int) toRead); | |
+ apkStream.write(buffer, 0, didRead); | |
+ size -= didRead; | |
+ } | |
+ } | |
+ session.commit(receiver.getIntentSender()); | |
+ } | |
+ } catch (IOException e) { | |
+ Log.e(TAG, "Install failed", e); | |
+ continue; | |
+ } | |
+ | |
+ Intent result = receiver.getResult(); | |
+ int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, | |
+ PackageInstaller.STATUS_FAILURE); | |
+ if (status == PackageInstaller.STATUS_SUCCESS) { | |
+ String installedPackageName = result.getStringExtra( | |
+ PackageInstaller.EXTRA_PACKAGE_NAME); | |
+ } else { | |
+ // ¯\_(ツ)_/¯ | |
+ } | |
+ } | |
+ } | |
+ | |
+ private boolean isAuthorized(int uid) { | |
+ return Process.SYSTEM_UID == uid; | |
+ } | |
+ | |
+ private boolean copyDir(File src, File dst) { | |
+ File[] files = src.listFiles(); | |
+ boolean success = true; | |
+ | |
+ if (files != null) { | |
+ for (File file : files) { | |
+ File newFile = new File(dst, file.getName()); | |
+ if (file.isDirectory()) { | |
+ success &= copyDir(file, newFile); | |
+ } else { | |
+ success &= FileUtils.copyFile(file, newFile); | |
+ } | |
+ } | |
+ } else { | |
+ // not a directory | |
+ success = false; | |
+ } | |
+ return success; | |
+ } | |
+ }; | |
+ | |
+ @Override | |
+ public IBinder onBind(Intent intent) { | |
+ return mISubstratumHelperService.asBinder(); | |
+ } | |
+ | |
+ 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, IBinder whitelistToken, | |
+ 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/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml | |
index 8633fb6efae..a4a2af50f46 100644 | |
--- a/packages/SystemUI/AndroidManifest.xml | |
+++ b/packages/SystemUI/AndroidManifest.xml | |
@@ -318,6 +318,16 @@ | |
<data android:scheme="package" /> | |
</intent-filter> | |
+ <intent-filter> | |
+ <action android:name="com.android.systemui.action.RESTART_THEME" /> | |
+ </intent-filter> | |
+ </receiver> | |
+ | |
+ <receiver android:name=".SoundRefreshReceiver" | |
+ android:exported="false"> | |
+ <intent-filter> | |
+ <action android:name="com.android.systemui.action.REFRESH_SOUND" /> | |
+ </intent-filter> | |
</receiver> | |
<!-- started from PhoneWindowManager | |
diff --git a/packages/SystemUI/src/com/android/systemui/SoundRefreshReceiver.java b/packages/SystemUI/src/com/android/systemui/SoundRefreshReceiver.java | |
new file mode 100644 | |
index 00000000000..2d9a899ea63 | |
--- /dev/null | |
+++ b/packages/SystemUI/src/com/android/systemui/SoundRefreshReceiver.java | |
@@ -0,0 +1,34 @@ | |
+/* | |
+ * Copyright (C) 2018 Projekt Substratum | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file | |
+ * except in compliance with the License. You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software distributed under the | |
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
+ * KIND, either express or implied. See the License for the specific language governing | |
+ * permissions and limitations under the License. | |
+ */ | |
+ | |
+package com.android.systemui; | |
+ | |
+import android.content.BroadcastReceiver; | |
+import android.content.Context; | |
+import android.content.Intent; | |
+ | |
+import com.android.systemui.SystemUIApplication; | |
+import com.android.systemui.keyguard.KeyguardViewMediator; | |
+ | |
+public class SoundRefreshReceiver extends BroadcastReceiver { | |
+ public static String ACTION = "com.android.systemui.action.REFRESH_SOUND"; | |
+ | |
+ @Override | |
+ public void onReceive(Context context, Intent intent) { | |
+ if (ACTION.equals(intent.getAction())) { | |
+ ((SystemUIApplication) context.getApplicationContext()) | |
+ .getComponent(KeyguardViewMediator.class).refreshSounds(); | |
+ } | |
+ } | |
+} | |
diff --git a/packages/SystemUI/src/com/android/systemui/SysuiRestartReceiver.java b/packages/SystemUI/src/com/android/systemui/SysuiRestartReceiver.java | |
index cdeef2fbfad..75de186415a 100644 | |
--- a/packages/SystemUI/src/com/android/systemui/SysuiRestartReceiver.java | |
+++ b/packages/SystemUI/src/com/android/systemui/SysuiRestartReceiver.java | |
@@ -25,12 +25,19 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; | |
public class SysuiRestartReceiver extends BroadcastReceiver { | |
public static String ACTION = "com.android.systemui.action.RESTART"; | |
+ public static String ACTION_THEME = "com.android.systemui.action.RESTART_THEME"; | |
@Override | |
public void onReceive(Context context, Intent intent) { | |
- if (ACTION.equals(intent.getAction())) { | |
+ boolean runAction = ACTION.equals(intent.getAction()); | |
+ boolean runThemeAction = ACTION_THEME.equals(intent.getAction()); | |
+ | |
+ if (runAction) { | |
String pkg = intent.getData().toString().substring(10); | |
NotificationManager.from(context).cancel(pkg, SystemMessage.NOTE_PLUGIN); | |
+ } | |
+ | |
+ if (runAction || runThemeAction) { | |
Process.killProcess(Process.myPid()); | |
} | |
} | |
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java | |
index e239a18af50..d5bfe232dae 100644 | |
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java | |
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java | |
@@ -2236,5 +2236,23 @@ public class KeyguardViewMediator extends SystemUI { | |
public CharSequence getMessage() { | |
return mMessage; | |
} | |
+ } | |
+ | |
+ public void refreshSounds() { | |
+ ContentResolver cr = mContext.getContentResolver(); | |
+ String soundPath = Settings.Global.getString(cr, Settings.Global.LOCK_SOUND); | |
+ if (soundPath != null) { | |
+ mLockSoundId = mLockSounds.load(soundPath, 1); | |
+ } | |
+ if (soundPath == null || mLockSoundId == 0) { | |
+ Log.w(TAG, "failed to load lock sound from " + soundPath); | |
+ } | |
+ soundPath = Settings.Global.getString(cr, Settings.Global.UNLOCK_SOUND); | |
+ if (soundPath != null) { | |
+ mUnlockSoundId = mLockSounds.load(soundPath, 1); | |
+ } | |
+ if (soundPath == null || mUnlockSoundId == 0) { | |
+ Log.w(TAG, "failed to load unlock sound from " + soundPath); | |
+ } | |
} | |
} | |
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java | |
index 91df6e06237..9c54307faf0 100644 | |
--- a/services/core/java/com/android/server/am/ActivityManagerService.java | |
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java | |
@@ -283,6 +283,8 @@ import android.content.IIntentReceiver; | |
import android.content.IIntentSender; | |
import android.content.Intent; | |
import android.content.IntentFilter; | |
+import android.content.om.IOverlayManager; | |
+import android.content.om.OverlayInfo; | |
import android.content.pm.ActivityInfo; | |
import android.content.pm.ApplicationInfo; | |
import android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy; | |
@@ -14832,6 +14834,34 @@ public class ActivityManagerService extends IActivityManager.Stub | |
Context.WINDOW_SERVICE)).addView(v, lp); | |
} | |
+ public final void disableOverlays() { | |
+ try { | |
+ IOverlayManager iom = IOverlayManager.Stub.asInterface( | |
+ ServiceManager.getService("overlay")); | |
+ if (iom == null) { | |
+ return; | |
+ } | |
+ Log.d(TAG, "Contacting the Overlay Manager Service for the list of enabled overlays"); | |
+ Map<String, List<OverlayInfo>> allOverlays = iom.getAllOverlays(UserHandle.USER_SYSTEM); | |
+ if (allOverlays != null) { | |
+ Log.d(TAG, "The Overlay Manager Service provided the list of enabled overlays"); | |
+ Set<String> set = allOverlays.keySet(); | |
+ for (String targetPackageName : set) { | |
+ for (OverlayInfo oi : allOverlays.get(targetPackageName)) { | |
+ if (oi.isEnabled()) { | |
+ iom.setEnabled(oi.packageName, false, UserHandle.USER_SYSTEM); | |
+ Log.d(TAG, "Now disabling \'" + oi.packageName + "\'"); | |
+ } | |
+ } | |
+ } | |
+ } | |
+ } catch (RemoteException re) { | |
+ re.printStackTrace(); | |
+ Log.d(TAG, "RemoteException while trying to contact the Overlay Manager Service!"); | |
+ } | |
+ } | |
+ | |
+ | |
@Override | |
public void noteWakeupAlarm(IIntentSender sender, WorkSource workSource, int sourceUid, | |
String sourcePkg, String tag) { | |
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java | |
index b457a31bc83..e30a6cf7b47 100644 | |
--- a/services/core/java/com/android/server/am/AppErrors.java | |
+++ b/services/core/java/com/android/server/am/AppErrors.java | |
@@ -459,6 +459,14 @@ class AppErrors { | |
task = data.task; | |
msg.obj = data; | |
mService.mUiHandler.sendMessage(msg); | |
+ | |
+ // Send broadcast intent to alert Substratum | |
+ Intent intent = new Intent("projekt.substratum.APP_CRASHED"); | |
+ intent.putExtra("projekt.substratum.EXTRA_PACKAGE_NAME", r.info.packageName); | |
+ intent.putExtra("projekt.substratum.EXTRA_CRASH_REPEATING", data.repeating); | |
+ intent.putExtra("projekt.substratum.EXTRA_EXCEPTION_CLASS_NAME", | |
+ crashInfo.exceptionClassName); | |
+ mContext.sendBroadcast(intent); | |
} | |
int res = result.get(); | |
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java | |
index 1a0e4507e0a..3991723465d 100644 | |
--- a/services/core/java/com/android/server/audio/AudioService.java | |
+++ b/services/core/java/com/android/server/audio/AudioService.java | |
@@ -298,6 +298,7 @@ public class AudioService extends IAudioService.Stub | |
/* Sound effect file names */ | |
private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/"; | |
+ private static final String SOUND_EFFECTS_THEMED_PATH = "/data/system/theme/audio/ui/"; | |
private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>(); | |
/* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to | |
@@ -5377,11 +5378,15 @@ public class AudioService extends IAudioService.Stub | |
} | |
private String getSoundEffectFilePath(int effectType) { | |
- String filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH | |
- + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]); | |
+ String filePath = SOUND_EFFECTS_THEMED_PATH + | |
+ SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]); | |
if (!new File(filePath).isFile()) { | |
- filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH | |
+ filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH | |
+ SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]); | |
+ if (!new File(filePath).isFile()) { | |
+ filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH | |
+ + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]); | |
+ } | |
} | |
return filePath; | |
} | |
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java | |
index c7387018900..0a82f9349ba 100644 | |
--- a/services/core/java/com/android/server/om/OverlayManagerService.java | |
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java | |
@@ -539,7 +539,6 @@ public final class OverlayManagerService extends SystemService { | |
@Override | |
public boolean setEnabled(@Nullable final String packageName, final boolean enable, | |
int userId) throws RemoteException { | |
- enforceChangeOverlayPackagesPermission("setEnabled"); | |
userId = handleIncomingUser(userId, "setEnabled"); | |
if (packageName == null) { | |
return false; | |
@@ -558,7 +557,6 @@ public final class OverlayManagerService extends SystemService { | |
@Override | |
public boolean setEnabledExclusive(@Nullable final String packageName, final boolean enable, | |
int userId) throws RemoteException { | |
- enforceChangeOverlayPackagesPermission("setEnabled"); | |
userId = handleIncomingUser(userId, "setEnabled"); | |
if (packageName == null || !enable) { | |
return false; | |
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java | |
index 112059daf95..380ba5cd0dc 100644 | |
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java | |
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java | |
@@ -21,6 +21,7 @@ import static android.content.om.OverlayInfo.STATE_ENABLED; | |
import static android.content.om.OverlayInfo.STATE_ENABLED_STATIC; | |
import static android.content.om.OverlayInfo.STATE_MISSING_TARGET; | |
import static android.content.om.OverlayInfo.STATE_NO_IDMAP; | |
+import static android.content.om.OverlayInfo.STATE_OVERLAY_NOT_AVAILABLE; | |
import static android.content.om.OverlayInfo.STATE_OVERLAY_UPGRADING; | |
import static android.content.om.OverlayInfo.STATE_TARGET_UPGRADING; | |
@@ -313,7 +314,9 @@ final class OverlayManagerServiceImpl { | |
} | |
// check for enabled framework overlays | |
- modified = modified || !getEnabledOverlayPackageNames("android", userId).isEmpty(); | |
+ if (!"android".equals(targetPackageName)) { | |
+ modified = modified || !getEnabledOverlayPackageNames("android", userId).isEmpty(); | |
+ } | |
return modified; | |
} | |
@@ -682,6 +685,14 @@ final class OverlayManagerServiceImpl { | |
return STATE_MISSING_TARGET; | |
} | |
+ if (!targetPackage.applicationInfo.enabled) { | |
+ return STATE_MISSING_TARGET; | |
+ } | |
+ | |
+ if (!overlayPackage.applicationInfo.enabled) { | |
+ return STATE_OVERLAY_NOT_AVAILABLE; | |
+ } | |
+ | |
if (!mIdmapManager.idmapExists(overlayPackage, userId)) { | |
return STATE_NO_IDMAP; | |
} | |
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java | |
index cdb09ff0840..706004b09e2 100644 | |
--- a/services/core/java/com/android/server/pm/PackageManagerService.java | |
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java | |
@@ -475,6 +475,7 @@ public class PackageManagerService extends IPackageManager.Stub | |
static final int SCAN_AS_OEM = 1<<19; | |
static final int SCAN_AS_VENDOR = 1<<20; | |
static final int SCAN_AS_PRODUCT = 1<<21; | |
+ static final int SCAN_AS_THEME = 1<<22; | |
@IntDef(flag = true, prefix = { "SCAN_" }, value = { | |
SCAN_NO_DEX, | |
@@ -583,6 +584,8 @@ public class PackageManagerService extends IPackageManager.Stub | |
private static final String SYSTEM_OVERLAY_DIR = "/system/overlay"; | |
+ private static final String THEME_OVERLAY_DIR = "/data/system/theme"; | |
+ | |
/** Canonical intent used to identify what counts as a "web browser" app */ | |
private static final Intent sBrowserIntent; | |
static { | |
@@ -2638,6 +2641,14 @@ public class PackageManagerService extends IPackageManager.Stub | |
scanFlags | |
| SCAN_AS_SYSTEM, | |
0); | |
+ scanDirTracedLI(new File(THEME_OVERLAY_DIR), | |
+ mDefParseFlags | |
+ | PackageParser.PARSE_IS_SYSTEM_DIR, | |
+ scanFlags | |
+ | SCAN_AS_SYSTEM | |
+ | SCAN_AS_PRODUCT | |
+ | SCAN_AS_THEME, | |
+ 0); | |
mParallelPackageParserCallback.findStaticOverlayPackages(); | |
@@ -11371,16 +11382,26 @@ public class PackageManagerService extends IPackageManager.Stub | |
} | |
// The only case where we allow installation of a non-system overlay is when | |
- // its signature is signed with the platform certificate. | |
- PackageSetting platformPkgSetting = mSettings.getPackageLPr("android"); | |
- if ((platformPkgSetting.signatures.mSigningDetails | |
- != PackageParser.SigningDetails.UNKNOWN) | |
- && (compareSignatures( | |
- platformPkgSetting.signatures.mSigningDetails.signatures, | |
- pkg.mSigningDetails.signatures) | |
- != PackageManager.SIGNATURE_MATCH)) { | |
+ // its signature is signed with a whitelisted OEM theme system certificate. | |
+ ArraySet<String> wlSigApps = | |
+ SystemConfig.getInstance().getThemeSystemSignatureWhitelistedApps(); | |
+ boolean sigAllowed = false; | |
+ for (String pkgName : wlSigApps) { | |
+ PackageSetting platformPkgSetting = mSettings.getPackageLPr(pkgName); | |
+ sigAllowed = (platformPkgSetting.signatures.mSigningDetails | |
+ != PackageParser.SigningDetails.UNKNOWN) | |
+ && (compareSignatures( | |
+ platformPkgSetting.signatures.mSigningDetails.signatures, | |
+ pkg.mSigningDetails.signatures) | |
+ == PackageManager.SIGNATURE_MATCH); | |
+ if (sigAllowed) { | |
+ break; | |
+ } | |
+ } | |
+ | |
+ if (!sigAllowed) { | |
throw new PackageManagerException("Overlay " + pkg.packageName + | |
- " must be signed with the platform certificate."); | |
+ " must be signed with a whitelisted OEM theme system certificate."); | |
} | |
} | |
} | |
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java | |
index c3c49d46552..ff693e94fd3 100644 | |
--- a/services/core/java/com/android/server/power/ShutdownThread.java | |
+++ b/services/core/java/com/android/server/power/ShutdownThread.java | |
@@ -377,7 +377,7 @@ public final class ShutdownThread extends Thread { | |
pd.setCancelable(false); | |
pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); | |
- pd.show(); | |
+ if (!themeShutdownAnimationExists()) pd.show(); | |
return pd; | |
} | |
@@ -481,6 +481,10 @@ public final class ShutdownThread extends Thread { | |
SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason); | |
} | |
+ if (themeShutdownAnimationExists()) { | |
+ startShutdownAnimation(); | |
+ } | |
+ | |
/* | |
* If we are rebooting into safe mode, write a system property | |
* indicating so. | |
@@ -701,6 +705,7 @@ public final class ShutdownThread extends Thread { | |
*/ | |
public static void rebootOrShutdown(final Context context, boolean reboot, String reason) { | |
if (reboot) { | |
+ stopShutdownAnimation(); | |
Log.i(TAG, "Rebooting, reason: " + reason); | |
PowerManagerService.lowLevelReboot(reason); | |
Log.e(TAG, "Reboot failed, will attempt shutdown instead"); | |
@@ -721,6 +726,9 @@ public final class ShutdownThread extends Thread { | |
} catch (InterruptedException unused) { | |
} | |
} | |
+ | |
+ stopShutdownAnimation(); | |
+ | |
// Shutdown power | |
Log.i(TAG, "Performing low-level shutdown..."); | |
PowerManagerService.lowLevelShutdown(reason); | |
@@ -813,4 +821,24 @@ public final class ShutdownThread extends Thread { | |
} | |
} | |
} | |
+ | |
+ private static boolean themeShutdownAnimationExists() { | |
+ return new File("/data/system/theme/shutdownanimation.zip").exists(); | |
+ } | |
+ | |
+ private static void startShutdownAnimation() { | |
+ SystemProperties.set("service.bootanim.exit", "0"); | |
+ SystemProperties.set("sys.powerctl", "animate"); | |
+ SystemProperties.set("ctl.start", "bootanim"); | |
+ } | |
+ | |
+ private static void stopShutdownAnimation() { | |
+ SystemProperties.set("service.bootanim.exit", "1"); | |
+ while (SystemProperties.get("init.svc.bootanim").equals("running")) { | |
+ try { | |
+ Thread.sleep(10); | |
+ } catch (InterruptedException unused) { | |
+ } | |
+ } | |
+ } | |
} | |
diff --git a/services/core/java/com/android/server/substratum/SoundUtils.java b/services/core/java/com/android/server/substratum/SoundUtils.java | |
new file mode 100644 | |
index 00000000000..4b0acbf4332 | |
--- /dev/null | |
+++ b/services/core/java/com/android/server/substratum/SoundUtils.java | |
@@ -0,0 +1,229 @@ | |
+/* | |
+ * Copyright (C) 2018 Projekt Substratum | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); | |
+ * you may not use this file except in compliance with the License. | |
+ * You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software | |
+ * distributed under the License is distributed on an "AS IS" BASIS, | |
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ * See the License for the specific language governing permissions and | |
+ * limitations under the License. | |
+ */ | |
+ | |
+package com.android.server.substratum; | |
+ | |
+import android.content.ContentResolver; | |
+import android.content.ContentValues; | |
+import android.content.Context; | |
+import android.database.Cursor; | |
+import android.database.sqlite.SQLiteConstraintException; | |
+import android.media.RingtoneManager; | |
+import android.net.Uri; | |
+import android.os.SystemProperties; | |
+import android.os.UserHandle; | |
+import android.provider.MediaStore; | |
+import android.provider.Settings; | |
+import android.util.Log; | |
+ | |
+import java.io.File; | |
+import java.util.Arrays; | |
+ | |
+public class SoundUtils { | |
+ private static final String TAG = "SubstratumService"; | |
+ 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"; | |
+ | |
+ 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) { | |
+ 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 | |
+ private static Boolean allowedUISound(String targetValue) { | |
+ String[] allowed_themable = { | |
+ "lock_sound", | |
+ "unlock_sound" | |
+ }; | |
+ | |
+ return Arrays.asList(allowed_themable).contains(targetValue); | |
+ } | |
+ | |
+ private 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 boolean setAudible(Context context, File ringtone, 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, ringtone.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(); | |
+ String id = String.valueOf(c.getLong(0)); | |
+ c.close(); | |
+ | |
+ newUri = Uri.withAppendedPath(Uri.parse(MEDIA_CONTENT_URI), id); | |
+ try { | |
+ context.getContentResolver().update(uri, values, | |
+ MediaStore.MediaColumns._ID + "=" + id, null); | |
+ } catch (SQLiteConstraintException e) { | |
+ // intentionally left empty | |
+ } | |
+ } | |
+ | |
+ if (newUri == null) { | |
+ newUri = context.getContentResolver().insert(uri, values); | |
+ } | |
+ | |
+ try { | |
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, newUri); | |
+ } catch (Exception e) { | |
+ Log.e(TAG, "Failed to apply audible", e); | |
+ return false; | |
+ } | |
+ | |
+ return true; | |
+ } | |
+ | |
+ public static boolean setUIAudible(Context context, 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, ringtone_file.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(); | |
+ String id = String.valueOf(c.getLong(0)); | |
+ c.close(); | |
+ | |
+ newUri = Uri.withAppendedPath(Uri.parse(MEDIA_CONTENT_URI), id); | |
+ try { | |
+ context.getContentResolver().update(uri, values, | |
+ MediaStore.MediaColumns._ID + "=" + id, null); | |
+ } catch (SQLiteConstraintException e) { | |
+ // intentionally left empty | |
+ } | |
+ } | |
+ | |
+ if (newUri == null) { | |
+ newUri = context.getContentResolver().insert(uri, values); | |
+ } | |
+ | |
+ try { | |
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, newUri); | |
+ } catch (Exception e) { | |
+ Log.e(TAG, "Failed to apply ui audible", e); | |
+ return false; | |
+ } | |
+ | |
+ return true; | |
+ } | |
+ | |
+ public static boolean setDefaultAudible(Context context, int type) { | |
+ final String audiblePath = getDefaultAudiblePath(type); | |
+ if (audiblePath == null) { | |
+ return false; | |
+ } | |
+ | |
+ 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(); | |
+ String id = String.valueOf(c.getLong(0)); | |
+ c.close(); | |
+ | |
+ uri = Uri.withAppendedPath(Uri.parse(MEDIA_CONTENT_URI), id); | |
+ } | |
+ | |
+ try { | |
+ RingtoneManager.setActualDefaultRingtoneUri(context, type, uri); | |
+ } catch (Exception e) { | |
+ Log.e(TAG, "Failed to apply default audible", e); | |
+ return false; | |
+ } | |
+ | |
+ return true; | |
+ } | |
+} | |
+ | |
diff --git a/services/core/java/com/android/server/substratum/SubstratumService.java b/services/core/java/com/android/server/substratum/SubstratumService.java | |
new file mode 100644 | |
index 00000000000..5c5634bbf57 | |
--- /dev/null | |
+++ b/services/core/java/com/android/server/substratum/SubstratumService.java | |
@@ -0,0 +1,1101 @@ | |
+/* | |
+ * Copyright (C) 2018 Projekt Development | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); | |
+ * you may not use this file except in compliance with the License. | |
+ * You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software | |
+ * distributed under the License is distributed on an "AS IS" BASIS, | |
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ * See the License for the specific language governing permissions and | |
+ * limitations under the License. | |
+ */ | |
+ | |
+package com.android.server.substratum; | |
+ | |
+import android.annotation.NonNull; | |
+import android.app.AppGlobals; | |
+import android.content.ComponentName; | |
+import android.content.Context; | |
+import android.content.Intent; | |
+import android.content.om.IOverlayManager; | |
+import android.content.om.OverlayInfo; | |
+import android.content.pm.ApplicationInfo; | |
+import android.content.pm.IPackageDeleteObserver; | |
+import android.content.pm.IPackageInstallObserver2; | |
+import android.content.pm.IPackageManager; | |
+import android.content.pm.PackageInfo; | |
+import android.content.pm.PackageManager; | |
+import android.content.pm.PackageManager.NameNotFoundException; | |
+import android.content.pm.Signature; | |
+import android.content.res.AssetManager; | |
+import android.content.ServiceConnection; | |
+import android.content.substratum.ISubstratumService; | |
+import android.database.ContentObserver; | |
+import android.media.AudioManager; | |
+import android.media.RingtoneManager; | |
+import android.net.Uri; | |
+import android.os.Binder; | |
+import android.os.Bundle; | |
+import android.os.Environment; | |
+import android.os.FileUtils; | |
+import android.os.Handler; | |
+import android.os.IBinder; | |
+import android.os.RemoteException; | |
+import android.os.SELinux; | |
+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 com.android.internal.substratum.ISubstratumHelperService; | |
+import com.android.server.SystemService; | |
+ | |
+import java.io.BufferedInputStream; | |
+import java.io.File; | |
+import java.io.FileInputStream; | |
+import java.io.FileNotFoundException; | |
+import java.io.FileOutputStream; | |
+import java.io.InputStream; | |
+import java.io.IOException; | |
+import java.io.OutputStream; | |
+import java.lang.Throwable; | |
+import java.util.Arrays; | |
+import java.util.List; | |
+import java.util.Map; | |
+import java.util.zip.ZipEntry; | |
+import java.util.zip.ZipInputStream; | |
+import static android.os.Binder.getCallingUid; | |
+ | |
+public final class SubstratumService extends SystemService { | |
+ | |
+ private static final String TAG = "SubstratumService"; | |
+ private static final String SUBSTRATUM_PACKAGE = "projekt.substratum"; | |
+ private static final boolean DEBUG = true; | |
+ | |
+ private static final Signature SUBSTRATUM_SIGNATURE = new Signature("" | |
+ + "308202eb308201d3a003020102020411c02f2f300d06092a864886f70d01010b050030263124302206" | |
+ + "03550403131b5375627374726174756d20446576656c6f706d656e74205465616d301e170d31363037" | |
+ + "30333032333335385a170d3431303632373032333335385a3026312430220603550403131b53756273" | |
+ + "74726174756d20446576656c6f706d656e74205465616d30820122300d06092a864886f70d01010105" | |
+ + "000382010f003082010a02820101008855626336f645a335aa5d40938f15db911556385f72f72b5f8b" | |
+ + "ad01339aaf82ae2d30302d3f2bba26126e8da8e76a834e9da200cdf66d1d5977c90a4e4172ce455704" | |
+ + "a22bbe4a01b08478673b37d23c34c8ade3ec040a704da8570d0a17fce3c7397ea63ebcde3a2a3c7c5f" | |
+ + "983a163e4cd5a1fc80c735808d014df54120e2e5708874739e22e5a22d50e1c454b2ae310b480825ab" | |
+ + "3d877f675d6ac1293222602a53080f94e4a7f0692b627905f69d4f0bb1dfd647e281cc0695e0733fa3" | |
+ + "efc57d88706d4426c4969aff7a177ac2d9634401913bb20a93b6efe60e790e06dad3493776c2c0878c" | |
+ + "e82caababa183b494120edde3d823333efd464c8aea1f51f330203010001a321301f301d0603551d0e" | |
+ + "04160414203ec8b075d1c9eb9d600100281c3924a831a46c300d06092a864886f70d01010b05000382" | |
+ + "01010042d4bd26d535ce2bf0375446615ef5bf25973f61ecf955bdb543e4b6e6b5d026fdcab09fec09" | |
+ + "c747fb26633c221df8e3d3d0fe39ce30ca0a31547e9ec693a0f2d83e26d231386ff45f8e4fd5c06095" | |
+ + "8681f9d3bd6db5e940b1e4a0b424f5c463c79c5748a14a3a38da4dd7a5499dcc14a70ba82a50be5fe0" | |
+ + "82890c89a27e56067d2eae952e0bcba4d6beb5359520845f1fdb7df99868786055555187ba46c69ee6" | |
+ + "7fa2d2c79e74a364a8b3544997dc29cc625395e2f45bf8bdb2c9d8df0d5af1a59a58ad08b32cdbec38" | |
+ + "19fa49201bb5b5aadeee8f2f096ac029055713b77054e8af07cd61fe97f7365d0aa92d570be98acb89" | |
+ + "41b8a2b0053b54f18bfde092eb"); | |
+ | |
+ private static final Signature SUBSTRATUM_CI_SIGNATURE = new Signature("" | |
+ + "308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e" | |
+ + "64726f69642044656275673110300e060355040a0c07416e64726f6964310b30090603550406130255" | |
+ + "53301e170d3137303232333036303730325a170d3437303231363036303730325a3037311630140603" | |
+ + "5504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906" | |
+ + "035504061302555330819f300d06092a864886f70d010101050003818d00308189028181008aa6cf56" | |
+ + "e3ba4d0921da3baf527529205efbe440e1f351c40603afa5e6966e6a6ef2def780c8be80d189dc6101" | |
+ + "935e6f8340e61dc699cfd34d50e37d69bf66fbb58619d0ebf66f22db5dbe240b6087719aa3ceb1c68f" | |
+ + "3fa277b8846f1326763634687cc286b0760e51d1b791689fa2d948ae5f31cb8e807e00bd1eb72788b2" | |
+ + "330203010001300d06092a864886f70d0101050500038181007b2b7e432bff612367fbb6fdf8ed0ad1" | |
+ + "a19b969e4c4ddd8837d71ae2ec0c35f52fe7c8129ccdcdc41325f0bcbc90c38a0ad6fc0c604a737209" | |
+ + "17d37421955c47f9104ea56ad05031b90c748b94831969a266fa7c55bc083e20899a13089402be49a5" | |
+ + "edc769811adc2b0496a8a066924af9eeb33f8d57d625a5fa150f7bc18e55"); | |
+ | |
+ private static final File SYSTEM_THEME_DIR = | |
+ new File(Environment.getDataSystemDirectory(), "theme"); | |
+ private static final File SYSTEM_THEME_CACHE_DIR = new File(SYSTEM_THEME_DIR, "cache"); | |
+ private static final File SYSTEM_THEME_FONT_DIR = new File(SYSTEM_THEME_DIR, "fonts"); | |
+ private static final File SYSTEM_THEME_AUDIO_DIR = new File(SYSTEM_THEME_DIR, "audio"); | |
+ private static final File SYSTEM_THEME_RINGTONE_DIR = | |
+ new File(SYSTEM_THEME_AUDIO_DIR, "ringtones"); | |
+ private static final File SYSTEM_THEME_NOTIFICATION_DIR = | |
+ new File(SYSTEM_THEME_AUDIO_DIR, "notifications"); | |
+ private static final File SYSTEM_THEME_ALARM_DIR = new File(SYSTEM_THEME_AUDIO_DIR, "alarms"); | |
+ private static final File SYSTEM_THEME_UI_SOUNDS_DIR = new File(SYSTEM_THEME_AUDIO_DIR, "ui"); | |
+ private static final File SYSTEM_THEME_BOOTANIMATION_DIR = | |
+ new File(SYSTEM_THEME_DIR, "bootanimation.zip"); | |
+ private static final File SYSTEM_THEME_SHUTDOWNANIMATION_DIR = | |
+ new File(SYSTEM_THEME_DIR, "shutdownanimation.zip"); | |
+ | |
+ private static final Signature[] AUTHORIZED_SIGNATURES = new Signature[]{ | |
+ SUBSTRATUM_SIGNATURE, | |
+ SUBSTRATUM_CI_SIGNATURE, | |
+ }; | |
+ | |
+ private static final List<Sound> SOUNDS = Arrays.asList( | |
+ new Sound(SYSTEM_THEME_UI_SOUNDS_DIR.getAbsolutePath(), "/SoundsCache/ui/", "Effect_Tick", | |
+ "Effect_Tick", RingtoneManager.TYPE_RINGTONE), | |
+ new Sound(SYSTEM_THEME_UI_SOUNDS_DIR.getAbsolutePath(), "/SoundsCache/ui/", "lock_sound", | |
+ "Lock"), | |
+ new Sound(SYSTEM_THEME_UI_SOUNDS_DIR.getAbsolutePath(), "/SoundsCache/ui/", "unlock_sound", | |
+ "Unlock"), | |
+ new Sound(SYSTEM_THEME_ALARM_DIR.getAbsolutePath(), "/SoundsCache/alarms/", "alarm", | |
+ "alarm", RingtoneManager.TYPE_ALARM), | |
+ new Sound(SYSTEM_THEME_NOTIFICATION_DIR.getAbsolutePath(), "/SoundsCache/notifications/", | |
+ "notification", "notification", RingtoneManager.TYPE_NOTIFICATION), | |
+ new Sound(SYSTEM_THEME_RINGTONE_DIR.getAbsolutePath(), "/SoundsCache/ringtones/", | |
+ "ringtone", "ringtone", RingtoneManager.TYPE_RINGTONE) | |
+ ); | |
+ | |
+ private IOverlayManager mOm; | |
+ private IPackageManager mPm; | |
+ private boolean mIsWaiting; | |
+ private String mInstalledPackageName; | |
+ | |
+ private Context mContext; | |
+ private final Object mLock = new Object(); | |
+ private boolean mSigOverride = false; | |
+ private SettingsObserver mObserver = new SettingsObserver(); | |
+ | |
+ private ISubstratumHelperService mHelperService; | |
+ private final ServiceConnection mHelperConnection = new ServiceConnection() { | |
+ @Override | |
+ public void onServiceConnected(ComponentName name, IBinder service) { | |
+ mHelperService = ISubstratumHelperService.Stub.asInterface(service); | |
+ log("Helper service connected"); | |
+ } | |
+ | |
+ @Override | |
+ public void onServiceDisconnected(ComponentName name) { | |
+ mHelperService = null; | |
+ log("Helper service disconnected"); | |
+ } | |
+ }; | |
+ | |
+ public SubstratumService(@NonNull final Context context) { | |
+ super(context); | |
+ mContext = context; | |
+ } | |
+ | |
+ @Override | |
+ public void onBootPhase(int phase) { | |
+ if (phase == PHASE_SYSTEM_SERVICES_READY) { | |
+ if (makeDir(SYSTEM_THEME_DIR)) { | |
+ restoreconThemeDir(); | |
+ } | |
+ | |
+ mContext.getContentResolver().registerContentObserver( | |
+ Settings.Secure.getUriFor(Settings.Secure.FORCE_AUTHORIZE_SUBSTRATUM_PACKAGES), | |
+ false, mObserver); | |
+ updateSettings(); | |
+ | |
+ mOm = IOverlayManager.Stub.asInterface(ServiceManager.getService("overlay")); | |
+ mPm = AppGlobals.getPackageManager(); | |
+ publishBinderService("substratum", mService); | |
+ | |
+ log("published substratum service"); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void onStart() { | |
+ // Intentionally left empty | |
+ } | |
+ | |
+ @Override | |
+ public void onSwitchUser(final int newUserId) { | |
+ // Intentionally left empty | |
+ } | |
+ | |
+ private void waitForHelperConnection() { | |
+ if (mHelperService == null) { | |
+ Intent intent = new Intent("projekt.substratum.helper.SubstratumHelperService"); | |
+ intent.setPackage("projekt.substratum.helper"); | |
+ mContext.bindServiceAsUser(intent, mHelperConnection, | |
+ Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT_BACKGROUND, UserHandle.SYSTEM); | |
+ int retryCount = 1; | |
+ while (mHelperService == null && retryCount <= 30) { | |
+ try { | |
+ Thread.sleep(100); | |
+ } catch (InterruptedException ignored) { | |
+ } | |
+ retryCount++; | |
+ } | |
+ } | |
+ } | |
+ | |
+ private boolean doSignaturesMatch(String packageName, Signature signature) { | |
+ if (packageName != null) { | |
+ try { | |
+ PackageInfo pi = mPm.getPackageInfo(packageName, | |
+ PackageManager.GET_SIGNATURES, UserHandle.USER_SYSTEM); | |
+ if (pi.signatures != null | |
+ && pi.signatures.length == 1 | |
+ && signature.equals(pi.signatures[0])) { | |
+ return true; | |
+ } | |
+ } catch (RemoteException ignored) { | |
+ return false; | |
+ } | |
+ } | |
+ return false; | |
+ } | |
+ | |
+ private void checkCallerAuthorization(int uid) { | |
+ String callingPackage; | |
+ try { | |
+ callingPackage = mPm.getPackagesForUid(uid)[0]; | |
+ } catch (RemoteException ignored) { | |
+ throw new SecurityException("Cannot check caller authorization"); | |
+ } | |
+ | |
+ if (TextUtils.equals(callingPackage, SUBSTRATUM_PACKAGE)) { | |
+ for (Signature sig : AUTHORIZED_SIGNATURES) { | |
+ if (doSignaturesMatch(callingPackage, sig)) { | |
+ return; | |
+ } | |
+ } | |
+ } | |
+ | |
+ if (mSigOverride) { | |
+ log("\'" + callingPackage + "\' is not an authorized calling package, but the user " + | |
+ "has explicitly allowed all calling packages, " + | |
+ "validating calling package permissions..."); | |
+ return; | |
+ } | |
+ | |
+ throw new SecurityException("Caller is not authorized"); | |
+ } | |
+ | |
+ private final IBinder mService = new ISubstratumService.Stub() { | |
+ @Override | |
+ public void installOverlay(List<String> paths) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ synchronized (mLock) { | |
+ waitForHelperConnection(); | |
+ mHelperService.installOverlay(paths); | |
+ } | |
+ } catch (RemoteException ignored) { | |
+ // ¯\_(ツ)_/¯ | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void uninstallOverlay(List<String> packages, boolean restartUi) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ synchronized (mLock) { | |
+ PackageDeleteObserver observer = new PackageDeleteObserver(); | |
+ for (String p : packages) { | |
+ if (isOverlayEnabled(p)) { | |
+ log("Remover - disabling overlay for \'" + p + "\'..."); | |
+ switchOverlayState(p, false); | |
+ } | |
+ | |
+ try { | |
+ PackageInfo pi = mPm.getPackageInfo(p, 0, UserHandle.USER_SYSTEM); | |
+ if ((pi.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0 && | |
+ pi.overlayTarget != null) { | |
+ log("Remover - uninstalling \'" + p + "\'..."); | |
+ mIsWaiting = true; | |
+ mPm.deletePackageAsUser( | |
+ p, | |
+ pi.versionCode, | |
+ observer, | |
+ 0, | |
+ UserHandle.USER_SYSTEM); | |
+ | |
+ while (mIsWaiting) { | |
+ try { | |
+ Thread.sleep(1); | |
+ } catch (InterruptedException e) { | |
+ // Someone interrupted my sleep, ugh! | |
+ } | |
+ } | |
+ } | |
+ } catch (RemoteException e) { | |
+ logE("There is an exception when trying to uninstall package", e); | |
+ } | |
+ } | |
+ if (restartUi) { | |
+ restartUi(); | |
+ } | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void switchOverlay(List<String> packages, boolean enable, boolean restartUi) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ synchronized (mLock) { | |
+ for (String p : packages) { | |
+ log(enable ? "Enabling" : "Disabling" + " overlay " + p); | |
+ switchOverlayState(p, enable); | |
+ } | |
+ if (restartUi) { | |
+ restartUi(); | |
+ } | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void setPriority(List<String> packages, boolean restartUi) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ synchronized (mLock) { | |
+ log("PriorityJob - processing priority changes..."); | |
+ for (int i = 0; i < packages.size() - 1; i++) { | |
+ String parentName = packages.get(i); | |
+ String packageName = packages.get(i + 1); | |
+ | |
+ mOm.setPriority(packageName, parentName, | |
+ UserHandle.USER_SYSTEM); | |
+ } | |
+ if (restartUi) { | |
+ restartUi(); | |
+ } | |
+ } | |
+ } catch (RemoteException e) { | |
+ logE("There is an exception when trying to adjust overlay priority", e); | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void restartSystemUI() { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ log("Restarting SystemUI..."); | |
+ restartUi(); | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void copy(String source, String destination) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ log("CopyJob - copying \'" + source + "\' to \'" + destination + "\'..."); | |
+ File sourceFile = new File(source); | |
+ if (sourceFile.exists()) { | |
+ if (sourceFile.isFile()) { | |
+ FileUtils.copyFile(sourceFile, new File(destination)); | |
+ } else { | |
+ copyDir(source, destination); | |
+ } | |
+ } else { | |
+ logE("CopyJob - \'" + source + "\' does not exist, aborting..."); | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void move(String source, String destination) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ log("MoveJob - moving \'" + source + "\' to \'" + destination + "\'..."); | |
+ File sourceFile = new File(source); | |
+ if (sourceFile.exists()) { | |
+ if (sourceFile.isFile()) { | |
+ FileUtils.copyFile(sourceFile, new File(destination)); | |
+ } else { | |
+ copyDir(source, destination); | |
+ } | |
+ FileUtils.deleteContentsAndDir(sourceFile); | |
+ } else { | |
+ logE("MoveJob - \'" + source + "\' does not exist, aborting..."); | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void mkdir(String destination) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ log("MkdirJob - creating \'" + destination + "\'..."); | |
+ makeDir(new File(destination)); | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void deleteDirectory(String directory, boolean withParent) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ if (withParent) { | |
+ FileUtils.deleteContentsAndDir(new File(directory)); | |
+ } else { | |
+ FileUtils.deleteContents(new File(directory)); | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void applyBootanimation(String name) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ if (name == null) { | |
+ log("Restoring system boot animation..."); | |
+ clearBootAnimation(); | |
+ } else { | |
+ log("Configuring themed boot animation..."); | |
+ copyBootAnimation(name); | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void applyFonts(String pid, String fileName) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ if (pid == null) { | |
+ log("Restoring system font..."); | |
+ clearFonts(); | |
+ } else { | |
+ log("Configuring theme font..."); | |
+ copyFonts(pid, fileName); | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void applySounds(String pid, String fileName) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ if (pid == null) { | |
+ log("Restoring system sounds..."); | |
+ clearSounds(); | |
+ } else { | |
+ log("Configuring theme sounds..."); | |
+ applyThemedSounds(pid, fileName); | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void applyProfile(List<String> enable, List<String> disable, String name, | |
+ boolean restartUi) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ log("ProfileJob - Applying profile: " + name); | |
+ waitForHelperConnection(); | |
+ boolean mRestartUi = restartUi; | |
+ | |
+ boolean oldFontsExists = SYSTEM_THEME_FONT_DIR.exists(); | |
+ boolean oldSoundsExists = SYSTEM_THEME_AUDIO_DIR.exists(); | |
+ | |
+ mHelperService.applyProfile(name); | |
+ | |
+ boolean newFontsExists = SYSTEM_THEME_FONT_DIR.exists(); | |
+ boolean newSoundsExists = SYSTEM_THEME_AUDIO_DIR.exists(); | |
+ | |
+ if (oldFontsExists || newFontsExists) { | |
+ refreshFonts(); | |
+ } | |
+ | |
+ if (oldSoundsExists || newSoundsExists) { | |
+ refreshSounds(); | |
+ mRestartUi = true; | |
+ } | |
+ | |
+ for (String overlay : disable) { | |
+ switchOverlayState(overlay, false); | |
+ } | |
+ | |
+ for (String overlay : enable) { | |
+ switchOverlayState(overlay, true); | |
+ } | |
+ | |
+ if (mRestartUi) { | |
+ restartUi(); | |
+ } | |
+ | |
+ log("ProfileJob - " + name + " successfully applied."); | |
+ } catch (RemoteException e) { | |
+ logE("Failed to apply profile", e); | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void applyShutdownAnimation(String name) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ if (name == null) { | |
+ log("Restoring system shutdown animation..."); | |
+ clearShutdownAnimation(); | |
+ } else { | |
+ log("Configuring themed shutdown animation..."); | |
+ copyShutdownAnimation(name); | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public Map getAllOverlays(int uid) { | |
+ checkCallerAuthorization(Binder.getCallingUid()); | |
+ try { | |
+ return mOm.getAllOverlays(uid); | |
+ } catch (RemoteException e) { | |
+ logE("There is an exception when trying to get all overlays", e); | |
+ return null; | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public boolean setEnabled(final String packageName, final boolean enable, | |
+ int userId) throws RemoteException { | |
+ userId = Binder.getCallingUid(); | |
+ if (packageName == null) { | |
+ return false; | |
+ } | |
+ log("setEnabled - File name = \'" + packageName + "\'"); | |
+ final long ident = Binder.clearCallingIdentity(); | |
+ try { | |
+ synchronized (mLock) { | |
+ return mOm.setEnabled(packageName, enable, UserHandle.USER_SYSTEM); | |
+ } | |
+ } finally { | |
+ Binder.restoreCallingIdentity(ident); | |
+ } | |
+ } | |
+ }; | |
+ | |
+ private Context getAppContext(String packageName) { | |
+ Context ctx = null; | |
+ try { | |
+ ctx = mContext.createPackageContext(packageName, | |
+ Context.CONTEXT_IGNORE_SECURITY); | |
+ } catch (NameNotFoundException e) { | |
+ logE("Failed to get " + packageName + " context"); | |
+ } | |
+ return ctx; | |
+ } | |
+ | |
+ private void switchOverlayState(String packageName, boolean enable) { | |
+ try { | |
+ mOm.setEnabled(packageName, enable, UserHandle.USER_SYSTEM); | |
+ } catch (RemoteException e) { | |
+ logE("There is an exception when trying to switch overlay state", e); | |
+ } | |
+ } | |
+ | |
+ private boolean isOverlayEnabled(String packageName) { | |
+ boolean enabled = false; | |
+ try { | |
+ OverlayInfo info = mOm.getOverlayInfo(packageName, UserHandle.USER_SYSTEM); | |
+ if (info != null) { | |
+ enabled = info.isEnabled(); | |
+ } else { | |
+ logE("Can't find OverlayInfo for " + packageName); | |
+ } | |
+ } catch (RemoteException e) { | |
+ logE("There is an exception when trying to check overlay state", e); | |
+ } | |
+ return enabled; | |
+ } | |
+ | |
+ private void restartUi() { | |
+ Intent i = new Intent("com.android.systemui.action.RESTART_THEME"); | |
+ i.setPackage("com.android.systemui"); | |
+ mContext.sendBroadcastAsUser(i, UserHandle.SYSTEM); | |
+ } | |
+ | |
+ private void copyBootAnimation(String fileName) { | |
+ try { | |
+ waitForHelperConnection(); | |
+ mHelperService.applyBootAnimation(); | |
+ } catch (RemoteException e) { | |
+ logE("There is an exception when trying to apply boot animation", e); | |
+ } | |
+ } | |
+ | |
+ private void clearBootAnimation() { | |
+ if (SYSTEM_THEME_BOOTANIMATION_DIR.exists()) { | |
+ boolean deleted = SYSTEM_THEME_BOOTANIMATION_DIR.delete(); | |
+ if (!deleted) { | |
+ logE("Could not delete themed boot animation"); | |
+ } | |
+ } | |
+ } | |
+ | |
+ private void copyShutdownAnimation(String fileName) { | |
+ try { | |
+ waitForHelperConnection(); | |
+ mHelperService.applyShutdownAnimation(); | |
+ } catch (RemoteException e) { | |
+ logE("There is an exception when trying to apply shutdown animation", e); | |
+ } | |
+ } | |
+ | |
+ private void clearShutdownAnimation() { | |
+ if (SYSTEM_THEME_SHUTDOWNANIMATION_DIR.exists()) { | |
+ boolean deleted = SYSTEM_THEME_SHUTDOWNANIMATION_DIR.delete(); | |
+ if (!deleted) { | |
+ logE("Could not delete themed shutdown animation"); | |
+ } | |
+ } | |
+ } | |
+ | |
+ 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(SYSTEM_THEME_CACHE_DIR, "FontCache"); | |
+ if (cacheDir.exists()) { | |
+ FileUtils.deleteContentsAndDir(cacheDir); | |
+ } | |
+ | |
+ boolean created = cacheDir.mkdirs(); | |
+ if (!created) { | |
+ Log.e(TAG, "Could not create cache directory..."); | |
+ logE("Could not create cache directory"); | |
+ } | |
+ | |
+ // Copy system fonts into our cache dir | |
+ copyDir("/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(); | |
+ File fontZip = new File(cacheDir, zipFileName); | |
+ try (InputStream inputStream = am.open("fonts/" + zipFileName)) { | |
+ FileUtils.copyToFile(inputStream, fontZip); | |
+ } catch (IOException e) { | |
+ logE("There is an exception when trying to copy themed fonts", e); | |
+ } | |
+ | |
+ // Unzip new fonts and delete zip file, overwriting any system fonts | |
+ unzip(fontZip.getAbsolutePath(), cacheDir.getAbsolutePath()); | |
+ | |
+ boolean deleted = fontZip.delete(); | |
+ if (!deleted) { | |
+ logE("Could not delete ZIP file"); | |
+ } | |
+ | |
+ // Check if theme zip included a fonts.xml. If not, get from existing file in /system | |
+ File srcConfig = new File("/system/etc/fonts.xml"); | |
+ File dstConfig = new File(cacheDir, "fonts.xml"); | |
+ if (!dstConfig.exists()) { | |
+ FileUtils.copyFile(srcConfig, dstConfig); | |
+ } | |
+ | |
+ // Prepare system theme fonts folder and copy new fonts folder from our cache | |
+ FileUtils.deleteContentsAndDir(SYSTEM_THEME_FONT_DIR); | |
+ makeDir(SYSTEM_THEME_FONT_DIR); | |
+ copyDir(cacheDir.getAbsolutePath(), SYSTEM_THEME_FONT_DIR.getAbsolutePath()); | |
+ | |
+ // Let system know it's time for a font change | |
+ FileUtils.deleteContentsAndDir(cacheDir); | |
+ refreshFonts(); | |
+ } | |
+ | |
+ private void clearFonts() { | |
+ FileUtils.deleteContentsAndDir(SYSTEM_THEME_FONT_DIR); | |
+ refreshFonts(); | |
+ } | |
+ | |
+ private void refreshFonts() { | |
+ // Set permissions on font files and config xml | |
+ if (SYSTEM_THEME_FONT_DIR.exists()) { | |
+ // Set permissions | |
+ setPermissionsRecursive(SYSTEM_THEME_FONT_DIR, | |
+ FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IRWXO, | |
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH); | |
+ restoreconThemeDir(); | |
+ } | |
+ | |
+ // Let system know it's time for a font change | |
+ SystemProperties.set("sys.refresh_font", "true"); | |
+ float fontSize = Settings.System.getFloatForUser(mContext.getContentResolver(), | |
+ Settings.System.FONT_SCALE, 1.0f, UserHandle.USER_CURRENT); | |
+ Settings.System.putFloatForUser(mContext.getContentResolver(), | |
+ Settings.System.FONT_SCALE, (fontSize + 0.0000001f), UserHandle.USER_CURRENT); | |
+ restartUi(); | |
+ } | |
+ | |
+ private void applyThemedSounds(String pid, String zipFileName) { | |
+ // Prepare local cache dir for font package assembly | |
+ log("CopySounds - Package ID = \'" + pid + "\'"); | |
+ log("CopySounds - File name = \'" + zipFileName + "\'"); | |
+ | |
+ File cacheDir = new File(SYSTEM_THEME_CACHE_DIR, "SoundsCache"); | |
+ if (cacheDir.exists()) { | |
+ FileUtils.deleteContentsAndDir(cacheDir); | |
+ } | |
+ | |
+ boolean created = cacheDir.mkdirs(); | |
+ if (!created) { | |
+ logE("Could not create cache directory"); | |
+ } | |
+ | |
+ // 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(); | |
+ File soundsZip = new File(cacheDir, zipFileName); | |
+ try (InputStream inputStream = am.open("audio/" + zipFileName)) { | |
+ FileUtils.copyToFile(inputStream, soundsZip); | |
+ } catch (IOException e) { | |
+ logE("There is an exception when trying to copy themed sounds", e); | |
+ } | |
+ | |
+ // Unzip new sounds and delete zip file | |
+ unzip(soundsZip.getAbsolutePath(), cacheDir.getAbsolutePath()); | |
+ | |
+ boolean deleted = soundsZip.delete(); | |
+ if (!deleted) { | |
+ logE("Could not delete ZIP file"); | |
+ } | |
+ | |
+ clearSounds(); | |
+ makeDir(SYSTEM_THEME_AUDIO_DIR); | |
+ | |
+ for (Sound sound : SOUNDS) { | |
+ File soundsCache = new File(SYSTEM_THEME_CACHE_DIR, sound.cachePath); | |
+ | |
+ if (!(soundsCache.exists() && soundsCache.isDirectory())) { | |
+ continue; | |
+ } | |
+ | |
+ makeDir(new File(sound.themePath)); | |
+ | |
+ File mp3 = new File(SYSTEM_THEME_CACHE_DIR, sound.cachePath + sound.soundPath + ".mp3"); | |
+ File ogg = new File(SYSTEM_THEME_CACHE_DIR, sound.cachePath + sound.soundPath + ".ogg"); | |
+ if (ogg.exists()) { | |
+ FileUtils.copyFile(ogg, | |
+ new File(sound.themePath + File.separator + sound.soundPath + ".ogg")); | |
+ } else if (mp3.exists()) { | |
+ FileUtils.copyFile(mp3, | |
+ new File(sound.themePath + File.separator + sound.soundPath + ".mp3")); | |
+ } | |
+ } | |
+ | |
+ // Let system know it's time for a sound change | |
+ FileUtils.deleteContentsAndDir(cacheDir); | |
+ refreshSounds(); | |
+ } | |
+ | |
+ private void clearSounds() { | |
+ FileUtils.deleteContentsAndDir(SYSTEM_THEME_AUDIO_DIR); | |
+ refreshSounds(); | |
+ } | |
+ | |
+ private void refreshSounds() { | |
+ if (!SYSTEM_THEME_AUDIO_DIR.exists()) { | |
+ // reset to default sounds | |
+ SoundUtils.setDefaultAudible(mContext, RingtoneManager.TYPE_ALARM); | |
+ SoundUtils.setDefaultAudible(mContext, RingtoneManager.TYPE_NOTIFICATION); | |
+ SoundUtils.setDefaultAudible(mContext, RingtoneManager.TYPE_RINGTONE); | |
+ SoundUtils.setDefaultUISounds(mContext.getContentResolver(), "lock_sound", "Lock.ogg"); | |
+ SoundUtils.setDefaultUISounds(mContext.getContentResolver(), "unlock_sound", "Unlock.ogg"); | |
+ } else { | |
+ // Set permissions | |
+ setPermissionsRecursive(SYSTEM_THEME_AUDIO_DIR, | |
+ FileUtils.S_IRWXU | FileUtils.S_IRGRP | FileUtils.S_IRWXO, | |
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH); | |
+ | |
+ for (Sound sound : SOUNDS) { | |
+ File themePath = new File(sound.themePath); | |
+ | |
+ if (!(themePath.exists() && themePath.isDirectory())) { | |
+ continue; | |
+ } | |
+ | |
+ String metadataName; | |
+ switch (sound.type) { | |
+ case RingtoneManager.TYPE_RINGTONE: | |
+ metadataName = "Theme Ringtone"; | |
+ break; | |
+ case RingtoneManager.TYPE_NOTIFICATION: | |
+ metadataName = "Theme Notification"; | |
+ break; | |
+ case RingtoneManager.TYPE_ALARM: | |
+ metadataName = "Theme Alarm"; | |
+ break; | |
+ default: | |
+ metadataName = "Theme"; | |
+ } | |
+ | |
+ File mp3 = new File(themePath, sound.soundPath + ".mp3"); | |
+ File ogg = new File(themePath, sound.soundPath + ".ogg"); | |
+ | |
+ if (ogg.exists()) { | |
+ if (sound.themePath.equals(SYSTEM_THEME_UI_SOUNDS_DIR.getAbsolutePath()) | |
+ && sound.type != 0) { // Effect_Tick | |
+ SoundUtils.setUIAudible(mContext, ogg, sound.type, sound.soundName); | |
+ } else if (sound.themePath.equals(SYSTEM_THEME_UI_SOUNDS_DIR.getAbsolutePath())) { | |
+ SoundUtils.setUISounds(mContext.getContentResolver(), sound.soundName, ogg | |
+ .getAbsolutePath()); | |
+ } else { | |
+ SoundUtils.setAudible(mContext, ogg, sound.type, metadataName); | |
+ } | |
+ } else if (mp3.exists()) { | |
+ if (sound.themePath.equals(SYSTEM_THEME_UI_SOUNDS_DIR.getAbsolutePath()) | |
+ && sound.type != 0) { // Effect_Tick | |
+ SoundUtils.setUIAudible(mContext, mp3, sound.type, sound.soundName); | |
+ } else if (sound.themePath.equals(SYSTEM_THEME_UI_SOUNDS_DIR.getAbsolutePath())) { | |
+ SoundUtils.setUISounds(mContext.getContentResolver(), sound.soundName, | |
+ mp3.getAbsolutePath()); | |
+ } else { | |
+ SoundUtils.setAudible(mContext, mp3, sound.type, metadataName); | |
+ } | |
+ } else { | |
+ if (sound.themePath.equals(SYSTEM_THEME_UI_SOUNDS_DIR.getAbsolutePath())) { | |
+ SoundUtils.setDefaultUISounds(mContext.getContentResolver(), | |
+ sound.soundName, sound.soundPath + ".ogg"); | |
+ } else { | |
+ SoundUtils.setDefaultAudible(mContext, sound.type); | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ // Refresh sounds | |
+ Intent i = new Intent("com.android.systemui.action.REFRESH_SOUND"); | |
+ i.setPackage("com.android.systemui"); | |
+ mContext.sendBroadcastAsUser(i, UserHandle.SYSTEM); | |
+ | |
+ final boolean soundEffectEnabled = Settings.System.getInt(mContext.getContentResolver(), | |
+ Settings.System.SOUND_EFFECTS_ENABLED, 1) == 1; | |
+ if (soundEffectEnabled) { | |
+ AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); | |
+ am.unloadSoundEffects(); | |
+ am.loadSoundEffects(); | |
+ } | |
+ } | |
+ | |
+ private 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 (IOException e) { | |
+ logE("There is an exception when trying to unzip", e); | |
+ } | |
+ } | |
+ | |
+ private boolean makeDir(File dir) { | |
+ | |
+ if (dir.exists()) { | |
+ return dir.isDirectory(); | |
+ } | |
+ | |
+ if (dir.mkdirs()) { | |
+ int permissions = FileUtils.S_IRWXU | FileUtils.S_IRWXG | | |
+ FileUtils.S_IRWXO; | |
+ SELinux.restorecon(dir); | |
+ return FileUtils.setPermissions(dir, permissions, -1, -1) == 0; | |
+ } | |
+ | |
+ return false; | |
+ } | |
+ | |
+ private boolean copyDir(String src, String dst) { | |
+ File[] files = new File(src).listFiles(); | |
+ boolean success = true; | |
+ | |
+ if (files != null) { | |
+ for (File file : files) { | |
+ File newFile = new File(dst + File.separator + | |
+ file.getName()); | |
+ if (file.isDirectory()) { | |
+ success &= copyDir(file.getAbsolutePath(), | |
+ newFile.getAbsolutePath()); | |
+ } else { | |
+ success &= FileUtils.copyFile(file, newFile); | |
+ } | |
+ } | |
+ } else { | |
+ // not a directory | |
+ success = false; | |
+ } | |
+ return success; | |
+ } | |
+ | |
+ void setPermissions(File path, int permissions) { | |
+ FileUtils.setPermissions(path, permissions, -1, -1); | |
+ } | |
+ | |
+ void setPermissionsRecursive(File dir, int file, int folder) { | |
+ if (!dir.isDirectory()) { | |
+ setPermissions(dir, file); | |
+ return; | |
+ } | |
+ | |
+ for (File child : dir.listFiles()) { | |
+ if (child.isDirectory()) { | |
+ setPermissionsRecursive(child, file, folder); | |
+ setPermissions(child, folder); | |
+ } else { | |
+ setPermissions(child, file); | |
+ } | |
+ } | |
+ | |
+ setPermissions(dir, folder); | |
+ } | |
+ | |
+ private boolean restoreconThemeDir() { | |
+ return SELinux.restoreconRecursive(SYSTEM_THEME_DIR); | |
+ } | |
+ | |
+ private void log(String msg) { | |
+ if (DEBUG) { | |
+ Log.e(TAG, msg); | |
+ } | |
+ } | |
+ | |
+ private void logE(String msg, Throwable tr) { | |
+ if (tr != null) { | |
+ Log.e(TAG, msg, tr); | |
+ } else { | |
+ Log.e(TAG, msg); | |
+ } | |
+ } | |
+ | |
+ private void logE(String msg) { | |
+ logE(msg, null); | |
+ } | |
+ | |
+ private void updateSettings() { | |
+ synchronized (mLock) { | |
+ mSigOverride = Settings.Secure.getIntForUser(mContext.getContentResolver(), | |
+ Settings.Secure.FORCE_AUTHORIZE_SUBSTRATUM_PACKAGES, 1, | |
+ UserHandle.USER_CURRENT) == 1; | |
+ } | |
+ } | |
+ | |
+ private static class Sound { | |
+ String themePath; | |
+ String cachePath; | |
+ String soundName; | |
+ String soundPath; | |
+ int type; | |
+ | |
+ Sound(String themePath, String cachePath, String soundName, String soundPath) { | |
+ this.themePath = themePath; | |
+ this.cachePath = cachePath; | |
+ this.soundName = soundName; | |
+ this.soundPath = soundPath; | |
+ } | |
+ | |
+ Sound(String themePath, String cachePath, String soundName, String soundPath, int type) { | |
+ this.themePath = themePath; | |
+ this.cachePath = cachePath; | |
+ this.soundName = soundName; | |
+ this.soundPath = soundPath; | |
+ this.type = type; | |
+ } | |
+ } | |
+ | |
+ private class SettingsObserver extends ContentObserver { | |
+ public SettingsObserver() { | |
+ super(new Handler()); | |
+ } | |
+ | |
+ @Override | |
+ public void onChange(boolean selfChange, Uri uri) { | |
+ updateSettings(); | |
+ } | |
+ }; | |
+ | |
+ private class PackageInstallObserver extends IPackageInstallObserver2.Stub { | |
+ @Override | |
+ public void onUserActionRequired(Intent intent) throws RemoteException { | |
+ log("Installer - user action required callback"); | |
+ mIsWaiting = false; | |
+ } | |
+ | |
+ @Override | |
+ public void onPackageInstalled(String packageName, int returnCode, | |
+ String msg, Bundle extras) { | |
+ log("Installer - successfully installed \'" + packageName + "\'!"); | |
+ mInstalledPackageName = packageName; | |
+ mIsWaiting = false; | |
+ } | |
+ } | |
+ | |
+ private class PackageDeleteObserver extends IPackageDeleteObserver.Stub { | |
+ @Override | |
+ public void packageDeleted(String packageName, int returnCode) { | |
+ log("Remover - successfully removed \'" + packageName + "\'"); | |
+ mIsWaiting = false; | |
+ } | |
+ } | |
+} | |
+ | |
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java | |
index bbbb9a73383..4436658205c 100644 | |
--- a/services/core/java/com/android/server/wm/WindowManagerService.java | |
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java | |
@@ -539,6 +539,7 @@ public class WindowManagerService extends IWindowManager.Stub | |
boolean mDisplayReady; | |
boolean mSafeMode; | |
+ boolean mDisableOverlays; | |
boolean mDisplayEnabled = false; | |
boolean mSystemBooted = false; | |
boolean mForceDisplayEnabled = false; | |
@@ -4548,6 +4549,27 @@ public class WindowManagerService extends IWindowManager.Stub | |
return mSafeMode; | |
} | |
+ public boolean detectDisableOverlays() { | |
+ if (!mInputMonitor.waitForInputDevicesReady( | |
+ INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) { | |
+ Slog.w(TAG_WM, "Devices still not ready after waiting " | |
+ + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS | |
+ + " milliseconds before attempting to detect safe mode."); | |
+ } | |
+ | |
+ int volumeUpState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, | |
+ KeyEvent.KEYCODE_VOLUME_UP); | |
+ mDisableOverlays = volumeUpState > 0; | |
+ | |
+ if (mDisableOverlays) { | |
+ Log.i(TAG_WM, "All enabled theme overlays will now be disabled."); | |
+ } else { | |
+ Log.i(TAG_WM, "System will boot with enabled overlays intact."); | |
+ } | |
+ | |
+ return mDisableOverlays; | |
+ } | |
+ | |
public void displayReady() { | |
final int displayCount = mRoot.mChildren.size(); | |
for (int i = 0; i < displayCount; ++i) { | |
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java | |
index 09c8fd5cad5..e902c23e255 100644 | |
--- a/services/java/com/android/server/SystemServer.java | |
+++ b/services/java/com/android/server/SystemServer.java | |
@@ -119,6 +119,7 @@ import com.android.server.soundtrigger.SoundTriggerService; | |
import com.android.server.stats.StatsCompanionService; | |
import com.android.server.statusbar.StatusBarManagerService; | |
import com.android.server.storage.DeviceStorageMonitorService; | |
+import com.android.server.substratum.SubstratumService; | |
import com.android.server.telecom.TelecomLoaderService; | |
import com.android.server.textclassifier.TextClassificationManagerService; | |
import com.android.server.trust.TrustManagerService; | |
@@ -709,6 +710,12 @@ public final class SystemServer { | |
mSystemServiceManager.startService(overlayManagerService); | |
traceEnd(); | |
+ // Substratum system server implementation | |
+ traceBeginAndSlog("StartSubstratumService"); | |
+ mSystemServiceManager.startService(new SubstratumService(mSystemContext)); | |
+ | |
+ traceEnd(); | |
+ | |
if (SystemProperties.getInt("persist.sys.displayinset.top", 0) > 0) { | |
// DisplayManager needs the overlay immediately. | |
overlayManagerService.updateSystemUiContext(); | |
@@ -1765,6 +1772,12 @@ public final class SystemServer { | |
mActivityManagerService.showSafeModeOverlay(); | |
} | |
+ // Let's check whether we should disable all theme overlays | |
+ final boolean disableOverlays = wm.detectDisableOverlays(); | |
+ if (disableOverlays) { | |
+ mActivityManagerService.disableOverlays(); | |
+ } | |
+ | |
// Update the configuration for this context by hand, because we're going | |
// to start using it before the config change done in wm.systemReady() will | |
// propagate to it. | |
-- | |
2.17.1 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment