Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save simrat39/7ffc1a9f511aa3a023c0851a75f57ea5 to your computer and use it in GitHub Desktop.
Save simrat39/7ffc1a9f511aa3a023c0851a75f57ea5 to your computer and use it in GitHub Desktop.
From f5c234b1278cfb477cfbca4438d3dc13e571c5fc Mon Sep 17 00:00:00 2001
From: Nicholas Chum <nicholaschum@gmail.com>
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
[harsh@prjkt.io: Rewrite for Pie]
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
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 <ivan@prjkt.io>
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
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 <harsh@prjkt.io>
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 <ivan@prjkt.io>
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
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 <harsh@prjkt.io>
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 <harsh@prjkt.io>
base: Introduce SubstratumService
Change-Id: I8c9f331ca7e4c68c35ea13bed587775ee4abeb30
Signed-off-by: Ivan Iskandar <ivan@prjkt.io>
Signed-off-by: Nicholas Chum <nicholas@prjkt.io>
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
SubstratumService: allow CHANGE_OVERLAY_PACKAGES permission
Change-Id: I226a42c711164139578825f573d7beb88fdfa8a2
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
SubstratumService: unify permissions
Change-Id: Ib4a16b1321d9e797ddc71401a80b5433c64cbfc2
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
base: modify/define custom overlay management
Change-Id: I37376416fdd847e354075d31ee3ecd1f83df0cc7
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
SubstratumService: Refactor & cleanup according to AOSP conventions
Change-Id: I588ae0c6ea89d05c824b94c7560d1f8a69423b48
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
Pie OMS: allow non-system overlays from Substratum
Change-Id: I6132f396c60f8020a650ebe37850cb662192438e
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
Unbreak public api by hiding substratum interfaces
Change-Id: I5857ad50124267e99c4d7c9c790648f76a9bef66
Signed-off-by: Alex Naidis <alex.naidis@paranoidandroid.co>
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
SubstratumService: Rewrite installation method for Pie InstallSession API
Change-Id: I378da3e891e02933cff24ec289b73b06de840942
Signed-off-by: Harsh Shandilya <harsh@prjkt.io>
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 <harsh@prjkt.io>
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 <harsh@prjkt.io>
---
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