From 25702d374b2c4bb1c325e86693035686ef2b9150 Mon Sep 17 00:00:00 2001 From: Diab Neiroukh Date: Wed, 30 Jun 2021 09:57:06 +0100 Subject: [PATCH 001/332] Add a config to state whether a device supports increased touch sensitivity. (#2) This is the partner commit to the addition of an option in Settings for the same feature. This config can be enabled by an overlay for devices that support increased touch sensitivity (otherwise known as "Glove Mode") via the persist.vendor.touch_sensitivity_mode system property. --- core/res/res/values/config.xml | 3 +++ core/res/res/values/symbols.xml | 1 + 2 files changed, 4 insertions(+) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5c12c39b87764..da64aad4212ae 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4218,6 +4218,9 @@ sleep. --> false + + false + - - - - - true - - diff --git a/core/res/res/values-mcc450/config.xml b/core/res/res/values-mcc450/config.xml deleted file mode 100644 index 2a2bd76020723..0000000000000 --- a/core/res/res/values-mcc450/config.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - true - - From 7227d36e29aa0d383c1e66bed8faf8d8ce90a72c Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Mon, 7 Mar 2022 22:07:14 +0000 Subject: [PATCH 003/332] colors: Switch to GrapheneOS blue color palette for Material You Generated with Android 12 Extensions v9.0.0-test2 [1] using #1565C0 (light link accent color from GrapheneOS website) as a seed color, with all other settings left at themelib [2] and colorkt [3] defaults. [1] https://github.com/kdrag0n/android12-extensions/ [2] https://github.com/ProtonAOSP/android_external_themelib [3] https://github.com/kdrag0n/colorkt --- core/res/res/values/colors.xml | 250 ++++++++++++++++----------------- 1 file changed, 125 insertions(+), 125 deletions(-) diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index bf1423bda7e41..d47face1c92c4 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -238,135 +238,135 @@ #F9AB00 - - #FFFFFF - - #FEFBFF - - #EEF0FF - - #D9E2FF - - #B0C6FF - - #94AAE4 - - #7A90C8 - - #6076AC - - #475D92 - - #2F4578 - - #152E60 - - #001945 - + + #ffffff + + #fafcff + + #e5f1ff + + #cbe2ff + + #95c4ff + + #5ba2ff + + #3884e7 + + #1b69c5 + + #0053a3 + + #003f7b + + #002b55 + + #001933 + #000000 - - #FFFFFF - - #FEFBFF - - #EEF0FF - - #DCE2F9 - - #C0C6DC - - #A4ABC1 - - #8A90A5 - - #70778B - - #575E71 - - #404659 - - #2A3042 - - #151B2C - + + #ffffff + + #fafcff + + #e5f1ff + + #cee2fb + + #b4c6dd + + #9aabc0 + + #8090a4 + + #677688 + + #505e6f + + #394656 + + #23303f + + #0d1b29 + #000000 - - #FFFFFF - - #FFFBFF - - #FFEBFA - - #FDD7FA - - #E0BBDD - - #C3A0C1 - - #A886A6 - - #8C6D8C - - #725572 - - #593D59 - - #412742 - - #2A122C - + + #ffffff + + #fffaff + + #fce8ff + + #f8d0ff + + #eba3f8 + + #ce8ad9 + + #b071bb + + #93589d + + #794183 + + #5f2968 + + #470e4f + + #2d0033 + #000000 - - #FFFFFF - - #FEFBFF - - #F1F0F7 - - #E2E2E9 - - #C6C6CD - - #ABABB1 - - #909097 - - #76777D - - #5D5E64 - - #45464C - - #2F3036 - - #1A1B20 - + + #ffffff + + #fafcff + + #ebf1f7 + + #dde2e9 + + #c2c6cc + + #a7abb1 + + #8c9196 + + #72767b + + #5b5e63 + + #43474b + + #2d3034 + + #181c1f + #000000 - - #FFFFFF - - #FEFBFF - - #F0F0FA - - #E1E2EC - - #C5C6D0 - - #A9ABB4 - - #8F9099 - - #757780 - - #5C5E67 - - #44464F - - #2E3038 - - #191B23 - + + #ffffff + + #fafcff + + #e6f1fd + + #d9e2ef + + #bdc6d2 + + #a2abb6 + + #88919a + + #6e767f + + #575e67 + + #40474f + + #2a3038 + + #151b23 + #000000 #FFFFFF From 18800fae4f312966ca98aa09763c90450d097fa9 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Fri, 18 Mar 2022 14:42:08 +0200 Subject: [PATCH 004/332] improve PendingIntent security check compatibility This switches to secure-by-default instead of crash-by-default for API 31 to work around apps which have updated to API 31 without specifying either FLAG_MUTABLE or FLAG_IMMUTABLE for PendingIntents. If the app ends up needing the FLAG_MUTABLE behavior, it may crash later, but it should still be obvious why it happened. There are many apps with outdated Play services client libraries lacking support for Android 12 which are nonetheless targeting API 31 or higher and will crash in certain situations. Google Play services will ask the client library to request runtime permissions from the user on behalf of it when it thinks that they're required for an operation that's requested. The older client libraries will cause a crash in the app by trying to create a PendingIntent with no FLAG_MUTABLE or FLAG_IMMUTABLE specified. This is a much more common issue on GrapheneOS since Play services is a regular user installed app with no special access or privileges, and starts without any standard runtime permissions granted to it. Co-authored-by: Dmitry Muhomor --- core/java/android/app/PendingIntent.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 393ec8c1d66d3..67dea069633d9 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -436,8 +436,8 @@ static void removeOnMarshaledListener(OnMarshaledListener listener) { sOnMarshaledListener.get().remove(listener); } - private static void checkPendingIntent(int flags, @NonNull Intent intent, - @NonNull Context context, boolean isActivityResultType) { + private static int checkPendingIntent2(int flags, @NonNull Intent intent, + @NonNull Context context, boolean isActivityResultType) { final boolean isFlagImmutableSet = (flags & PendingIntent.FLAG_IMMUTABLE) != 0; final boolean isFlagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0; final String packageName = context.getPackageName(); @@ -455,7 +455,9 @@ private static void checkPendingIntent(int flags, @NonNull Intent intent, + " using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality" + " depends on the PendingIntent being mutable, e.g. if it needs to" + " be used with inline replies or bubbles."; - throw new IllegalArgumentException(msg); + + Log.e(TAG, msg); + return flags | PendingIntent.FLAG_IMMUTABLE; } // For apps with target SDK < U, warn that creation or retrieval of a mutable implicit @@ -472,6 +474,7 @@ private static void checkPendingIntent(int flags, @NonNull Intent intent, + " for security reasons."; Log.w(TAG, new StackTrace(msg)); } + return flags; } /** @hide */ @@ -571,7 +574,7 @@ public static PendingIntent getActivityAsUser(Context context, int requestCode, @NonNull Intent intent, int flags, Bundle options, UserHandle user) { String packageName = context.getPackageName(); String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver()); - checkPendingIntent(flags, intent, context, /* isActivityResultType */ false); + flags = checkPendingIntent2(flags, intent, context, /* isActivityResultType */ false); try { intent.migrateExtraStreamToClipData(context); intent.prepareToLeaveProcess(context); @@ -705,7 +708,7 @@ public static PendingIntent getActivitiesAsUser(Context context, int requestCode intents[i].migrateExtraStreamToClipData(context); intents[i].prepareToLeaveProcess(context); resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); - checkPendingIntent(flags, intents[i], context, /* isActivityResultType */ false); + flags = checkPendingIntent2(flags, intents[i], context, /* isActivityResultType */ false); } try { IIntentSender target = @@ -758,7 +761,7 @@ public static PendingIntent getBroadcastAsUser(Context context, int requestCode, Intent intent, int flags, UserHandle userHandle) { String packageName = context.getPackageName(); String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver()); - checkPendingIntent(flags, intent, context, /* isActivityResultType */ false); + flags = checkPendingIntent2(flags, intent, context, /* isActivityResultType */ false); try { intent.prepareToLeaveProcess(context); IIntentSender target = @@ -837,7 +840,7 @@ private static PendingIntent buildServicePendingIntent(Context context, int requ Intent intent, int flags, int serviceKind) { String packageName = context.getPackageName(); String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver()); - checkPendingIntent(flags, intent, context, /* isActivityResultType */ false); + flags = checkPendingIntent2(flags, intent, context, /* isActivityResultType */ false); try { intent.prepareToLeaveProcess(context); IIntentSender target = From 3c8d680dbabef2a9a808cf18af3ce8346bc5cb6f Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Mon, 5 Oct 2020 21:42:48 -0700 Subject: [PATCH 005/332] Show USB icon in notification instead of generic system icon It doesn't make sense to show a generic Android letter version icon for USB. Change-Id: I0441fc76fa8beab16675ac91e92e9b0490044dec --- services/usb/java/com/android/server/usb/UsbDeviceManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index c65f7844f2011..ce1e73428768b 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -1667,7 +1667,7 @@ protected void updateUsbNotification(boolean force) { } Notification.Builder builder = new Notification.Builder(mContext, channel) - .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) + .setSmallIcon(com.android.internal.R.drawable.stat_sys_data_usb) .setWhen(0) .setOngoing(true) .setTicker(title) From 23c03fd720575fc89a95c9da7f24d5287c217fdc Mon Sep 17 00:00:00 2001 From: LuK1337 Date: Wed, 28 Oct 2020 23:59:20 +0100 Subject: [PATCH 006/332] Sharesheet: Display two rows of max ranked targets This change makes sharesheet way more useful by increasing the amount of visible ranked apps. Change-Id: Ic092f1d1784259c9f3c0870eda1dd1ae8544c697 --- core/java/com/android/internal/app/ChooserActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 9bc6671bbc310..5f2c554e38082 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2823,7 +2823,7 @@ public Intent getReferrerFillInIntent() { @Override // ChooserListCommunicator public int getMaxRankedTargets() { - return mMaxTargetsPerRow; + return mMaxTargetsPerRow * 2; } @Override // ChooserListCommunicator From 04615cc0e6d6f1d099984d9781d25c3a2700c061 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 12 Feb 2023 19:38:02 +0200 Subject: [PATCH 007/332] extsettings: add a set of helper classes for defining system settings --- core/api/system-current.txt | 70 +++++++ .../android/ext/settings/BoolSetting.java | 76 ++++++++ .../android/ext/settings/BoolSysProperty.java | 32 ++++ .../android/ext/settings/ExtSettings.java | 41 ++++ .../java/android/ext/settings/IntSetting.java | 111 +++++++++++ .../android/ext/settings/IntSysProperty.java | 39 ++++ core/java/android/ext/settings/Setting.java | 179 ++++++++++++++++++ .../android/ext/settings/StringSetting.java | 72 +++++++ .../ext/settings/StringSysProperty.java | 32 ++++ core/java/android/provider/Settings.java | 18 ++ core/res/res/values/config_ext.xml | 3 + 11 files changed, 673 insertions(+) create mode 100644 core/java/android/ext/settings/BoolSetting.java create mode 100644 core/java/android/ext/settings/BoolSysProperty.java create mode 100644 core/java/android/ext/settings/ExtSettings.java create mode 100644 core/java/android/ext/settings/IntSetting.java create mode 100644 core/java/android/ext/settings/IntSysProperty.java create mode 100644 core/java/android/ext/settings/Setting.java create mode 100644 core/java/android/ext/settings/StringSetting.java create mode 100644 core/java/android/ext/settings/StringSysProperty.java create mode 100644 core/res/res/values/config_ext.xml diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 31159b9541557..cf40ce1f5dec1 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4839,6 +4839,76 @@ package android.ext { } +package android.ext.settings { + + public class BoolSetting extends android.ext.settings.Setting { + ctor public BoolSetting(@NonNull android.ext.settings.Setting.Scope, @NonNull String, boolean); + ctor public BoolSetting(@NonNull android.ext.settings.Setting.Scope, @NonNull String, @NonNull java.util.function.Function); + method public final boolean get(@NonNull android.content.Context); + method public final boolean get(@NonNull android.content.Context, int); + method public final boolean put(@NonNull android.content.Context, boolean); + } + + public class BoolSysProperty extends android.ext.settings.BoolSetting { + ctor public BoolSysProperty(@NonNull String, boolean); + ctor public BoolSysProperty(@NonNull String, @NonNull java.util.function.Function); + method public boolean get(); + method public boolean put(boolean); + } + + public class IntSetting extends android.ext.settings.Setting { + ctor public IntSetting(@NonNull android.ext.settings.Setting.Scope, @NonNull String, int); + ctor public IntSetting(@NonNull android.ext.settings.Setting.Scope, @NonNull String, int, @NonNull int...); + ctor public IntSetting(@NonNull android.ext.settings.Setting.Scope, @NonNull String, @NonNull java.util.function.ToIntFunction); + ctor public IntSetting(@NonNull android.ext.settings.Setting.Scope, @NonNull String, @NonNull java.util.function.ToIntFunction, @NonNull int...); + method public final int get(@NonNull android.content.Context); + method public final int get(@NonNull android.content.Context, int); + method public final boolean put(@NonNull android.content.Context, int); + method public boolean validateValue(int); + } + + public class IntSysProperty extends android.ext.settings.IntSetting { + ctor public IntSysProperty(@NonNull String, int); + ctor public IntSysProperty(@NonNull String, int, @NonNull int...); + ctor public IntSysProperty(@NonNull String, @NonNull java.util.function.ToIntFunction); + ctor public IntSysProperty(@NonNull String, @NonNull java.util.function.ToIntFunction, @NonNull int...); + method public int get(); + method public boolean put(int); + } + + public abstract class Setting { + method public final boolean canObserveState(); + method @NonNull public final String getKey(); + method @NonNull public final android.ext.settings.Setting.Scope getScope(); + method @NonNull public final Object registerObserver(@NonNull android.content.Context, @NonNull android.os.Handler, @NonNull java.util.function.Consumer); + method @NonNull public final Object registerObserver(@NonNull android.content.Context, int, @NonNull android.os.Handler, @NonNull java.util.function.Consumer); + method public final void unregisterObserver(@NonNull android.content.Context, @NonNull Object); + } + + public enum Setting.Scope { + enum_constant public static final android.ext.settings.Setting.Scope GLOBAL; + enum_constant public static final android.ext.settings.Setting.Scope PER_USER; + enum_constant public static final android.ext.settings.Setting.Scope SYSTEM_PROPERTY; + } + + public class StringSetting extends android.ext.settings.Setting { + ctor public StringSetting(@NonNull android.ext.settings.Setting.Scope, @NonNull String, @NonNull String); + ctor public StringSetting(@NonNull android.ext.settings.Setting.Scope, @NonNull String, @NonNull java.util.function.Function); + method @NonNull public final String get(@NonNull android.content.Context); + method @NonNull public final String get(@NonNull android.content.Context, int); + method public final boolean put(@NonNull android.content.Context, @NonNull String); + method public boolean validateValue(@NonNull String); + } + + public class StringSysProperty extends android.ext.settings.StringSetting { + ctor public StringSysProperty(@NonNull String, @NonNull String); + ctor public StringSysProperty(@NonNull String, @NonNull java.util.function.Function); + method @NonNull public String get(); + method public boolean put(@NonNull String); + } + +} + package android.graphics.fonts { public final class FontFamilyUpdateRequest { diff --git a/core/java/android/ext/settings/BoolSetting.java b/core/java/android/ext/settings/BoolSetting.java new file mode 100644 index 0000000000000..fc6f808a7e73c --- /dev/null +++ b/core/java/android/ext/settings/BoolSetting.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 GrapheneOS + * SPDX-License-Identifier: Apache-2.0 + */ + +package android.ext.settings; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Context; + +import java.util.function.Function; + +/** @hide */ +@SystemApi +public class BoolSetting extends Setting { + private boolean defaultValue; + private volatile Function defaultValueSupplier; + + public BoolSetting(@NonNull Scope scope, @NonNull String key, boolean defaultValue) { + super(scope, key); + this.defaultValue = defaultValue; + } + + public BoolSetting(@NonNull Scope scope, @NonNull String key, @NonNull Function defaultValue) { + super(scope, key); + defaultValueSupplier = defaultValue; + } + + public final boolean get(@NonNull Context ctx) { + return get(ctx, ctx.getUserId()); + } + + // use only if this is a per-user setting and the context is not a per-user one + public final boolean get(@NonNull Context ctx, int userId) { + String valueStr = getRaw(ctx, userId); + + if (valueStr == null) { + return getDefaultValue(ctx); + } + + if (valueStr.equals("true")) { + return true; + } + + if (valueStr.equals("false")) { + return false; + } + + try { + int valueInt = Integer.parseInt(valueStr); + if (valueInt == 1) { + return true; + } else if (valueInt == 0) { + return false; + } + } catch (NumberFormatException e) { + e.printStackTrace(); + } + + return getDefaultValue(ctx); + } + + public final boolean put(@NonNull Context ctx, boolean val) { + return putRaw(ctx, val ? "1" : "0"); + } + + private boolean getDefaultValue(Context ctx) { + Function supplier = defaultValueSupplier; + if (supplier != null) { + defaultValue = supplier.apply(ctx).booleanValue(); + defaultValueSupplier = null; + } + return defaultValue; + } +} diff --git a/core/java/android/ext/settings/BoolSysProperty.java b/core/java/android/ext/settings/BoolSysProperty.java new file mode 100644 index 0000000000000..4ea251cb1ffb3 --- /dev/null +++ b/core/java/android/ext/settings/BoolSysProperty.java @@ -0,0 +1,32 @@ +package android.ext.settings; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.UserHandle; + +import java.util.function.BooleanSupplier; +import java.util.function.Function; + +/** @hide */ +@SystemApi +public class BoolSysProperty extends BoolSetting { + + public BoolSysProperty(@NonNull String key, boolean defaultValue) { + super(Scope.SYSTEM_PROPERTY, key, defaultValue); + } + + public BoolSysProperty(@NonNull String key, @NonNull Function defaultValue) { + super(Scope.SYSTEM_PROPERTY, key, defaultValue); + } + + public boolean get() { + //noinspection DataFlowIssue + return super.get(null, UserHandle.USER_SYSTEM); + } + + public boolean put(boolean val) { + //noinspection DataFlowIssue + return super.put(null, val); + } +} diff --git a/core/java/android/ext/settings/ExtSettings.java b/core/java/android/ext/settings/ExtSettings.java new file mode 100644 index 0000000000000..c22ddc7883fc1 --- /dev/null +++ b/core/java/android/ext/settings/ExtSettings.java @@ -0,0 +1,41 @@ +package android.ext.settings; + +import android.annotation.BoolRes; +import android.annotation.IntegerRes; +import android.annotation.StringRes; +import android.content.Context; +import android.content.res.Resources; +import android.provider.Settings; + +import com.android.internal.R; + +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.IntSupplier; +import java.util.function.Supplier; +import java.util.function.ToIntFunction; + +/** + * Note that android.provider.Settings setting names should be defined in the corresponding classes, + * since the readability of settings is determined by using Java reflection on members of that class. + * + * @see android.provider.Settings#getPublicSettingsForClass + * @hide + */ +public class ExtSettings { + + private ExtSettings() {} + + public static Function defaultBool(@BoolRes int res) { + return ctx -> ctx.getResources().getBoolean(res); + } + + public static ToIntFunction defaultInt(@IntegerRes int res) { + return ctx -> ctx.getResources().getInteger(res); + } + + public static Function defaultString(@StringRes int res) { + return ctx -> ctx.getString(res); + } +} diff --git a/core/java/android/ext/settings/IntSetting.java b/core/java/android/ext/settings/IntSetting.java new file mode 100644 index 0000000000000..bffc35ae75cc3 --- /dev/null +++ b/core/java/android/ext/settings/IntSetting.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 GrapheneOS + * SPDX-License-Identifier: Apache-2.0 + */ + +package android.ext.settings; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; + +import java.util.function.ToIntFunction; + +/** @hide */ +@SystemApi +public class IntSetting extends Setting { + private int defaultValue; + private volatile ToIntFunction defaultValueSupplier; + + @Nullable private final int[] validValues; + + private IntSetting(@NonNull Scope scope, @NonNull String key, @Nullable int[] validValues) { + super(scope, key); + this.validValues = validValues; + } + + public IntSetting(@NonNull Scope scope, @NonNull String key, int defaultValue) { + this(scope, key, (int[]) null); + setDefaultValue(defaultValue); + } + + public IntSetting(@NonNull Scope scope, @NonNull String key, int defaultValue, @NonNull int... validValues) { + this(scope, key, validValues); + setDefaultValue(defaultValue); + } + + public IntSetting(@NonNull Scope scope, @NonNull String key, @NonNull ToIntFunction defaultValue) { + this(scope, key, (int[]) null); + defaultValueSupplier = defaultValue; + } + + public IntSetting(@NonNull Scope scope, @NonNull String key, @NonNull ToIntFunction defaultValue, + @NonNull int... validValues) { + this(scope, key, validValues); + defaultValueSupplier = defaultValue; + } + + public boolean validateValue(int val) { + if (validValues == null) { + return true; + } + // don't do sort() + bsearch() of validValues array, it's expected to have a small number of entries + for (int validValue : validValues) { + if (val == validValue) { + return true; + } + } + return false; + } + + public final int get(@NonNull Context ctx) { + return get(ctx, ctx.getUserId()); + } + + // use only if this is a per-user setting and the context is not a per-user one + public final int get(@NonNull Context ctx, int userId) { + String valueStr = getRaw(ctx, userId); + + if (valueStr == null) { + return getDefaultValue(ctx); + } + + int value; + try { + value = Integer.parseInt(valueStr); + } catch (NumberFormatException e) { + e.printStackTrace(); + return getDefaultValue(ctx); + } + + if (!validateValue(value)) { + return getDefaultValue(ctx); + } + + return value; + } + + public final boolean put(@NonNull Context ctx, int val) { + if (!validateValue(val)) { + throw new IllegalArgumentException(Integer.toString(val)); + } + return putRaw(ctx, Integer.toString(val)); + } + + private void setDefaultValue(int val) { + if (!validateValue(val)) { + throw new IllegalStateException("invalid default value " + val); + } + defaultValue = val; + } + + private int getDefaultValue(Context ctx) { + ToIntFunction supplier = defaultValueSupplier; + if (supplier != null) { + setDefaultValue(supplier.applyAsInt(ctx)); + defaultValueSupplier = null; + } + return defaultValue; + } +} diff --git a/core/java/android/ext/settings/IntSysProperty.java b/core/java/android/ext/settings/IntSysProperty.java new file mode 100644 index 0000000000000..c8842ebb54557 --- /dev/null +++ b/core/java/android/ext/settings/IntSysProperty.java @@ -0,0 +1,39 @@ +package android.ext.settings; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.UserHandle; + +import java.util.function.ToIntFunction; + +/** @hide */ +@SystemApi +public class IntSysProperty extends IntSetting { + + public IntSysProperty(@NonNull String key, int defaultValue) { + super(Scope.SYSTEM_PROPERTY, key, defaultValue); + } + + public IntSysProperty(@NonNull String key, int defaultValue, @NonNull int... validValues) { + super(Scope.SYSTEM_PROPERTY, key, defaultValue, validValues); + } + + public IntSysProperty(@NonNull String key, @NonNull ToIntFunction defaultValue) { + super(Scope.SYSTEM_PROPERTY, key, defaultValue); + } + + public IntSysProperty(@NonNull String key, @NonNull ToIntFunction defaultValue, @NonNull int... validValues) { + super(Scope.SYSTEM_PROPERTY, key, defaultValue, validValues); + } + + public int get() { + //noinspection DataFlowIssue + return super.get(null, UserHandle.USER_SYSTEM); + } + + public boolean put(int val) { + //noinspection DataFlowIssue + return super.put(null, val); + } +} diff --git a/core/java/android/ext/settings/Setting.java b/core/java/android/ext/settings/Setting.java new file mode 100644 index 0000000000000..236df65a6cf45 --- /dev/null +++ b/core/java/android/ext/settings/Setting.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 GrapheneOS + * SPDX-License-Identifier: Apache-2.0 + */ + +package android.ext.settings; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.SystemProperties; +import android.provider.Settings; +import android.util.Log; + +import java.util.function.Consumer; + +/** @hide */ +@SystemApi +public abstract class Setting { + + @SuppressLint("Enum") + public enum Scope { + SYSTEM_PROPERTY, // android.os.SystemProperties, doesn't support state observers + GLOBAL, // android.provider.Settings.Global + PER_USER, // android.provider.Settings.Secure + } + + private final Scope scope; + private final String key; + + /** @hide */ + protected Setting(Scope scope, String key) { + this.scope = scope; + this.key = key; + } + + @NonNull + public final String getKey() { + return key; + } + + @NonNull + public final Scope getScope() { + return scope; + } + + /** @hide */ + @Nullable + protected final String getRaw(Context ctx, int userId) { + try { + switch (scope) { + case SYSTEM_PROPERTY: { + String s = SystemProperties.get(key); + if (s.isEmpty()) { + return null; + } + return s; + } + case GLOBAL: + return Settings.Global.getString(ctx.getContentResolver(), key); + case PER_USER: + return Settings.Secure.getStringForUser(ctx.getContentResolver(), key, userId); + } + } catch (Throwable e) { + Log.e("ExtSettings", "key: " + key, e); + if (Settings.isInSystemServer()) { + // should never happen under normal circumstances, but if it does, + // don't crash the system_server + return null; + } + + throw e; + } + + // "switch (scope)" above should be exhaustive + throw new IllegalStateException(); + } + + /** @hide */ + protected final boolean putRaw(Context ctx, String val) { + switch (scope) { + case SYSTEM_PROPERTY: { + try { + SystemProperties.set(key, val); + return true; + } catch (RuntimeException e) { + e.printStackTrace(); + if (e instanceof IllegalArgumentException) { + // see doc + throw e; + } + return false; + } + } + case GLOBAL: + return Settings.Global.putString(ctx.getContentResolver(), key, val); + case PER_USER: + return Settings.Secure.putString(ctx.getContentResolver(), key, val); + default: + throw new IllegalStateException(); + } + } + + public final boolean canObserveState() { + return scope != Scope.SYSTEM_PROPERTY; + } + + // pass the return value to unregisterObserver() to remove the observer + @NonNull + public final Object registerObserver(@NonNull Context ctx, @NonNull Handler handler, @NonNull Consumer callback) { + return registerObserver(ctx, ctx.getUserId(), handler, callback); + } + + @NonNull + public final Object registerObserver(@NonNull Context ctx, int userId, @NonNull Handler handler, @NonNull Consumer callback) { + if (scope == Scope.SYSTEM_PROPERTY) { + // SystemProperties.addChangeCallback() doesn't work unless the change is actually + // reported elsewhere in the same process with SystemProperties.callChangeCallbacks() + // or with its native equivalent (report_sysprop_change()). + // Leave the code in place in case this changes in the future. + if (false) { + Runnable observer = new Runnable() { + private volatile String prev = SystemProperties.get(getKey()); + + @Override + public void run() { + String value = SystemProperties.get(getKey()); + // change callback is dispatched whenever any change to system props occurs + if (!prev.equals(value)) { + prev = value; + handler.post(() -> callback.accept((SelfType) Setting.this)); + } + } + }; + SystemProperties.addChangeCallback(observer); + return observer; + } + throw new UnsupportedOperationException("observing sysprop state is not supported"); + } + + Uri uri; + switch (scope) { + case GLOBAL: + uri = Settings.Global.getUriFor(key); + break; + case PER_USER: + uri = Settings.Secure.getUriFor(key); + break; + default: + throw new IllegalStateException(); + } + + ContentObserver observer = new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange) { + callback.accept((SelfType) Setting.this); + } + }; + ctx.getContentResolver().registerContentObserver(uri, false, observer, userId); + + return observer; + } + + public final void unregisterObserver(@NonNull Context ctx, @NonNull Object observer) { + if (scope == Scope.SYSTEM_PROPERTY) { + if (false) { // see comment in registerObserverInner + SystemProperties.removeChangeCallback((Runnable) observer); + } + throw new UnsupportedOperationException("observing sysprop state is not supported"); + } else { + ctx.getContentResolver().unregisterContentObserver((ContentObserver) observer); + } + } +} diff --git a/core/java/android/ext/settings/StringSetting.java b/core/java/android/ext/settings/StringSetting.java new file mode 100644 index 0000000000000..46fdf5a4e21a6 --- /dev/null +++ b/core/java/android/ext/settings/StringSetting.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 GrapheneOS + * SPDX-License-Identifier: Apache-2.0 + */ + +package android.ext.settings; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Context; + +import java.util.function.Function; + +/** @hide */ +@SystemApi +public class StringSetting extends Setting { + private String defaultValue; + private volatile Function defaultValueSupplier; + + public StringSetting(@NonNull Scope scope, @NonNull String key, @NonNull String defaultValue) { + super(scope, key); + setDefaultValue(defaultValue); + } + + public StringSetting(@NonNull Scope scope, @NonNull String key, @NonNull Function defaultValue) { + super(scope, key); + this.defaultValueSupplier = defaultValue; + } + + public boolean validateValue(@NonNull String val) { + return true; + } + + @NonNull + public final String get(@NonNull Context ctx) { + return get(ctx, ctx.getUserId()); + } + + @NonNull + // use only if this is a per-user setting and the context does not specify the userId + public final String get(@NonNull Context ctx, int userId) { + String s = getRaw(ctx, userId); + if (s == null || !validateValue(s)) { + return getDefaultValue(ctx); + } + return s; + } + + public final boolean put(@NonNull Context ctx, @NonNull String val) { + if (!validateValue(val)) { + throw new IllegalStateException("invalid value " + val); + } + return putRaw(ctx, val); + } + + private void setDefaultValue(@NonNull String val) { + if (!validateValue(val)) { + throw new IllegalStateException("invalid default value " + val); + } + defaultValue = val; + } + + @NonNull + private String getDefaultValue(Context ctx) { + Function supplier = defaultValueSupplier; + if (supplier != null) { + setDefaultValue(supplier.apply(ctx)); + defaultValueSupplier = null; + } + return defaultValue; + } +} diff --git a/core/java/android/ext/settings/StringSysProperty.java b/core/java/android/ext/settings/StringSysProperty.java new file mode 100644 index 0000000000000..c641426ca0c33 --- /dev/null +++ b/core/java/android/ext/settings/StringSysProperty.java @@ -0,0 +1,32 @@ +package android.ext.settings; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.UserHandle; + +import java.util.function.Function; + +/** @hide */ +@SystemApi +public class StringSysProperty extends StringSetting { + + public StringSysProperty(@NonNull String key, @NonNull String defaultValue) { + super(Scope.SYSTEM_PROPERTY, key, defaultValue); + } + + public StringSysProperty(@NonNull String key, @NonNull Function defaultValue) { + super(Scope.SYSTEM_PROPERTY, key, defaultValue); + } + + @NonNull + public String get() { + //noinspection DataFlowIssue + return super.get(null, UserHandle.USER_SYSTEM); + } + + public boolean put(@NonNull String val) { + //noinspection DataFlowIssue + return super.put(null, val); + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e7bca14194186..1297be8094dc9 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7009,6 +7009,15 @@ public static boolean canWrite(Context context) { * or by calling the "put" methods that this class contains. */ public static final class Secure extends NameValueTable { + // It's important to define setting names here, since readability of settings is determined + // by using Java reflection on members of this class. + /** @see android.provider.Settings#getPublicSettingsForClass */ + // ExtSettings BEGIN + + + + // ExtSettings END + // NOTE: If you add new settings here, be sure to add them to // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoSecureSettingsLocked. @@ -13338,6 +13347,15 @@ public static void setLocationProviderEnabled(ContentResolver cr, * explicitly modify through the system UI or specialized APIs for those values. */ public static final class Global extends NameValueTable { + // It's important to define setting names here, since readability of settings is determined + // by using Java reflection on members of this class. + /** @see android.provider.Settings#getPublicSettingsForClass */ + // ExtSettings BEGIN + + + + // ExtSettings END + // NOTE: If you add new settings here, be sure to add them to // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoGlobalSettingsLocked. diff --git a/core/res/res/values/config_ext.xml b/core/res/res/values/config_ext.xml new file mode 100644 index 0000000000000..045e125f3d8da --- /dev/null +++ b/core/res/res/values/config_ext.xml @@ -0,0 +1,3 @@ + + + From 915337af4e938873cfc976be84d9ed0d0cd1ca3a Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Sat, 14 Mar 2015 18:10:20 -0400 Subject: [PATCH 008/332] add exec-based spawning support --- .../android/ext/settings/ExtSettings.java | 3 + .../com/android/internal/os/ExecInit.java | 115 ++++++++++++++++++ .../com/android/internal/os/WrapperInit.java | 2 +- .../android/internal/os/ZygoteConnection.java | 10 +- 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 core/java/com/android/internal/os/ExecInit.java diff --git a/core/java/android/ext/settings/ExtSettings.java b/core/java/android/ext/settings/ExtSettings.java index c22ddc7883fc1..86fcb940bbd9b 100644 --- a/core/java/android/ext/settings/ExtSettings.java +++ b/core/java/android/ext/settings/ExtSettings.java @@ -25,6 +25,9 @@ */ public class ExtSettings { + public static final BoolSysProperty EXEC_SPAWNING = new BoolSysProperty( + "persist.security.exec_spawn", true); + private ExtSettings() {} public static Function defaultBool(@BoolRes int res) { diff --git a/core/java/com/android/internal/os/ExecInit.java b/core/java/com/android/internal/os/ExecInit.java new file mode 100644 index 0000000000000..2adcab7fdbe68 --- /dev/null +++ b/core/java/com/android/internal/os/ExecInit.java @@ -0,0 +1,115 @@ +package com.android.internal.os; + +import android.os.Trace; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Slog; +import android.util.TimingsTraceLog; +import dalvik.system.VMRuntime; + +/** + * Startup class for the process. + * @hide + */ +public class ExecInit { + /** + * Class not instantiable. + */ + private ExecInit() { + } + + /** + * The main function called when starting a runtime application. + * + * The first argument is the target SDK version for the app. + * + * The remaining arguments are passed to the runtime. + * + * @param args The command-line arguments. + */ + public static void main(String[] args) { + // Parse our mandatory argument. + int targetSdkVersion = Integer.parseInt(args[0], 10); + + // Mimic system Zygote preloading. + ZygoteInit.preload(new TimingsTraceLog("ExecInitTiming", + Trace.TRACE_TAG_DALVIK)); + + // Launch the application. + String[] runtimeArgs = new String[args.length - 1]; + System.arraycopy(args, 1, runtimeArgs, 0, runtimeArgs.length); + Runnable r = execInit(targetSdkVersion, runtimeArgs); + + r.run(); + } + + /** + * Executes a runtime application with exec-based spawning. + * This method never returns. + * + * @param niceName The nice name for the application, or null if none. + * @param targetSdkVersion The target SDK version for the app. + * @param args Arguments for {@link RuntimeInit#main}. + */ + public static void execApplication(String niceName, int targetSdkVersion, + String instructionSet, String[] args) { + int niceArgs = niceName == null ? 0 : 1; + int baseArgs = 5 + niceArgs; + String[] argv = new String[baseArgs + args.length]; + if (VMRuntime.is64BitInstructionSet(instructionSet)) { + argv[0] = "/system/bin/app_process64"; + } else { + argv[0] = "/system/bin/app_process32"; + } + argv[1] = "/system/bin"; + argv[2] = "--application"; + if (niceName != null) { + argv[3] = "--nice-name=" + niceName; + } + argv[3 + niceArgs] = "com.android.internal.os.ExecInit"; + argv[4 + niceArgs] = Integer.toString(targetSdkVersion); + System.arraycopy(args, 0, argv, baseArgs, args.length); + + WrapperInit.preserveCapabilities(); + try { + Os.execv(argv[0], argv); + } catch (ErrnoException e) { + throw new RuntimeException(e); + } + } + + /** + * The main function called when an application is started with exec-based spawning. + * + * When the app starts, the runtime starts {@link RuntimeInit#main} + * which calls {@link main} which then calls this method. + * So we don't need to call commonInit() here. + * + * @param targetSdkVersion target SDK version + * @param argv arg strings + */ + private static Runnable execInit(int targetSdkVersion, String[] argv) { + if (RuntimeInit.DEBUG) { + Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from exec"); + } + + // Check whether the first argument is a "-cp" in argv, and assume the next argument is the + // classpath. If found, create a PathClassLoader and use it for applicationInit. + ClassLoader classLoader = null; + if (argv != null && argv.length > 2 && argv[0].equals("-cp")) { + classLoader = ZygoteInit.createPathClassLoader(argv[1], targetSdkVersion); + + // Install this classloader as the context classloader, too. + Thread.currentThread().setContextClassLoader(classLoader); + + // Remove the classpath from the arguments. + String removedArgs[] = new String[argv.length - 2]; + System.arraycopy(argv, 2, removedArgs, 0, argv.length - 2); + argv = removedArgs; + } + + // Perform the same initialization that would happen after the Zygote forks. + Zygote.nativePreApplicationInit(); + return RuntimeInit.applicationInit(targetSdkVersion, /*disabledCompatChanges*/ null, argv, classLoader); + } +} diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java index 6860759eea8ae..a2eef62f80be2 100644 --- a/core/java/com/android/internal/os/WrapperInit.java +++ b/core/java/com/android/internal/os/WrapperInit.java @@ -186,7 +186,7 @@ private static Runnable wrapperInit(int targetSdkVersion, String[] argv) { * This is acceptable here as failure will leave the wrapped app with strictly less * capabilities, which may make it crash, but not exceed its allowances. */ - private static void preserveCapabilities() { + public static void preserveCapabilities() { StructCapUserHeader header = new StructCapUserHeader( OsConstants._LINUX_CAPABILITY_VERSION_3, 0); StructCapUserData[] data; diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 2eab0813956d4..0af071597c43c 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -25,6 +25,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ApplicationInfo; +import android.ext.settings.ExtSettings; import android.net.Credentials; import android.net.LocalSocket; import android.os.Parcel; @@ -242,7 +243,7 @@ Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) { fdsToClose[1] = zygoteFd.getInt$(); } - if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote + if (parsedArgs.mInvokeWith != null || ExtSettings.EXEC_SPAWNING.get() || parsedArgs.mStartChildZygote || !multipleOK || peer.getUid() != Process.SYSTEM_UID) { // Continue using old code for now. TODO: Handle these cases in the other path. pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, @@ -525,6 +526,13 @@ private Runnable handleChildProc(ZygoteArguments parsedArgs, throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); } else { if (!isZygote) { + if (ExtSettings.EXEC_SPAWNING.get()) { + ExecInit.execApplication(parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, + VMRuntime.getCurrentInstructionSet(), parsedArgs.mRemainingArgs); + + // Should not get here. + throw new IllegalStateException("ExecInit.execApplication unexpectedly returned"); + } return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, parsedArgs.mDisabledCompatChanges, parsedArgs.mRemainingArgs, null /* classLoader */); From 769ee78604c5572cd5d7cf5da73d12f98d8dc82e Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 21 May 2019 23:54:20 -0400 Subject: [PATCH 009/332] disable exec spawning when using debugging options The debugging options are not yet supported probably, so disable exec spawning when doing debugging. --- core/java/com/android/internal/os/ZygoteConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 0af071597c43c..55c4c279b3ff7 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -526,7 +526,8 @@ private Runnable handleChildProc(ZygoteArguments parsedArgs, throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); } else { if (!isZygote) { - if (ExtSettings.EXEC_SPAWNING.get()) { + if (ExtSettings.EXEC_SPAWNING.get() && + (parsedArgs.mRuntimeFlags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { ExecInit.execApplication(parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, VMRuntime.getCurrentInstructionSet(), parsedArgs.mRemainingArgs); From dfece2b75be9fba011bb7ffe7804c2201aaa2160 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 14 May 2019 14:24:21 -0400 Subject: [PATCH 010/332] add parameter for avoiding full preload with exec --- core/java/com/android/internal/os/ExecInit.java | 2 +- core/java/com/android/internal/os/ZygoteInit.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/java/com/android/internal/os/ExecInit.java b/core/java/com/android/internal/os/ExecInit.java index 2adcab7fdbe68..830e5b562a919 100644 --- a/core/java/com/android/internal/os/ExecInit.java +++ b/core/java/com/android/internal/os/ExecInit.java @@ -33,7 +33,7 @@ public static void main(String[] args) { // Mimic system Zygote preloading. ZygoteInit.preload(new TimingsTraceLog("ExecInitTiming", - Trace.TRACE_TAG_DALVIK)); + Trace.TRACE_TAG_DALVIK), false); // Launch the application. String[] runtimeArgs = new String[args.length - 1]; diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 38dc198fa9f8e..7e4e3796a32e3 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -125,7 +125,7 @@ public class ZygoteInit { */ private static ClassLoader sCachedSystemServerClassLoader = null; - static void preload(TimingsTraceLog bootTimingsTraceLog) { + static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { Log.d(TAG, "begin preload"); bootTimingsTraceLog.traceBegin("BeginPreload"); beginPreload(); @@ -174,6 +174,10 @@ static void preload(TimingsTraceLog bootTimingsTraceLog) { sPreloadComplete = true; } + static void preload(TimingsTraceLog bootTimingsTraceLog) { + preload(bootTimingsTraceLog, true); + } + static void lazyPreload() { Preconditions.checkState(!sPreloadComplete); Log.i(TAG, "Lazily preloading resources."); From ce990b773cd1cbffa3a744e30933d62271ea5b90 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Wed, 11 Sep 2019 06:43:55 -0400 Subject: [PATCH 011/332] pass through fullPreload to libcore --- core/java/com/android/internal/os/ZygoteInit.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 7e4e3796a32e3..94b586e84a6c5 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -128,7 +128,7 @@ public class ZygoteInit { static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { Log.d(TAG, "begin preload"); bootTimingsTraceLog.traceBegin("BeginPreload"); - beginPreload(); + beginPreload(fullPreload); bootTimingsTraceLog.traceEnd(); // BeginPreload bootTimingsTraceLog.traceBegin("PreloadClasses"); preloadClasses(); @@ -167,7 +167,7 @@ static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { // Ask the WebViewFactory to do any initialization that must run in the zygote process, // for memory sharing purposes. WebViewFactory.prepareWebViewInZygote(); - endPreload(); + endPreload(fullPreload); warmUpJcaProviders(); Log.d(TAG, "end preload"); @@ -185,14 +185,14 @@ static void lazyPreload() { preload(new TimingsTraceLog("ZygoteInitTiming_lazy", Trace.TRACE_TAG_DALVIK)); } - private static void beginPreload() { + private static void beginPreload(boolean fullPreload) { Log.i(TAG, "Calling ZygoteHooks.beginPreload()"); - ZygoteHooks.onBeginPreload(); + ZygoteHooks.onBeginPreload(fullPreload); } - private static void endPreload() { - ZygoteHooks.onEndPreload(); + private static void endPreload(boolean fullPreload) { + ZygoteHooks.onEndPreload(fullPreload); Log.i(TAG, "Called ZygoteHooks.endPreload()"); } From 21d6155e5dc34130f3b7c9b78544c126d81676f0 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 14 May 2019 14:28:27 -0400 Subject: [PATCH 012/332] disable OpenGL preloading for exec spawning --- core/java/com/android/internal/os/ZygoteInit.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 94b586e84a6c5..37df2b88f88ee 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -142,9 +142,11 @@ static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs"); nativePreloadAppProcessHALs(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); - Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadGraphicsDriver"); - maybePreloadGraphicsDriver(); - Trace.traceEnd(Trace.TRACE_TAG_DALVIK); + if (fullPreload) { + Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadGraphicsDriver"); + maybePreloadGraphicsDriver(); + Trace.traceEnd(Trace.TRACE_TAG_DALVIK); + } preloadSharedLibraries(); preloadTextResources(); From f5d46326970ad8ceb51d77709a2ef56d3cff34d1 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Sat, 16 Oct 2021 18:41:25 -0400 Subject: [PATCH 013/332] Revert "disable OpenGL preloading for exec spawning" This reverts commit 5a8d91b5fac0a1ae597de359128e0706776ce3a7. --- core/java/com/android/internal/os/ZygoteInit.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 37df2b88f88ee..94b586e84a6c5 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -142,11 +142,9 @@ static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs"); nativePreloadAppProcessHALs(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); - if (fullPreload) { - Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadGraphicsDriver"); - maybePreloadGraphicsDriver(); - Trace.traceEnd(Trace.TRACE_TAG_DALVIK); - } + Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadGraphicsDriver"); + maybePreloadGraphicsDriver(); + Trace.traceEnd(Trace.TRACE_TAG_DALVIK); preloadSharedLibraries(); preloadTextResources(); From 2c413f026e930962cf7f7874dc7892b78b1e770c Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 14 May 2019 14:28:52 -0400 Subject: [PATCH 014/332] disable resource preloading for exec spawning --- core/java/com/android/internal/os/ZygoteInit.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 94b586e84a6c5..6b8e11af1101c 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -136,9 +136,11 @@ static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { bootTimingsTraceLog.traceBegin("CacheNonBootClasspathClassLoaders"); cacheNonBootClasspathClassLoaders(); bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders - bootTimingsTraceLog.traceBegin("PreloadResources"); - Resources.preloadResources(); - bootTimingsTraceLog.traceEnd(); // PreloadResources + if (fullPreload) { + bootTimingsTraceLog.traceBegin("PreloadResources"); + Resources.preloadResources(); + bootTimingsTraceLog.traceEnd(); // PreloadResources + } Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs"); nativePreloadAppProcessHALs(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); From 382bf036b1686515377d9fdc963c84ea369695b7 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 14 May 2019 14:30:59 -0400 Subject: [PATCH 015/332] disable class preloading for exec spawning --- core/java/com/android/internal/os/ZygoteInit.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 6b8e11af1101c..924791bd491ba 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -130,9 +130,11 @@ static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { bootTimingsTraceLog.traceBegin("BeginPreload"); beginPreload(fullPreload); bootTimingsTraceLog.traceEnd(); // BeginPreload - bootTimingsTraceLog.traceBegin("PreloadClasses"); - preloadClasses(); - bootTimingsTraceLog.traceEnd(); // PreloadClasses + if (fullPreload) { + bootTimingsTraceLog.traceBegin("PreloadClasses"); + preloadClasses(); + bootTimingsTraceLog.traceEnd(); // PreloadClasses + } bootTimingsTraceLog.traceBegin("CacheNonBootClasspathClassLoaders"); cacheNonBootClasspathClassLoaders(); bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders From a9318b4d4c093fabcc5a08c038e8ff76026661be Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 14 May 2019 14:31:29 -0400 Subject: [PATCH 016/332] disable WebView reservation for exec spawning --- core/java/com/android/internal/os/ZygoteInit.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 924791bd491ba..1228d229d12c8 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -168,9 +168,11 @@ static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { Log.d(TAG, "HttpEngine.preload() threw " + e); } } - // Ask the WebViewFactory to do any initialization that must run in the zygote process, - // for memory sharing purposes. - WebViewFactory.prepareWebViewInZygote(); + if (fullPreload) { + // Ask the WebViewFactory to do any initialization that must run in the zygote process, + // for memory sharing purposes. + WebViewFactory.prepareWebViewInZygote(); + } endPreload(fullPreload); warmUpJcaProviders(); Log.d(TAG, "end preload"); From 291780563342bd6e718784b04e45cd3f0a3154be Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 14 May 2019 14:34:32 -0400 Subject: [PATCH 017/332] disable JCA provider warm up for exec spawning --- .../com/android/internal/os/ZygoteInit.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 1228d229d12c8..86c741683f3a4 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -174,7 +174,7 @@ static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { WebViewFactory.prepareWebViewInZygote(); } endPreload(fullPreload); - warmUpJcaProviders(); + warmUpJcaProviders(fullPreload); Log.d(TAG, "end preload"); sPreloadComplete = true; @@ -243,7 +243,7 @@ private static void preloadTextResources() { * By doing it here we avoid that each app does it when requesting a service from the provider * for the first time. */ - private static void warmUpJcaProviders() { + private static void warmUpJcaProviders(boolean fullPreload) { long startTime = SystemClock.uptimeMillis(); Trace.traceBegin( Trace.TRACE_TAG_DALVIK, "Starting installation of AndroidKeyStoreProvider"); @@ -253,15 +253,17 @@ private static void warmUpJcaProviders() { + (SystemClock.uptimeMillis() - startTime) + "ms."); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); - startTime = SystemClock.uptimeMillis(); - Trace.traceBegin( - Trace.TRACE_TAG_DALVIK, "Starting warm up of JCA providers"); - for (Provider p : Security.getProviders()) { - p.warmUpServiceProvision(); + if (fullPreload) { + startTime = SystemClock.uptimeMillis(); + Trace.traceBegin( + Trace.TRACE_TAG_DALVIK, "Starting warm up of JCA providers"); + for (Provider p : Security.getProviders()) { + p.warmUpServiceProvision(); + } + Log.i(TAG, "Warmed up JCA providers in " + + (SystemClock.uptimeMillis() - startTime) + "ms."); + Trace.traceEnd(Trace.TRACE_TAG_DALVIK); } - Log.i(TAG, "Warmed up JCA providers in " - + (SystemClock.uptimeMillis() - startTime) + "ms."); - Trace.traceEnd(Trace.TRACE_TAG_DALVIK); } private static boolean isExperimentEnabled(String experiment) { From 4de33b4e2053ea620a645388e6d722871d98d419 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Wed, 11 Sep 2019 06:57:24 -0400 Subject: [PATCH 018/332] disable preloading classloaders for exec spawning --- core/java/com/android/internal/os/ZygoteInit.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 86c741683f3a4..31212a770b0f0 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -135,9 +135,11 @@ static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { preloadClasses(); bootTimingsTraceLog.traceEnd(); // PreloadClasses } - bootTimingsTraceLog.traceBegin("CacheNonBootClasspathClassLoaders"); - cacheNonBootClasspathClassLoaders(); - bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders + if (fullPreload) { + bootTimingsTraceLog.traceBegin("CacheNonBootClasspathClassLoaders"); + cacheNonBootClasspathClassLoaders(); + bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders + } if (fullPreload) { bootTimingsTraceLog.traceBegin("PreloadResources"); Resources.preloadResources(); From 8fd7e4fe17ba6306ddd6d1002c301ed639ff6935 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Wed, 11 Sep 2019 06:58:51 -0400 Subject: [PATCH 019/332] disable preloading HALs for exec spawning --- core/java/com/android/internal/os/ZygoteInit.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 31212a770b0f0..e9d332ff9dc73 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -145,9 +145,11 @@ static void preload(TimingsTraceLog bootTimingsTraceLog, boolean fullPreload) { Resources.preloadResources(); bootTimingsTraceLog.traceEnd(); // PreloadResources } - Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs"); - nativePreloadAppProcessHALs(); - Trace.traceEnd(Trace.TRACE_TAG_DALVIK); + if (fullPreload) { + Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs"); + nativePreloadAppProcessHALs(); + Trace.traceEnd(Trace.TRACE_TAG_DALVIK); + } Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadGraphicsDriver"); maybePreloadGraphicsDriver(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); From 3392f4ac8e27b6ba8380dbba19867f3159ebe4bf Mon Sep 17 00:00:00 2001 From: anupritaisno1 Date: Sat, 31 Oct 2020 00:26:09 +0200 Subject: [PATCH 020/332] pass through runtime flags for exec spawning and implement them in the child --- .../com/android/internal/os/ExecInit.java | 14 +- core/java/com/android/internal/os/Zygote.java | 9 + .../android/internal/os/ZygoteConnection.java | 2 +- core/jni/com_android_internal_os_Zygote.cpp | 164 ++++++++++-------- 4 files changed, 107 insertions(+), 82 deletions(-) diff --git a/core/java/com/android/internal/os/ExecInit.java b/core/java/com/android/internal/os/ExecInit.java index 830e5b562a919..749c67abf389d 100644 --- a/core/java/com/android/internal/os/ExecInit.java +++ b/core/java/com/android/internal/os/ExecInit.java @@ -31,15 +31,20 @@ public static void main(String[] args) { // Parse our mandatory argument. int targetSdkVersion = Integer.parseInt(args[0], 10); + // Parse the runtime_flags. + int runtimeFlags = Integer.parseInt(args[1], 10); + // Mimic system Zygote preloading. ZygoteInit.preload(new TimingsTraceLog("ExecInitTiming", Trace.TRACE_TAG_DALVIK), false); // Launch the application. - String[] runtimeArgs = new String[args.length - 1]; - System.arraycopy(args, 1, runtimeArgs, 0, runtimeArgs.length); + String[] runtimeArgs = new String[args.length - 2]; + System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length); Runnable r = execInit(targetSdkVersion, runtimeArgs); + Zygote.nativeHandleRuntimeFlags(runtimeFlags); + r.run(); } @@ -52,9 +57,9 @@ public static void main(String[] args) { * @param args Arguments for {@link RuntimeInit#main}. */ public static void execApplication(String niceName, int targetSdkVersion, - String instructionSet, String[] args) { + String instructionSet, int runtimeFlags, String[] args) { int niceArgs = niceName == null ? 0 : 1; - int baseArgs = 5 + niceArgs; + int baseArgs = 6 + niceArgs; String[] argv = new String[baseArgs + args.length]; if (VMRuntime.is64BitInstructionSet(instructionSet)) { argv[0] = "/system/bin/app_process64"; @@ -68,6 +73,7 @@ public static void execApplication(String niceName, int targetSdkVersion, } argv[3 + niceArgs] = "com.android.internal.os.ExecInit"; argv[4 + niceArgs] = Integer.toString(targetSdkVersion); + argv[5 + niceArgs] = Integer.toString(runtimeFlags); System.arraycopy(args, 0, argv, baseArgs, args.length); WrapperInit.preserveCapabilities(); diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index cd17ed89058a1..65d6e0cde246b 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -1498,4 +1498,13 @@ && isCompatChangeEnabled( } return runtimeFlags; } + + /** + * Used on GrapheneOS to set up runtime flags + * + * @param runtimeFlags flags to be passed to the native method + * + * @hide + */ + public static native void nativeHandleRuntimeFlags(int runtimeFlags); } diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 55c4c279b3ff7..d10cc8c417c8c 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -529,7 +529,7 @@ private Runnable handleChildProc(ZygoteArguments parsedArgs, if (ExtSettings.EXEC_SPAWNING.get() && (parsedArgs.mRuntimeFlags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { ExecInit.execApplication(parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, - VMRuntime.getCurrentInstructionSet(), parsedArgs.mRemainingArgs); + VMRuntime.getCurrentInstructionSet(), parsedArgs.mRuntimeFlags, parsedArgs.mRemainingArgs); // Should not get here. throw new IllegalStateException("ExecInit.execApplication unexpectedly returned"); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index fea3b1dd1a6b0..9cd922f3552f9 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1896,6 +1896,86 @@ static void BindMountStorageDirs(JNIEnv* env, jobjectArray pkg_data_info_list, } } +static void HandleRuntimeFlags(JNIEnv* env, jint& runtime_flags, const char* process_name, const char* nice_name_ptr) { + // Set process properties to enable debugging if required. + if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_PTRACE) != 0) { + EnableDebugger(); + // Don't pass unknown flag to the ART runtime. + runtime_flags &= ~RuntimeFlags::DEBUG_ENABLE_PTRACE; + } + if ((runtime_flags & RuntimeFlags::PROFILE_FROM_SHELL) != 0) { + // simpleperf needs the process to be dumpable to profile it. + if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) { + ALOGE("prctl(PR_SET_DUMPABLE) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 1) failed"); + } + } + + HeapTaggingLevel heap_tagging_level; + switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) { + case RuntimeFlags::MEMORY_TAG_LEVEL_TBI: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI; + break; + case RuntimeFlags::MEMORY_TAG_LEVEL_ASYNC: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC; + break; + case RuntimeFlags::MEMORY_TAG_LEVEL_SYNC: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC; + break; + default: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE; + break; + } + mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level); + + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART + // runtime. + runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK; + + // Avoid heap zero initialization for applications without MTE. Zero init may + // cause app compat problems, use more memory, or reduce performance. While it + // would be nice to have them for apps, we will have to wait until they are + // proven out, have more efficient hardware, and/or apply them only to new + // applications. + if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED)) { + mallopt(M_BIONIC_ZERO_INIT, 0); + } + + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART + // runtime. + runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED; + + android_mallopt_gwp_asan_options_t gwp_asan_options; + const char* kGwpAsanAppRecoverableSysprop = + "persist.device_config.memory_safety_native.gwp_asan_recoverable_apps"; + // The system server doesn't have its nice name set by the time SpecializeCommon is called. + gwp_asan_options.program_name = nice_name_ptr ?: process_name; + switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) { + default: + case RuntimeFlags::GWP_ASAN_LEVEL_DEFAULT: + gwp_asan_options.mode = GetBoolProperty(kGwpAsanAppRecoverableSysprop, true) + ? Mode::APP_MANIFEST_DEFAULT + : Mode::APP_MANIFEST_NEVER; + android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); + break; + case RuntimeFlags::GWP_ASAN_LEVEL_NEVER: + gwp_asan_options.mode = Mode::APP_MANIFEST_NEVER; + android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); + break; + case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS: + gwp_asan_options.mode = Mode::APP_MANIFEST_ALWAYS; + android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); + break; + case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY: + gwp_asan_options.mode = Mode::APP_MANIFEST_DEFAULT; + android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); + break; + } + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART + // runtime. + runtime_flags &= ~RuntimeFlags::GWP_ASAN_LEVEL_MASK; +} + // Utility routine to specialize a zygote child process. static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, @@ -2060,84 +2140,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - // Set process properties to enable debugging if required. - if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_PTRACE) != 0) { - EnableDebugger(); - // Don't pass unknown flag to the ART runtime. - runtime_flags &= ~RuntimeFlags::DEBUG_ENABLE_PTRACE; - } - if ((runtime_flags & RuntimeFlags::PROFILE_FROM_SHELL) != 0) { - // simpleperf needs the process to be dumpable to profile it. - if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) { - ALOGE("prctl(PR_SET_DUMPABLE) failed: %s", strerror(errno)); - RuntimeAbort(env, __LINE__, "prctl(PR_SET_DUMPABLE, 1) failed"); - } - } - - HeapTaggingLevel heap_tagging_level; - switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) { - case RuntimeFlags::MEMORY_TAG_LEVEL_TBI: - heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI; - break; - case RuntimeFlags::MEMORY_TAG_LEVEL_ASYNC: - heap_tagging_level = M_HEAP_TAGGING_LEVEL_ASYNC; - break; - case RuntimeFlags::MEMORY_TAG_LEVEL_SYNC: - heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC; - break; - default: - heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE; - break; - } - mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level); - - // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART - // runtime. - runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK; - - // Avoid heap zero initialization for applications without MTE. Zero init may - // cause app compat problems, use more memory, or reduce performance. While it - // would be nice to have them for apps, we will have to wait until they are - // proven out, have more efficient hardware, and/or apply them only to new - // applications. - if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED)) { - mallopt(M_BIONIC_ZERO_INIT, 0); - } - - // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART - // runtime. - runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT_ENABLED; - const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr; - android_mallopt_gwp_asan_options_t gwp_asan_options; - const char* kGwpAsanAppRecoverableSysprop = - "persist.device_config.memory_safety_native.gwp_asan_recoverable_apps"; - // The system server doesn't have its nice name set by the time SpecializeCommon is called. - gwp_asan_options.program_name = nice_name_ptr ?: process_name; - switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) { - default: - case RuntimeFlags::GWP_ASAN_LEVEL_DEFAULT: - gwp_asan_options.mode = GetBoolProperty(kGwpAsanAppRecoverableSysprop, true) - ? Mode::APP_MANIFEST_DEFAULT - : Mode::APP_MANIFEST_NEVER; - android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); - break; - case RuntimeFlags::GWP_ASAN_LEVEL_NEVER: - gwp_asan_options.mode = Mode::APP_MANIFEST_NEVER; - android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); - break; - case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS: - gwp_asan_options.mode = Mode::APP_MANIFEST_ALWAYS; - android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); - break; - case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY: - gwp_asan_options.mode = Mode::APP_MANIFEST_DEFAULT; - android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); - break; - } - // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART - // runtime. - runtime_flags &= ~RuntimeFlags::GWP_ASAN_LEVEL_MASK; + + HandleRuntimeFlags(env, runtime_flags, process_name, nice_name_ptr); SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn); @@ -3066,6 +3071,10 @@ static void com_android_internal_os_Zygote_nativeAllowFilesOpenedByPreload(JNIEn gPreloadFdsExtracted = true; } +static void nativeHandleRuntimeFlagsWrapper(JNIEnv* env, jclass, jint runtime_flags) { + HandleRuntimeFlags(env, runtime_flags, nullptr, nullptr); +} + static const JNINativeMethod gMethods[] = { {"nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/" @@ -3118,6 +3127,7 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_internal_os_Zygote_nativeMarkOpenedFilesBeforePreload}, {"nativeAllowFilesOpenedByPreload", "()V", (void*)com_android_internal_os_Zygote_nativeAllowFilesOpenedByPreload}, + {"nativeHandleRuntimeFlags", "(I)V", (void*)nativeHandleRuntimeFlagsWrapper}, }; int register_com_android_internal_os_Zygote(JNIEnv* env) { From dfa2f94121859febd1652def79696a195a890480 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Mon, 8 Aug 2022 18:42:19 +0300 Subject: [PATCH 021/332] exec spawning: don't close the binder connection when the app crashes When an unhandled exception occured, binder connections were closed with IPCThreadState::stopProcess() before the invocation of java.lang.Thread#dispatchUncaughtException(). By default, that method tries to report the crash via ActivityManager#handleApplicationCrash(), which always failed due to the closed binder connection. This meant that the crash dialog was never shown and additional crash handling was skipped. Zygote-based spawning never calls IPCThreadState::stopProcess(). --- cmds/app_process/app_main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 4e41f2c1ac35a..9ce4bef5bb6c1 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -85,8 +85,10 @@ class AppRuntime : public AndroidRuntime AndroidRuntime* ar = AndroidRuntime::getRuntime(); ar->callMain(mClassName, mClass, mArgs); - IPCThreadState::self()->stopProcess(); - hardware::IPCThreadState::self()->stopProcess(); + if (mClassName != "com.android.internal.os.ExecInit") { + IPCThreadState::self()->stopProcess(); + hardware::IPCThreadState::self()->stopProcess(); + } } virtual void onZygoteInit() From 3c145f870aaa156aca834bece4f3bd622d34b5ac Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 14 Aug 2022 15:35:44 +0300 Subject: [PATCH 022/332] add a wrapper for execveat(2) Needed for exec spawning, to pass custom flags to the kernel. --- core/java/com/android/internal/os/Zygote.java | 2 + core/jni/Android.bp | 1 + core/jni/ExecStrings.cpp | 74 +++++++++++++++++++ core/jni/ExecStrings.h | 37 ++++++++++ core/jni/com_android_internal_os_Zygote.cpp | 15 ++++ 5 files changed, 129 insertions(+) create mode 100644 core/jni/ExecStrings.cpp create mode 100644 core/jni/ExecStrings.h diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 65d6e0cde246b..30ae4aa488d46 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -1507,4 +1507,6 @@ && isCompatChangeEnabled( * @hide */ public static native void nativeHandleRuntimeFlags(int runtimeFlags); + + public static native int execveatWrapper(int dirFd, String filename, String[] argv, int flags); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 40f6acceecb17..b06b53ee7d004 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -279,6 +279,7 @@ cc_library_shared_for_libandroid_runtime { "android_tracing_PerfettoDataSource.cpp", "android_tracing_PerfettoDataSourceInstance.cpp", "android_tracing_PerfettoProducer.cpp", + "ExecStrings.cpp", ], static_libs: [ diff --git a/core/jni/ExecStrings.cpp b/core/jni/ExecStrings.cpp new file mode 100644 index 0000000000000..6fdca3a39c63c --- /dev/null +++ b/core/jni/ExecStrings.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 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. + */ + +// copied from libcore/luni/src/main/native/ExecStrings.cpp, commit cab01ac294bb8ded259851673baa4c6ca226f828 + +#define LOG_TAG "ExecStrings" + +#include "ExecStrings.h" + +#include + +#include + +#include + +ExecStrings::ExecStrings(JNIEnv* env, jobjectArray java_string_array) + : env_(env), java_array_(java_string_array), array_(NULL) { + if (java_array_ == NULL) { + return; + } + + jsize length = env_->GetArrayLength(java_array_); + array_ = new char*[length + 1]; + array_[length] = NULL; + for (jsize i = 0; i < length; ++i) { + ScopedLocalRef java_string(env_, reinterpret_cast(env_->GetObjectArrayElement(java_array_, i))); + // We need to pass these strings to const-unfriendly code. + char* string = const_cast(env_->GetStringUTFChars(java_string.get(), NULL)); + array_[i] = string; + } +} + +ExecStrings::~ExecStrings() { + if (array_ == NULL) { + return; + } + + // Temporarily clear any pending exception so we can clean up. + jthrowable pending_exception = env_->ExceptionOccurred(); + if (pending_exception != NULL) { + env_->ExceptionClear(); + } + + jsize length = env_->GetArrayLength(java_array_); + for (jsize i = 0; i < length; ++i) { + ScopedLocalRef java_string(env_, reinterpret_cast(env_->GetObjectArrayElement(java_array_, i))); + env_->ReleaseStringUTFChars(java_string.get(), array_[i]); + } + delete[] array_; + + // Re-throw any pending exception. + if (pending_exception != NULL) { + if (env_->Throw(pending_exception) < 0) { + ALOGE("Error rethrowing exception!"); + } + } +} + +char** ExecStrings::get() { + return array_; +} diff --git a/core/jni/ExecStrings.h b/core/jni/ExecStrings.h new file mode 100644 index 0000000000000..7a161b589741d --- /dev/null +++ b/core/jni/ExecStrings.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2011 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. + */ + +// copied from libcore/luni/src/main/native/ExecStrings.h, commit cab01ac294bb8ded259851673baa4c6ca226f828 + +#include "jni.h" + +class ExecStrings { + public: + ExecStrings(JNIEnv* env, jobjectArray java_string_array); + + ~ExecStrings(); + + char** get(); + + private: + JNIEnv* env_; + jobjectArray java_array_; + char** array_; + + // Disallow copy and assignment. + ExecStrings(const ExecStrings&); + void operator=(const ExecStrings&); +}; diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 9cd922f3552f9..eceded57555cf 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -87,6 +87,8 @@ #include "nativebridge/native_bridge.h" +#include "ExecStrings.h" + #if defined(__BIONIC__) #include extern "C" void android_reset_stack_guards(); @@ -3075,6 +3077,18 @@ static void nativeHandleRuntimeFlagsWrapper(JNIEnv* env, jclass, jint runtime_fl HandleRuntimeFlags(env, runtime_flags, nullptr, nullptr); } +static jint execveatWrapper(JNIEnv* env, jclass, jint dirFd, jstring javaFilename, jobjectArray javaArgv, jint flags) { + ScopedUtfChars path(env, javaFilename); + if (path.c_str() == NULL) { + return EINVAL; + } + + ExecStrings argv(env, javaArgv); + TEMP_FAILURE_RETRY(execveat(dirFd, path.c_str(), argv.get(), environ, flags)); + // execveat never returns on success + return errno; +} + static const JNINativeMethod gMethods[] = { {"nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/" @@ -3128,6 +3142,7 @@ static const JNINativeMethod gMethods[] = { {"nativeAllowFilesOpenedByPreload", "()V", (void*)com_android_internal_os_Zygote_nativeAllowFilesOpenedByPreload}, {"nativeHandleRuntimeFlags", "(I)V", (void*)nativeHandleRuntimeFlagsWrapper}, + {"execveatWrapper", "(ILjava/lang/String;[Ljava/lang/String;I)I", (void*)execveatWrapper}, }; int register_com_android_internal_os_Zygote(JNIEnv* env) { From 4dc5f78540dd9142ba3b463d75ec8e0218fd11a9 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 14 Aug 2022 17:05:32 +0300 Subject: [PATCH 023/332] zygote: define runtime flags that depend on exec spawning --- core/java/com/android/internal/os/Zygote.java | 9 +++++++++ .../com/android/internal/os/ZygoteConnection.java | 15 +++++++++++---- ...om_android_internal_os_ZygoteCommandBuffer.cpp | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 30ae4aa488d46..229b7cdf252b2 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -204,6 +204,14 @@ public final class Zygote { /** Load 4KB ELF files on 16KB device using appcompat mode */ public static final int ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26; + public static final int DISABLE_HARDENED_MALLOC = 1 << 29; + public static final int ENABLE_COMPAT_VA_39_BIT = 1 << 30; + + // make sure to update isSimpleForkCommand() in core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp + // when adding new flags that depend on exec spawning + public static final int RUNTIME_FLAGS_DEPENDENT_ON_EXEC_SPAWNING = DISABLE_HARDENED_MALLOC | ENABLE_COMPAT_VA_39_BIT; + public static final int CUSTOM_RUNTIME_FLAGS = DISABLE_HARDENED_MALLOC | ENABLE_COMPAT_VA_39_BIT; + /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; /** Default external storage should be mounted. */ @@ -1168,6 +1176,7 @@ private static void callPostForkSystemServerHooks(int runtimeFlags) { @SuppressWarnings("unused") private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer, boolean isZygote, String instructionSet) { + runtimeFlags &= ~CUSTOM_RUNTIME_FLAGS; // a warning is printed when an unknown flag is passed ZygoteHooks.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet); } diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index d10cc8c417c8c..5859d38301b39 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -244,7 +244,8 @@ Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) { } if (parsedArgs.mInvokeWith != null || ExtSettings.EXEC_SPAWNING.get() || parsedArgs.mStartChildZygote - || !multipleOK || peer.getUid() != Process.SYSTEM_UID) { + || !multipleOK || peer.getUid() != Process.SYSTEM_UID + || (parsedArgs.mRuntimeFlags & Zygote.RUNTIME_FLAGS_DEPENDENT_ON_EXEC_SPAWNING) != 0) { // Continue using old code for now. TODO: Handle these cases in the other path. pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits, @@ -526,10 +527,16 @@ private Runnable handleChildProc(ZygoteArguments parsedArgs, throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); } else { if (!isZygote) { - if (ExtSettings.EXEC_SPAWNING.get() && - (parsedArgs.mRuntimeFlags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { + final int runtimeFlags = parsedArgs.mRuntimeFlags; + boolean useExecInit = + ((runtimeFlags & Zygote.RUNTIME_FLAGS_DEPENDENT_ON_EXEC_SPAWNING) != 0 + || ExtSettings.EXEC_SPAWNING.get()) + && + (runtimeFlags & ApplicationInfo.FLAG_DEBUGGABLE) == 0; + + if (useExecInit) { ExecInit.execApplication(parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, - VMRuntime.getCurrentInstructionSet(), parsedArgs.mRuntimeFlags, parsedArgs.mRemainingArgs); + VMRuntime.getCurrentInstructionSet(), runtimeFlags, parsedArgs.mRemainingArgs); // Should not get here. throw new IllegalStateException("ExecInit.execApplication unexpectedly returned"); diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp index c4259f41e3802..211181b5fbbfb 100644 --- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp +++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp @@ -172,6 +172,9 @@ class NativeCommandBuffer { static const size_t CA_LENGTH = strlen(CAPABILITIES); static const size_t NN_LENGTH = strlen(NICE_NAME); + static const char* RUNTIME_FLAGS = "--runtime-flags="; + static const size_t RF_LENGTH = strlen(RUNTIME_FLAGS); + bool saw_setuid = false, saw_setgid = false; bool saw_runtime_args = false; @@ -186,6 +189,17 @@ class NativeCommandBuffer { saw_runtime_args = true; continue; } + if (static_cast(arg_end - arg_start) >= RF_LENGTH + && strncmp(arg_start, RUNTIME_FLAGS, RF_LENGTH) == 0) { + int flags = digitsVal(arg_start + RF_LENGTH, arg_end); + const int DISABLE_HARDENED_MALLOC = 1 << 29; + const int ENABLE_COMPAT_VA_39_BIT = 1 << 30; + if (flags & (DISABLE_HARDENED_MALLOC | ENABLE_COMPAT_VA_39_BIT)) { + // fallback to the slow path that calls ExecInit + return false; + } + continue; + } if (static_cast(arg_end - arg_start) >= NN_LENGTH && strncmp(arg_start, NICE_NAME, NN_LENGTH) == 0) { size_t name_len = arg_end - (arg_start + NN_LENGTH); From 13010cb91ca04375280de1d660afd4f5061a5604 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Thu, 30 Mar 2023 17:42:56 +0300 Subject: [PATCH 024/332] exec spawning: support runtime resource overlays --- core/java/android/app/IActivityManager.aidl | 2 + .../android/content/res/AssetManager.java | 37 +++++++++++++++++-- .../com/android/internal/os/ExecInit.java | 4 ++ .../server/am/ActivityManagerService.java | 6 +++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 6cdfb97520ae6..ae4decb5fb459 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -1038,4 +1038,6 @@ interface IActivityManager { */ @EnforcePermission("INTERACT_ACROSS_USERS_FULL") IBinder refreshIntentCreatorToken(in Intent intent); + + String[] getSystemIdmapPaths(); } diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 008bf2f522c39..4d71463549234 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -27,6 +27,7 @@ import android.annotation.StringRes; import android.annotation.StyleRes; import android.annotation.TestApi; +import android.app.ActivityManager; import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; @@ -46,6 +47,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.om.OverlayConfig; +import com.android.internal.os.ExecInit; import com.android.internal.ravenwood.RavenwoodEnvironment; import java.io.FileDescriptor; @@ -266,6 +268,9 @@ private AssetManager(boolean sentinel) { } } + /** @hide */ + public static volatile String[] systemIdmapPaths_; + /** * This must be called from Zygote so that system assets are shared by all applications. * @hide @@ -283,9 +288,35 @@ public static void createSystemAssetsInZygoteLocked(boolean reinitialize, apkAssets.add(ApkAssets.loadFromPath(frameworkPath, ApkAssets.PROPERTY_SYSTEM)); // TODO(Ravenwood): overlay support? - final String[] systemIdmapPaths = - RavenwoodEnvironment.getInstance().isRunningOnRavenwood() ? new String[0] : - OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote(); + String[] systemIdmapPaths; + if (RavenwoodEnvironment.getInstance().isRunningOnRavenwood()) { + systemIdmapPaths = new String[0]; + } else { + // createImmutableFrameworkIdmapsInZygote() should be called only in zygote, it fails + // in regular processes and is unnecessary there. + // When it's called in zygote, overlay state is cached in /data/resource-cache/*@idmap + // files. These files are readable by regular app processes. + // + // When exec-based spawning in used, in-memory cache of assets is lost, and the spawned + // process is unable to recreate it, since it's not allowed to create idmaps. + // + // As a workaround, ask the ActivityManager to return paths of cached idmaps and use + // them directly. ActivityManager runs in system_server, which always uses zygote-based + // spawning. + if (ExecInit.isExecSpawned) { + try { + systemIdmapPaths = ActivityManager.getService().getSystemIdmapPaths(); + Objects.requireNonNull(systemIdmapPaths); + } catch (Throwable t) { + Log.e(TAG, "unable to retrieve systemIdmapPaths", t); + systemIdmapPaths = new String[0]; + } + } else { + systemIdmapPaths = OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote(); + systemIdmapPaths_ = systemIdmapPaths; + } + } + for (String idmapPath : systemIdmapPaths) { apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, ApkAssets.PROPERTY_SYSTEM)); } diff --git a/core/java/com/android/internal/os/ExecInit.java b/core/java/com/android/internal/os/ExecInit.java index 749c67abf389d..39f08b6a0f15b 100644 --- a/core/java/com/android/internal/os/ExecInit.java +++ b/core/java/com/android/internal/os/ExecInit.java @@ -84,6 +84,8 @@ public static void execApplication(String niceName, int targetSdkVersion, } } + public static boolean isExecSpawned; + /** * The main function called when an application is started with exec-based spawning. * @@ -99,6 +101,8 @@ private static Runnable execInit(int targetSdkVersion, String[] argv) { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from exec"); } + isExecSpawned = true; + // Check whether the first argument is a "-cp" in argv, and assume the next argument is the // classpath. If found, create a PathClassLoader and use it for applicationInit. ClassLoader classLoader = null; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f6e8e8b2b7b6b..df71db91b7ede 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19521,4 +19521,10 @@ private static IntentCreatorToken createOrGetIntentCreatorToken(Intent intent, } return token; } + + @Override + public String[] getSystemIdmapPaths() { + // see comment in AssetManager#createSystemAssetsInZygoteLocked() + return android.content.res.AssetManager.systemIdmapPaths_; + } } From a6dd82160bd8a19bb201c08efa9436ca5f384790 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 14 Aug 2022 17:08:28 +0300 Subject: [PATCH 025/332] exec spawning: support disabling hardened_malloc and extended VA space --- .../com/android/internal/os/ExecInit.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/core/java/com/android/internal/os/ExecInit.java b/core/java/com/android/internal/os/ExecInit.java index 39f08b6a0f15b..a24bbf94f698f 100644 --- a/core/java/com/android/internal/os/ExecInit.java +++ b/core/java/com/android/internal/os/ExecInit.java @@ -3,6 +3,7 @@ import android.os.Trace; import android.system.ErrnoException; import android.system.Os; +import android.system.OsConstants; import android.util.Slog; import android.util.TimingsTraceLog; import dalvik.system.VMRuntime; @@ -78,7 +79,26 @@ public static void execApplication(String niceName, int targetSdkVersion, WrapperInit.preserveCapabilities(); try { - Os.execv(argv[0], argv); + if ((runtimeFlags & Zygote.DISABLE_HARDENED_MALLOC) != 0) { + // checked by bionic during early init + Os.setenv("DISABLE_HARDENED_MALLOC", "1", true); + } + + if ((runtimeFlags & Zygote.ENABLE_COMPAT_VA_39_BIT) != 0) { + final int FLAG_COMPAT_VA_39_BIT = 1 << 30; + + int errno = Zygote.execveatWrapper(-1, argv[0], argv, FLAG_COMPAT_VA_39_BIT); + + if (errno == OsConstants.EINVAL) { + // kernel doesn't support FLAG_COMPAT_VA_39_BIT, or a different error that will + // be thrown by execv() anyway + Os.execv(argv[0], argv); + } else { + throw new ErrnoException("execveat", errno); + } + } else { + Os.execv(argv[0], argv); + } } catch (ErrnoException e) { throw new RuntimeException(e); } From a1c324bf3027e351100d2cefb8b365d92868a447 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Tue, 9 Apr 2024 14:06:27 +0300 Subject: [PATCH 026/332] exec spawning: add workaround for late init of ART userfaultfd GC Chromium browser and its derivatives setup a seccomp syscall filter in their isolated processes, which blocks creation of new userfaultfds. Since 14 QPR2, ART uses a new userfaultfd-based GC. When zygote-based process spawning is used, userfaultfd GC is initialized before any of app's code is executed, i.e. before Chromium's seccomp syscall filter is installed. When exec spawning is used, userfaultfd GC initialization is delayed until first garbage collection. Chromium's seccomp syscall filter is already installed at that point. This leads to crashes of isolated Chromium processes (both browser and WebView), with the following log messages: E cr_seccomp: ../../sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc:**CRASHING**:seccomp-bpf failure in syscall E cr_seccomp: nr=0x11a arg1=0x80001 arg2=0xc arg3=0xffffffffffffffff arg4=0xc As a workaround, perform early initialization of ART userfaultfd GC in isolated processes by calling System.gc() before executing app's code. On Pixel 8, this increases startup latency by around 4 to 10 milliseconds. --- core/java/com/android/internal/os/ExecInit.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/java/com/android/internal/os/ExecInit.java b/core/java/com/android/internal/os/ExecInit.java index a24bbf94f698f..5873a011ffa39 100644 --- a/core/java/com/android/internal/os/ExecInit.java +++ b/core/java/com/android/internal/os/ExecInit.java @@ -1,5 +1,6 @@ package com.android.internal.os; +import android.os.Process; import android.os.Trace; import android.system.ErrnoException; import android.system.Os; @@ -140,6 +141,9 @@ private static Runnable execInit(int targetSdkVersion, String[] argv) { // Perform the same initialization that would happen after the Zygote forks. Zygote.nativePreApplicationInit(); + if (Process.isIsolated()) { + System.gc(); + } return RuntimeInit.applicationInit(targetSdkVersion, /*disabledCompatChanges*/ null, argv, classLoader); } } From 39cfba3772f0fcc1da815fec587ecf726eb2fe32 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Tue, 23 Jan 2024 14:21:17 +0200 Subject: [PATCH 027/332] use HTTPS X-Time header for time updates instead of NTP Based on 57448ae65ee86fd218bd411947588b848658e5e8 and d17960e03d9b641dbed7ff330f77480a82d97faf Co-authored-by: Renlord Co-authored-by: Daniel Micay --- core/java/android/net/EventLogTags.logtags | 2 + core/java/android/net/HttpsTimeClient.java | 421 +++++++++++++++++++++ core/java/android/util/NtpTrustedTime.java | 43 ++- core/res/res/values/config.xml | 6 + 4 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 core/java/android/net/HttpsTimeClient.java diff --git a/core/java/android/net/EventLogTags.logtags b/core/java/android/net/EventLogTags.logtags index 32953c92d1203..94726cae55443 100644 --- a/core/java/android/net/EventLogTags.logtags +++ b/core/java/android/net/EventLogTags.logtags @@ -4,3 +4,5 @@ option java_package android.net 50080 ntp_success (server|3),(rtt|2),(offset|2) 50081 ntp_failure (server|3),(msg|3) +50082 https_time_success (url|3),(rtt|2),(offset|2) +50083 https_time_failure (url|3),(msg|3) diff --git a/core/java/android/net/HttpsTimeClient.java b/core/java/android/net/HttpsTimeClient.java new file mode 100644 index 0000000000000..fadfc94a4c769 --- /dev/null +++ b/core/java/android/net/HttpsTimeClient.java @@ -0,0 +1,421 @@ +package android.net; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.os.SystemClock; +import android.util.Log; +import android.util.NtpTrustedTime.TimeResult; + +import com.android.internal.R; + +import java.io.InputStream; +import java.math.BigInteger; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; + +import libcore.io.IoUtils; + +import static com.android.net.module.util.ConnectivityUtils.saturatedCast; + +/** @hide */ +public class HttpsTimeClient { + private static final String TAG = HttpsTimeClient.class.getSimpleName(); + + private final Config config; + private final Network network; + + public HttpsTimeClient(Config config, Network network) { + this.config = config; + this.network = network; + } + + public static class Config { + public final List urls; + public final int timeoutMillis; + + Config(List urls, int timeoutMillis) { + this.urls = urls; + this.timeoutMillis = timeoutMillis; + } + + public static Config getDefault(Context ctx) { + Resources res = ctx.getResources(); + + String[] urlArr = res.getStringArray(R.array.config_httpsTimeUrls); + int num = urlArr.length; + List urls = new ArrayList<>(num); + for (int i = 0; i < num; ++i) { + URL url; + try { + url = new URL(urlArr[i]); + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } + urls.add(url); + } + final int timeoutMillis = res.getInteger(R.integer.config_ntpTimeout); + + return new Config(urls, timeoutMillis); + } + + @Override + public String toString() { + return "HttpsTimeConfig{urls=" + Arrays.toString(urls.toArray()) + + ", timeoutMillis=" + timeoutMillis + + '}'; + } + } + + public static class Result { + public final TimeResult timeResult; + public final URL url; + + Result(TimeResult timeResult, URL url) { + this.timeResult = timeResult; + this.url = url; + } + } + + public Result requestTime(@Nullable URL lastSuccessfulUrl) { + ArrayList urls = new ArrayList<>(config.urls); + if (urls.remove(lastSuccessfulUrl)) { + urls.add(0, lastSuccessfulUrl); + } + + for (URL url : urls) { + TimeResult timeResult = requestTimeInner(url); + if (timeResult != null) { + return new Result(timeResult, url); + } + } + return null; + } + + private TimeResult requestTimeInner(URL url) { + final Network networkForResolv = network.getPrivateDnsBypassingCopy(); + final int timeout = config.timeoutMillis; + HttpsURLConnection conn = null; + InputStream streamToClose = null; + try { + SSLSocketFactory socketFactory = createSSLSocketFactory(); + + // establish HTTPS connection in advance to improve accuracy + conn = (HttpsURLConnection) networkForResolv.openConnection(url); + conn.setSSLSocketFactory(socketFactory); + conn.setConnectTimeout(timeout); + conn.setReadTimeout(timeout); + // closing the InputStream makes the connection available for reuse + conn.getInputStream().close(); + + conn = (HttpsURLConnection) networkForResolv.openConnection(url); + conn.setSSLSocketFactory(socketFactory); + conn.setConnectTimeout(timeout); + conn.setReadTimeout(timeout); + conn.setRequestProperty("Connection", "close"); + + final long requestTime = System.currentTimeMillis(); + final long requestTicks = SystemClock.elapsedRealtime(); + streamToClose = conn.getInputStream(); + final long responseTicks = SystemClock.elapsedRealtime(); + + long serverTime; + try { + serverTime = Long.parseLong(conn.getHeaderField("X-Time")); + } catch (final NumberFormatException e) { + Log.w(TAG, "X-Time header is missing, falling back to the less precise date header", e); + serverTime = conn.getDate(); + } + + final long roundTripTime = responseTicks - requestTicks; + final long responseTime = requestTime + roundTripTime; + final long clockOffset = ((serverTime - requestTime) + (serverTime - responseTime)) / 2; + + Log.d(TAG, "roundTripTime: " + roundTripTime + " ms, " + + "clockOffset: " + clockOffset + " ms"); + + if (serverTime < android.os.Build.TIME) { + throw new GeneralSecurityException("server timestamp is before android.os.Build.TIME"); + } + + EventLogTags.writeHttpsTimeSuccess(url.toString(), roundTripTime, clockOffset); + + return new TimeResult( + responseTime + clockOffset, // unixEpochTimeMillis + responseTicks, // elapsedRealtimeMillis + saturatedCast(roundTripTime / 2), // uncertaintyMillis + InetSocketAddress.createUnresolved(url.getHost(), 443) // ntpServerSocketAddress + ); + } catch (Exception e) { + EventLogTags.writeHttpsTimeFailure(url.toString(), e.toString()); + Log.e(TAG, "request failed, url: " + url, e); + return null; + } finally { + if (conn != null) { + conn.disconnect(); + } + if (streamToClose != null) { + // it's important to close the stream after conn.disconnect(), otherwise connection + // might be reused for the next request + IoUtils.closeQuietly(streamToClose); + } + } + } + + // SSL certificate time checks might fail if the time was never synced before, or if it has + // drifted far enough after the previous sync, + // + // To prevent this issue, construct a special SSLSocketFactory that uses the OS build time + // (android.os.Build.TIME) for SSL certificate expiration checks + private static SSLSocketFactory createSSLSocketFactory() throws KeyManagementException, + KeyStoreException, NoSuchAlgorithmException { + var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + + TrustManager[] trustManagers = tmf.getTrustManagers(); + for (int i = 0; i < trustManagers.length; ++i) { + if (trustManagers[i] instanceof X509TrustManager xtm) { + trustManagers[i] = new X509TrustManagerWrapper(xtm); + } + } + + SSLContext sslCtx = SSLContext.getInstance("TLS"); + sslCtx.init(null, trustManagers, null); + return sslCtx.getSocketFactory(); + } + + // see createSSLSocketFactory() + static class X509TrustManagerWrapper implements X509TrustManager { + final X509TrustManager orig; + + X509TrustManagerWrapper(X509TrustManager orig) { + this.orig = orig; + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + int num = chain.length; + var wrappedChain = new X509Certificate[num]; + for (int i = 0; i < num; ++i) { + wrappedChain[i] = new X509CertificateWrapper(chain[i]); + } + orig.checkServerTrusted(wrappedChain, authType); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return orig.getAcceptedIssuers(); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + // unused + throw new IllegalStateException(authType); + } + } + + // see createSSLSocketFactory() + static class X509CertificateWrapper extends X509Certificate { + private final X509Certificate orig; + + X509CertificateWrapper(X509Certificate orig) { + this.orig = orig; + } + + @Override + public void checkValidity() throws CertificateExpiredException { + checkValidity(null); + } + + @Override + public void checkValidity(Date ignored) throws CertificateExpiredException { + final Date buildDate = new Date(android.os.Build.TIME); + + // don't check notBefore: HttpsTimeClient is the main time source + + if (buildDate.after(getNotAfter())) { + String msg = "notAfter: " + getNotAfter() + ", buildDate: " + buildDate; + throw new CertificateExpiredException(msg); + } + } + + @Override + public Set getCriticalExtensionOIDs() { + return orig.getCriticalExtensionOIDs(); + } + + @Override + public byte[] getExtensionValue(String oid) { + return orig.getExtensionValue(oid); + } + + @Override + public Set getNonCriticalExtensionOIDs() { + return orig.getNonCriticalExtensionOIDs(); + } + + @Override + public boolean hasUnsupportedCriticalExtension() { + return orig.hasUnsupportedCriticalExtension(); + } + + @Override + public int getBasicConstraints() { + return orig.getBasicConstraints(); + } + + @Override + public Principal getIssuerDN() { + return orig.getIssuerDN(); + } + + @Override + public boolean[] getIssuerUniqueID() { + return orig.getIssuerUniqueID(); + } + + @Override + public boolean[] getKeyUsage() { + return orig.getKeyUsage(); + } + + @Override + public Date getNotAfter() { + return orig.getNotAfter(); + } + + @Override + public Date getNotBefore() { + return orig.getNotBefore(); + } + + @Override + public BigInteger getSerialNumber() { + return orig.getSerialNumber(); + } + + @Override + public String getSigAlgName() { + return orig.getSigAlgName(); + } + + @Override + public String getSigAlgOID() { + return orig.getSigAlgOID(); + } + + @Override + public byte[] getSigAlgParams() { + return orig.getSigAlgParams(); + } + + @Override + public byte[] getSignature() { + return orig.getSignature(); + } + + @Override + public Principal getSubjectDN() { + return orig.getSubjectDN(); + } + + @Override + public boolean[] getSubjectUniqueID() { + return orig.getSubjectUniqueID(); + } + + @Override + public byte[] getTBSCertificate() throws CertificateEncodingException { + return orig.getTBSCertificate(); + } + + @Override + public int getVersion() { + return orig.getVersion(); + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return orig.getEncoded(); + } + + @Override + public PublicKey getPublicKey() { + return orig.getPublicKey(); + } + + @Override + public String toString() { + return orig.toString(); + } + + @Override + public void verify(PublicKey key) throws CertificateException, InvalidKeyException, + NoSuchAlgorithmException, NoSuchProviderException, SignatureException { + orig.verify(key); + } + + @Override + public void verify(PublicKey key, String sigProvider) throws CertificateException, + InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, + SignatureException { + orig.verify(key, sigProvider); + } + + @Override + public List getExtendedKeyUsage() throws CertificateParsingException { + return orig.getExtendedKeyUsage(); + } + + @Override + public Collection> getIssuerAlternativeNames() throws CertificateParsingException { + return orig.getIssuerAlternativeNames(); + } + + @Override + public X500Principal getIssuerX500Principal() { + return orig.getIssuerX500Principal(); + } + + @Override + public Collection> getSubjectAlternativeNames() throws CertificateParsingException { + return orig.getSubjectAlternativeNames(); + } + + @Override + public X500Principal getSubjectX500Principal() { + return orig.getSubjectX500Principal(); + } + } +} diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index 3adbd686cd2c1..afcbf1b9aa3ef 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.res.Resources; import android.net.ConnectivityManager; +import android.net.HttpsTimeClient; import android.net.Network; import android.net.NetworkInfo; import android.net.SntpClient; @@ -38,6 +39,7 @@ import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -246,6 +248,10 @@ public String toString() { @Nullable private volatile URI mLastSuccessfulNtpServerUri; + @GuardedBy("mRefreshLock") + @Nullable + private URL mLastSuccessfulHttpsTimeUrl; + protected NtpTrustedTime() { } @@ -270,7 +276,7 @@ public void setServerConfigForTests(@NonNull NtpConfig ntpConfig) { /** Forces a refresh using the default network. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean forceRefresh() { + public final boolean forceRefresh() { synchronized (mRefreshLock) { Network network = getDefaultNetwork(); if (network == null) { @@ -283,7 +289,7 @@ public boolean forceRefresh() { } /** Forces a refresh using the specified network. */ - public boolean forceRefresh(@NonNull Network network) { + public final boolean forceRefresh(@NonNull Network network) { Objects.requireNonNull(network); synchronized (mRefreshLock) { @@ -301,6 +307,24 @@ private boolean forceRefreshLocked(@NonNull Network network) { return false; } + final String networkTimeMode = "https"; + + if ("https".equals(networkTimeMode)) { + var client = new HttpsTimeClient(getHttpsTimeConfig(), network); + HttpsTimeClient.Result res = client.requestTime(mLastSuccessfulHttpsTimeUrl); + if (res == null) { + return false; + } + + mTimeResult = res.timeResult; + mLastSuccessfulHttpsTimeUrl = res.url; + return true; + } + + if (!"ntp".equals(networkTimeMode)) { + throw new IllegalStateException(networkTimeMode); + } + NtpConfig ntpConfig = getNtpConfig(); if (ntpConfig == null) { // missing server config, so no NTP time available @@ -376,6 +400,10 @@ private NtpConfig getNtpConfig() { } } + private HttpsTimeClient.Config getHttpsTimeConfig() { + return HttpsTimeClient.Config.getDefault(getContext()); + } + /** * Returns the {@link NtpConfig} to use during an NTP query. This method can return {@code null} * if there is no config, or the config found is invalid. @@ -606,6 +634,8 @@ private static URI validateNtpServerUri(@NonNull URI uri) throws URISyntaxExcept /** Prints debug information. */ public void dump(PrintWriter pw) { synchronized (mConfigLock) { + pw.println("getHttpsTimeConfig()=" + getHttpsTimeConfig()); + pw.println("mLastSuccessfulHttpsTimeUrl=" + mLastSuccessfulHttpsTimeUrl); pw.println("getNtpConfig()=" + getNtpConfig()); pw.println("mNtpConfigForTests=" + mNtpConfigForTests); } @@ -748,5 +778,14 @@ private static int saturatedCast(long longValue) { } return (int) longValue; } + + @NonNull + @Override + protected Context getContext() { + return mContext; + } } + + @NonNull + protected abstract Context getContext(); } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index da64aad4212ae..5be8ed6fd2f90 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2756,6 +2756,12 @@ 5000 + + https://time.grapheneos.org/generate_204 + + + + 64800000 From 585c6c24b8d38bd9c222eeaff9db8bbfe832f3bc Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 24 Jan 2024 19:56:01 +0200 Subject: [PATCH 028/332] extend auto-time setting to forced time updates Original commit: c1d9385442ef0b620527f6e46e37bff35425b4d8 --- core/java/android/util/NtpTrustedTime.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index afcbf1b9aa3ef..cf64566ca8e8c 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -302,6 +302,13 @@ public final boolean forceRefresh(@NonNull Network network) { private boolean forceRefreshLocked(@NonNull Network network) { Objects.requireNonNull(network); + final ContentResolver resolver = getContext().getContentResolver(); + + if (Settings.Global.getInt(resolver, Settings.Global.AUTO_TIME, 0) == 0) { + Log.d(TAG, "skipped forceRefresh: auto time is disabled"); + return false; + } + if (!isNetworkConnected(network)) { if (LOGD) Log.d(TAG, "forceRefreshLocked: network=" + network + " is not connected"); return false; From b9138336ddabf77bec435989d7f1a478cc9cc77c Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Wed, 22 Feb 2023 14:40:02 -0500 Subject: [PATCH 029/332] reduce system clock drift warning --- .../android/server/timedetector/TimeDetectorStrategyImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index 6405353e1f9a9..a6a198ee8769a 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -86,7 +86,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { * actual system clock time before a warning is logged. Used to help identify situations where * there is something other than this class setting the system clock. */ - private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; + private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 250; /** * The number of suggestions to keep. These are logged in bug reports to assist when debugging From a35476f4d43942658a9f7b6e4113d3808c36c23e Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Wed, 22 Feb 2023 14:18:13 -0500 Subject: [PATCH 030/332] core/config: disable using telephony-based auto time --- core/res/res/values/config.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5be8ed6fd2f90..44c0edb0e462d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2034,7 +2034,6 @@ See com.android.server.timedetector.TimeDetectorStrategy for available sources. --> network - telephony - internet,bt,flashlight,dnd,modes_dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects + internet,bt,flashlight,dnd,modes_dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,nfc,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects From a8bf62c24616ce91e6f9bf8d695a012f9ad948be Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Mon, 14 Mar 2022 19:51:20 +0200 Subject: [PATCH 038/332] SystemUI: Enable production-ready feature flags - Conversations: enabled by default on Pixel - Charging ripple: enabled by default on Pixel Taken from https://github.com/ProtonAOSP/android_vendor_proton/commit/a32d8c4c9e731e609b16b37da191669f5c607aad --- packages/SystemUI/res/values/flags.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 07a40c86d03a5..81235b6f0553a 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -21,14 +21,14 @@ true - false + true false - false + true From 9e83bb8d812b077bbc6c4907cdae045e47b0b8c2 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Tue, 12 Oct 2021 01:03:59 +0300 Subject: [PATCH 039/332] SystemUI: Use new privacy indicators for location Android has had location indicators for a while, but let's use the new privacy indicator infrastructure for displaying them. This makes them integrate better with the new camera and microphone indicators. Change-Id: Ie457bb2dad17bddbf9dc3a818e3ec586023ce918 --- core/java/android/permission/PermissionUsageHelper.java | 2 +- .../src/com/android/systemui/privacy/PrivacyItemController.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index a698b180dbe07..dbb8f2ede7824 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -114,7 +114,7 @@ private static boolean shouldShowIndicators() { private static boolean shouldShowLocationIndicator() { return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_LOCATION_INDICATORS_ENABLED, false); + PROPERTY_LOCATION_INDICATORS_ENABLED, true); } private static long getRecentThreshold(Long now) { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index f9a7205c56f52..46276ae14a0a0 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -63,7 +63,7 @@ class PrivacyItemController @Inject constructor( val micCameraAvailable get() = privacyConfig.micCameraAvailable val locationAvailable - get() = privacyConfig.locationAvailable + get() = true val allIndicatorsAvailable get() = micCameraAvailable && locationAvailable && privacyConfig.mediaProjectionAvailable @@ -274,4 +274,4 @@ class PrivacyItemController @Inject constructor( listeningCanceller = delegate.executeDelayed({ setListeningState() }, 0L) } } -} \ No newline at end of file +} From bbb7341a301887b948123e6ce9536620290442ee Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Wed, 17 Aug 2022 12:40:13 -0400 Subject: [PATCH 040/332] SystemUI: fully enable location indicators by default --- .../src/com/android/systemui/privacy/PrivacyConfig.kt | 2 +- .../systemui/statusbar/policy/LocationControllerImpl.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt index 79a513f249954..4385035dbb875 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt @@ -47,7 +47,7 @@ class PrivacyConfig @Inject constructor( private const val MEDIA_PROJECTION = SystemUiDeviceConfigFlags.PROPERTY_MEDIA_PROJECTION_INDICATORS_ENABLED private const val DEFAULT_MIC_CAMERA = true - private const val DEFAULT_LOCATION = false + private const val DEFAULT_LOCATION = true private const val DEFAULT_MEDIA_PROJECTION = true } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index f57b696396771..2508ebc94d6ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -210,16 +210,16 @@ private boolean isUserLocationRestricted(int userId) { private boolean getAllAccessesSetting() { return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false); + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, true); } private boolean getShowSystemFlag() { return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, false); + SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, true); } private boolean getShowSystemSetting() { - return mSecureSettings.getIntForUser(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0, + return mSecureSettings.getIntForUser(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1, UserHandle.USER_CURRENT) == 1; } From 968eeb0c623c0ed7671e6789cb1c2f6b32c6e49e Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 18 Jun 2023 10:53:24 +0300 Subject: [PATCH 041/332] SystemUI: specify config_sceenshotWorkProfileFilesApp This resource is overridden with an overlay on stock OS to Google Files app. If it's empty, SystemUI crashes after screenshot of a work profile app is taken. Screenshot was still saved, crash happened when constructing post-screenshot UI. --- packages/SystemUI/res/values/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 566a276e1a110..14513965e379a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -515,7 +515,7 @@ screenshots. The icon for this app will be shown to the user when informing them that a screenshot has been saved to a different profile (e.g. work profile). If blank, a default icon will be shown. --> - + com.android.documentsui/.files.LauncherActivity - internet,bt,flashlight,dnd,modes_dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,nfc,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects + internet,bt,flashlight,dnd,modes_dnd,alarm,airplane,controls,wallet,rotation,battery,batteryShare,cast,screenrecord,mictoggle,cameratoggle,location,nfc,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index bbf56936d5608..4b268d279d3ac 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2235,6 +2235,7 @@ Battery Saver + Battery Share diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryShareTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryShareTile.java new file mode 100644 index 0000000000000..dbb8abe614460 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryShareTile.java @@ -0,0 +1,150 @@ +package com.android.systemui.qs.tiles; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.Handler; +import android.os.Looper; +import android.service.quicksettings.Tile; +import android.view.View; +import android.widget.Switch; + +import androidx.annotation.Nullable; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.Expandable; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.qs.QSTile.BooleanState; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; + +import javax.inject.Inject; +import vendor.google.wireless_charger.ReverseWirelessCharger; + +public class BatteryShareTile extends QSTileImpl { + + public static final String TILE_SPEC = "batteryShare"; + + private final Icon mIcon = ResourceIcon.get(R.drawable.ic_battery_share); + private final ReverseWirelessCharger mWirelessCharger; + private final Context mContext; + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + refreshState(); + } + }; + + @Inject + public BatteryShareTile( + QSHost host, + QsEventLogger uiEventLogger, + @Background Looper backgroundLooper, + @Main Handler mainHandler, + FalsingManager falsingManager, + MetricsLogger metricsLogger, + StatusBarStateController statusBarStateController, + ActivityStarter activityStarter, + QSLogger qsLogger + ) { + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); + mWirelessCharger = ReverseWirelessCharger.getInstance(); + mContext = host.getContext(); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + mContext.registerReceiver(mBroadcastReceiver, filter); + } + + @Override + public BooleanState newTileState() { + return new BooleanState(); + } + + @Override + protected void handleDestroy() { + mContext.unregisterReceiver(mBroadcastReceiver); + super.handleDestroy(); + } + + @Override + protected void handleUserSwitch(int newUserId) { + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.QS_BATTERY_TILE; + } + + @Override + public void handleSetListening(boolean listening) { + super.handleSetListening(listening); + } + + @Override + public boolean isAvailable() { + return isSupported(); + } + + @Override + public Intent getLongClickIntent() { + return new Intent(Intent.ACTION_POWER_USAGE_SUMMARY); + } + + @Override + protected void handleClick(@Nullable Expandable expandable) { + if (getState().state == Tile.STATE_UNAVAILABLE) { + refreshState(); + return; + } + mWirelessCharger.setRtxMode(!isReverseWirelessChargingOn()); + refreshState(); + } + + private boolean isReverseWirelessChargingOn() { + return mWirelessCharger.isRtxModeOn(); + } + + private boolean isSupported() { + return mWirelessCharger.isRtxSupported(); + } + + @Override + public CharSequence getTileLabel() { + return mContext.getString(R.string.battery_share_switch_title); + } + + private int getAvailableStatus() { + return isPlugged() || !mWirelessCharger.isRtxSupported() ? Tile.STATE_UNAVAILABLE + : isReverseWirelessChargingOn() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + } + + public boolean isPlugged() { + Intent intent = mContext.registerReceiver(null, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + state.icon = mIcon; + state.label = mContext.getString(R.string.battery_share_switch_title); + state.contentDescription = state.label; + state.expandedAccessibilityClassName = Switch.class.getName(); + state.value = isReverseWirelessChargingOn(); + state.secondaryLabel = ""; + state.state = getAvailableStatus(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index f6e0123be4460..01ea7366d35bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -27,6 +27,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.AlarmTile +import com.android.systemui.qs.tiles.BatteryShareTile import com.android.systemui.qs.tiles.CameraToggleTile import com.android.systemui.qs.tiles.DndTile import com.android.systemui.qs.tiles.FlashlightTile @@ -528,4 +529,9 @@ interface PolicyModule { @IntoMap @StringKey(UiModeNightTile.TILE_SPEC) fun bindUiModeNightTile(uiModeNightTile: UiModeNightTile): QSTileImpl<*> + + @Binds + @IntoMap + @StringKey(BatteryShareTile.TILE_SPEC) + fun bindBatteryShareTile(batteryShareTile: BatteryShareTile): QSTileImpl<*> } From 672dcdb44b774056dd20386075dee1bd7af99779 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 19 Oct 2021 08:05:17 -0400 Subject: [PATCH 043/332] SystemUI: change default quick tiles and quick tile order --- packages/SystemUI/res/values/config.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 90769f445029e..08f4fd99a9148 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -102,7 +102,7 @@ - internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.v33.SafetyCenterQsTileService) + internet,bt,airplane,flashlight,dnd,alarm,rotation,battery,screenrecord,mictoggle,cameratoggle,location,custom(com.android.permissioncontroller/.permission.service.v33.SafetyCenterQsTileService) @@ -115,7 +115,7 @@ - internet,bt,flashlight,dnd,modes_dnd,alarm,airplane,controls,wallet,rotation,battery,batteryShare,cast,screenrecord,mictoggle,cameratoggle,location,nfc,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects + internet,bt,airplane,flashlight,dnd,modes_dnd,alarm,airplane,controls,wallet,rotation,battery,batteryShare,cast,screenrecord,mictoggle,cameratoggle,location,nfc,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes,desktopeffects From 7780c23d99f47fee99681c935136bb0cb0c243af Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Fri, 23 Jun 2023 10:49:32 +0300 Subject: [PATCH 044/332] SystemUI: apply "Add users from lock screen" setting to guest users --- .../systemui/user/domain/interactor/UserSwitcherInteractor.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt index b82aefc1ac1cd..d4782d8146a22 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt @@ -805,6 +805,10 @@ constructor( settings: UserSwitcherSettingsModel, canAccessUserSwitcher: Boolean, ): Boolean { + if (!settings.isAddUsersFromLockscreen) { + return false + } + return guestUserInteractor.isGuestUserAutoCreated || UserActionsUtil.canCreateGuest( manager, From 89fbdebb5db844657aa5b39edde63088ae649205 Mon Sep 17 00:00:00 2001 From: quh4gko8 <88831734+quh4gko8@users.noreply.github.com> Date: Tue, 10 Oct 2023 07:00:23 +0000 Subject: [PATCH 045/332] SystemUI: Allow customizing lock screen shortcuts via ThemePicker --- packages/SystemUI/res/values/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 08f4fd99a9148..dd38380d18734 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -45,7 +45,7 @@ 0 - false + true false From 6f614de758297ead90319275e03174a13b96d4c8 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Thu, 17 Feb 2022 08:15:46 +0200 Subject: [PATCH 046/332] Add config to exempt telephony-related app from location indicators On some devices, there is a telephony-related app that frequently requests location, so exempting it from privacy indicators is desirable. For example, the Pixel 6 has an IMS service where this applies. Change-Id: I5e99c89367bc3ffd31794736b0d66014fdc4faae --- core/java/android/permission/PermissionManager.java | 2 +- core/res/res/values/config.xml | 4 ++++ core/res/res/values/public-final.xml | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 7df11b553c2f1..f72c1b12a3660 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -209,7 +209,7 @@ public final class PermissionManager { private static final int[] EXEMPTED_ROLES = {R.string.config_systemAmbientAudioIntelligence, R.string.config_systemUiIntelligence, R.string.config_systemAudioIntelligence, R.string.config_systemNotificationIntelligence, R.string.config_systemTextIntelligence, - R.string.config_systemVisualIntelligence}; + R.string.config_systemVisualIntelligence, R.string.config_systemTelephonyPackage}; private static final String[] INDICATOR_EXEMPTED_PACKAGES = new String[EXEMPTED_ROLES.length]; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 44c0edb0e462d..535cd4d5fd1f0 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6733,6 +6733,10 @@ + + + false diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml index 61e2a28562d2d..18894e7ecb731 100644 --- a/core/res/res/values/public-final.xml +++ b/core/res/res/values/public-final.xml @@ -3192,6 +3192,8 @@ + + From f9ed96a6031c9b90320269f26b11f76f18afc4c8 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Sun, 17 Apr 2022 14:45:50 -0400 Subject: [PATCH 047/332] exempt some system packages from status bar privacy indicator --- .../android/permission/PermissionManager.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index f72c1b12a3660..7fe97f344be69 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -180,6 +180,11 @@ public final class PermissionManager { "permission grant or revoke changed gids"; private static final String SYSTEM_PKG = "android"; + private static final String BLUETOOTH_PKG = "com.android.bluetooth"; + private static final String PHONE_SERVICES_PKG = "com.android.phone"; + private static final String PRINT_SPOOLER_PKG = "com.android.printspooler"; + private static final String FUSED_LOCATION_PKG = "com.android.location.fused"; + private static final String CELL_BROADCAST_SERVICE_PKG = "com.android.cellbroadcastservice"; /** * Refuse to install package if groups of permissions are bad @@ -1365,6 +1370,23 @@ public static Set getIndicatorExemptedPackages(@NonNull Context context) updateIndicatorExemptedPackages(context); ArraySet pkgNames = new ArraySet<>(); pkgNames.add(SYSTEM_PKG); + // Scanning for Bluetooth devices when Bluetooth is enabled is considered + // to be location access. It shouldn't be shown for the OS implementation + // of Bluetooth. + pkgNames.add(BLUETOOTH_PKG); + // Phone services (not the Dialer) accesses detailed cellular information + // which is considered to be location information. It's a base OS component + // serving the intended purpose and shouldn't trigger spurious location + // indicator notices every time it retrieves cellular information. + pkgNames.add(PHONE_SERVICES_PKG); + // Location usage indicator can get triggered when sharing a file to a printer + pkgNames.add(PRINT_SPOOLER_PKG); + pkgNames.add(FUSED_LOCATION_PKG); + // indicator pops up when determining location during a geofenced alert + pkgNames.add(CELL_BROADCAST_SERVICE_PKG); + // location indicator sometimes gets triggered when turning on Wi-Fi hotspot + pkgNames.add(android.ext.KnownSystemPackages.get(context).settings); + for (int i = 0; i < INDICATOR_EXEMPTED_PACKAGES.length; i++) { String exemptedPackage = INDICATOR_EXEMPTED_PACKAGES[i]; if (exemptedPackage != null) { From 1e1724fdbfb7c287fe19e1f70acc01d588e83ac1 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Tue, 5 Mar 2024 18:24:28 +0200 Subject: [PATCH 048/332] allow PackageInstaller to use platform APIs PackageInstaller is being made into an updatable app upstream, but it currently depends on several platform APIs on GrapheneOS. --- packages/PackageInstaller/Android.bp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp index 8b1828c5f41f1..64effff4f1b2a 100644 --- a/packages/PackageInstaller/Android.bp +++ b/packages/PackageInstaller/Android.bp @@ -43,8 +43,7 @@ android_app { certificate: "platform", privileged: true, - platform_apis: false, - sdk_version: "system_current", + platform_apis: true, rename_resources_package: false, static_libs: [ "android.content.pm.flags-aconfig-java", @@ -75,8 +74,7 @@ android_app { certificate: "platform", privileged: true, - platform_apis: false, - sdk_version: "system_current", + platform_apis: true, rename_resources_package: false, overrides: ["PackageInstaller"], From 7708c4544b682d856c56c96b898ccb2b916c6fb8 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 17 Apr 2022 17:55:17 +0300 Subject: [PATCH 049/332] PackageInstaller: link "App info" screen from the uninstallation dialog "App info" screen now has a "Disable" button, which can be used as an alternative to uninstallation. --- core/java/android/content/Intent.java | 7 +++++++ packages/PackageInstaller/res/values/strings.xml | 2 ++ .../handheld/UninstallAlertDialogFragment.java | 16 ++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index a253613e060ce..52ecc47d96799 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1903,6 +1903,13 @@ public static Intent createChooser(Intent target, CharSequence title, IntentSend public static final String EXTRA_UNINSTALL_ALL_USERS = "android.intent.extra.UNINSTALL_ALL_USERS"; + /** + * Specify whether the "More options" button should be shown in the package uninstallation UI. + * @hide + */ + public static final String EXTRA_UNINSTALL_SHOW_MORE_OPTIONS_BUTTON + = "android.intent.extra.UNINSTALL_SHOW_MORE_OPTIONS_BUTTON"; + /** * A string that associates with a metadata entry, indicating the last run version of the * platform that was setup. diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml index 324293532db4d..2197cf5ecc9e7 100644 --- a/packages/PackageInstaller/res/values/strings.xml +++ b/packages/PackageInstaller/res/values/strings.xml @@ -360,4 +360,6 @@ Close + + More options diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java index 407ab5f1729ba..4bb90b4691d2a 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java @@ -25,16 +25,20 @@ import android.app.DialogFragment; import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; +import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; import android.os.Flags; import android.os.Process; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -226,6 +230,10 @@ && hasClonedInstance(dialogInfo.appInfo.packageName)) { appDataSize = getAppDataSize(pkg, dialogInfo.allUsers ? null : dialogInfo.user); } + if (getActivity().getIntent().getBooleanExtra(Intent.EXTRA_UNINSTALL_SHOW_MORE_OPTIONS_BUTTON, true)) { + dialogBuilder.setNeutralButton(R.string.more_options_button, this); + } + if (appDataSize == 0) { dialogBuilder.setMessage(messageBuilder.toString()); } else { @@ -291,6 +299,14 @@ public void onClick(DialogInterface dialog, int which) { mKeepData != null && mKeepData.isChecked(), mIsClonedApp); } else { ((UninstallerActivity) getActivity()).dispatchAborted(); + + if (which == Dialog.BUTTON_NEUTRAL) { + UninstallerActivity.DialogInfo dialogInfo = ((UninstallerActivity) getActivity()).getDialogInfo(); + var i = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + i.setData(Uri.fromParts("package", dialogInfo.appInfo.packageName, null)); + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + getActivity().startActivityAsUser(i, dialogInfo.user); + } } } From e9444a36e4c510d37aadc113479ad9c7364ec420 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sat, 19 Oct 2024 21:01:55 +0300 Subject: [PATCH 050/332] PackageInstaller: fix crash when showing app's first confirmation prompt --- .../com/android/packageinstaller/PackageInstallerActivity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index 824dd4a5fdaf7..fc6f3ec81a248 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -265,6 +265,7 @@ public void onActivityResult(int request, int result, Intent data) { if (currentDialog != null) { currentDialog.dismissAllowingStateLoss(); } + bindUi(); initiateInstall(); } else { finish(); From ac15ad5b840b391e29bb41823f8af644b85a274b Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Fri, 22 Dec 2023 11:44:00 +0200 Subject: [PATCH 051/332] disable support for pre-approving PackageInstaller sessions APK is not yet available at the time of pre-approval request, which is incompatible with the "Allow Network permission" checkbox in the confirmation dialog. See PackageInstaller.Session#requestUserPreapproval() for more info. --- core/res/res/values/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 535cd4d5fd1f0..9021e45ab0b3f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -7240,7 +7240,7 @@ the underlying display device changes. --> false - true + false From c9c6079b3a0440466562974e0907b459905a0640 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sat, 6 Jan 2024 19:42:35 +0200 Subject: [PATCH 052/332] define READ_LOGS_FULL permission for promptless logcat access It's used by the LogViewer app. --- core/res/AndroidManifest.xml | 5 +++++ .../android/server/logcat/LogcatManagerService.java | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b8f0ccdf8c9a3..5a926ac572e47 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5180,6 +5180,11 @@ + + + + diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java index fee54f5ed3375..09614925e1d4c 100644 --- a/services/core/java/com/android/server/logcat/LogcatManagerService.java +++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java @@ -426,7 +426,19 @@ private boolean shouldShowConfirmationDialog(LogAccessClient client) { return procState == ActivityManager.PROCESS_STATE_TOP; } + private boolean isPrivilegedClient(LogAccessClient client) { + PackageManager pm = mContext.getPackageManager(); + String perm = android.Manifest.permission.READ_LOGS_FULL; + return pm.checkPermission(perm, client.mPackageName) == PackageManager.PERMISSION_GRANTED; + } + private void processNewLogAccessRequest(LogAccessClient client) { + if (isPrivilegedClient(client)) { + onAccessApprovedForClient(client); + Slog.d(TAG, "approved LogAccessRequest for privileged client: " + client.mPackageName); + return; + } + boolean isInstrumented = mActivityManagerInternal.getInstrumentationSourceUid(client.mUid) != android.os.Process.INVALID_UID; // The instrumented apks only run for testing, so we don't check user permission. From 9e466461a9139117fec088f541c9f8c5d6ff77b8 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sat, 6 Jan 2024 19:45:07 +0200 Subject: [PATCH 053/332] add APIs for launching LogViewer app --- core/java/android/ext/LogViewerApp.java | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 core/java/android/ext/LogViewerApp.java diff --git a/core/java/android/ext/LogViewerApp.java b/core/java/android/ext/LogViewerApp.java new file mode 100644 index 0000000000000..0eb6239bc279b --- /dev/null +++ b/core/java/android/ext/LogViewerApp.java @@ -0,0 +1,67 @@ +package android.ext; + +import android.content.Intent; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.UUID; +import java.util.zip.GZIPOutputStream; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** @hide */ +public class LogViewerApp { + private static final String PACKAGE_NAME = "app.grapheneos.logviewer"; + + public static final String ACTION_ERROR_REPORT = PACKAGE_NAME + ".ERROR_REPORT"; + public static final String ACTION_LOGCAT = PACKAGE_NAME + ".LOGCAT"; + public static final String ACTION_PKG_LOGCAT = PACKAGE_NAME + ".PKG_LOGCAT"; + + public static final String EXTRA_ERROR_TYPE = "type"; + public static final String EXTRA_GZIPPED_MESSAGE = "gzipped_msg"; + public static final String EXTRA_SOURCE_APP_INFO = "source_app_info"; + public static final String EXTRA_SHOW_REPORT_BUTTON = "show_report_button"; + public static final String EXTRA_TEXT_TOMBSTONE_FILE_PATH = "text_tombstone_file_path"; + // Tombstone file path can be reused by a subsequent tombstone file, last modified timestamp is + // used to detect that case. + public static final String EXTRA_TEXT_TOMBSTONE_LAST_MODIFIED_TIME = "text_tombstone_last_modified"; + public static final String EXTRA_PREFER_TEXT_TOMBSTONE = "prefer_text_tombstone"; + + public static String getPackageName() { + return PACKAGE_NAME; + } + + public static Intent createBaseErrorReportIntent(String msg) { + var i = new Intent(ACTION_ERROR_REPORT); + i.putExtra(EXTRA_GZIPPED_MESSAGE, gzipString(msg)); + // this is needed for correct usage via PendingIntent + i.setIdentifier(UUID.randomUUID().toString()); + i.setPackage(PACKAGE_NAME); + return i; + } + + public static Intent getPackageLogcatIntent(String pkgName) { + var i = new Intent(ACTION_PKG_LOGCAT); + i.putExtra(Intent.EXTRA_PACKAGE_NAME, pkgName); + i.setPackage(PACKAGE_NAME); + return i; + } + + public static Intent getLogcatIntent() { + var i = new Intent(ACTION_LOGCAT); + i.setPackage(PACKAGE_NAME); + return i; + } + + private static byte[] gzipString(String msg) { + var bos = new ByteArrayOutputStream(msg.length() / 4); + + try (var s = new GZIPOutputStream(bos)) { + s.write(msg.getBytes(UTF_8)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + return bos.toByteArray(); + } +} From 9aea7cbb180272d3aa4beedefaa1e921762328c9 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 31 Jul 2022 10:06:14 +0300 Subject: [PATCH 054/332] add helpers for system_server extensions --- .../SystemNotificationChannels.java | 15 +++ .../server/ext/DelayedConditionalAction.java | 122 ++++++++++++++++++ .../android/server/ext/IntentReceiver.java | 103 +++++++++++++++ .../java/com/android/server/ext/SseUtils.java | 23 ++++ .../android/server/ext/SystemServerExt.java | 37 ++++++ .../java/com/android/server/SystemServer.java | 2 + 6 files changed, 302 insertions(+) create mode 100644 services/core/java/com/android/server/ext/DelayedConditionalAction.java create mode 100644 services/core/java/com/android/server/ext/IntentReceiver.java create mode 100644 services/core/java/com/android/server/ext/SseUtils.java create mode 100644 services/core/java/com/android/server/ext/SystemServerExt.java diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index 972c2ea403e07..8fecdc5b52e44 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -230,6 +230,8 @@ public static void createAll(Context context) { NotificationManager.IMPORTANCE_LOW); channelsList.add(abusiveBackgroundAppsChannel); + extraChannels(context, channelsList); + nm.createNotificationChannels(channelsList); // Delete channels created by previous Android versions that are no longer used. @@ -268,4 +270,17 @@ private static NotificationChannel newAccountChannel(Context context) { } private SystemNotificationChannels() {} + + private static void extraChannels(Context ctx, List dest) { + } + + private static NotificationChannel channel(Context ctx, String id, int nameRes, int importance, boolean silent, List dest) { + var c = new NotificationChannel(id, ctx.getText(nameRes), importance); + if (silent) { + c.setSound(null, null); + c.enableVibration(false); + } + dest.add(c); + return c; + } } diff --git a/services/core/java/com/android/server/ext/DelayedConditionalAction.java b/services/core/java/com/android/server/ext/DelayedConditionalAction.java new file mode 100644 index 0000000000000..2513ddffc4b28 --- /dev/null +++ b/services/core/java/com/android/server/ext/DelayedConditionalAction.java @@ -0,0 +1,122 @@ +package com.android.server.ext; + +import android.annotation.IntRange; +import android.app.AlarmManager; +import android.ext.settings.IntSetting; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.util.Slog; + +/** + * Infrastructure for actions that: + * - happen after a user-configurable device-wide delay + * - need to be taken even when the device is in deep sleep + * - need to be rescheduled based on some listenable event + */ +public abstract class DelayedConditionalAction { + protected final SystemServerExt sse; + protected final Thread thread; + protected final Handler handler; + + protected final IntSetting setting; + + protected final AlarmManager alarmManager; + private final AlarmManager.OnAlarmListener alarmListener; + + protected DelayedConditionalAction(SystemServerExt sse, IntSetting setting, Handler handler) { + this.sse = sse; + this.setting = setting; + + Looper looper = handler.getLooper(); + thread = looper.getThread(); + this.handler = handler; + + if (thread != Thread.currentThread()) { + throw new IllegalStateException("all calls should happen on the same thread"); + } + + alarmManager = sse.context.getSystemService(AlarmManager.class); + alarmListener = () -> { + String TAG = getLogTag(); + Slog.d(TAG, "alarm triggered"); + + if (getDelayDurationMillis() == 0) { + Slog.d(TAG, "alarm has been disabled, returning"); + return; + } + + alarmTriggered(); + }; + } + + public void init() { + registerStateListener(); + + if (setting.canObserveState()) { + setting.registerObserver(sse.context, handler, s -> update()); + } + + update(); + } + + private boolean alarmScheduled; + + protected final void update() { + final String TAG = getLogTag(); + final Thread curThread = Thread.currentThread(); + + if (curThread != thread) { + String msg = "update() called on an unknown thread " + curThread; + throw new IllegalStateException(msg); + } + + Slog.d(TAG, "update: alarm already scheduled: " + alarmScheduled); + + if (alarmScheduled) { + alarmManager.cancel(alarmListener); + Slog.d(TAG, "canceled previous alarm"); + alarmScheduled = false; + } + + boolean shouldScheduleAlarm = shouldScheduleAlarm(); + Slog.d(TAG, "shouldScheduleAlarm: " + shouldScheduleAlarm); + if (!shouldScheduleAlarm) { + return; + } + + long delayMillis = getDelayDurationMillis(); + Slog.d(TAG, "delayMillis: " + delayMillis); + + if (delayMillis == 0) { + return; + } + + long current = SystemClock.elapsedRealtime(); + + if (Long.MAX_VALUE - delayMillis < current) { + return; + } + + final long triggerAt = current + delayMillis; + + alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAt, + getClass().getName(), alarmListener, handler); + Slog.d(TAG, "scheduled alarm"); + alarmScheduled = true; + } + + @IntRange(from = 0) + private long getDelayDurationMillis() { + return Math.max(0, setting.get(sse.context)); + } + + // Make sure to use the same Handler that is used for all other callbacks; + // call update() to reschedule / cancel the alarm + protected abstract void registerStateListener(); + + protected abstract boolean shouldScheduleAlarm(); + protected abstract void alarmTriggered(); + + protected abstract String getLogTag(); +} diff --git a/services/core/java/com/android/server/ext/IntentReceiver.java b/services/core/java/com/android/server/ext/IntentReceiver.java new file mode 100644 index 0000000000000..f2572b15bb32d --- /dev/null +++ b/services/core/java/com/android/server/ext/IntentReceiver.java @@ -0,0 +1,103 @@ +package com.android.server.ext; + +import android.annotation.Nullable; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.Handler; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.os.BackgroundThread; + +import java.util.function.Supplier; + +// Note that instances of IntentReceiver subclasses are: +// - singletons +// - registered lazily +// - never unregistered +// - not automatically re-registered after process restart (doesn't matter in system_server) +public abstract class IntentReceiver extends BroadcastReceiver { + protected final String TAG = getClass().getSimpleName(); + + private static final ArrayMap map = new ArrayMap<>(); + + public static IntentReceiver getInstance( + Class cls, Supplier supplier, Context ctx) { + synchronized (map) { + IntentReceiver instance = map.get(cls); + if (instance == null) { + instance = supplier.get(); + instance.context = ctx; + var filter = new IntentFilter(instance.getIntentAction()); + ctx.registerReceiver(instance, filter, null, + instance.getScheduler(), Context.RECEIVER_NOT_EXPORTED); + map.put(cls, instance); + } + return instance; + } + } + + private Context context; + + private PendingIntent basePendingIntent; + private long prevId; + + private String getIntentAction() { + return getClass().getName(); + } + + private Intent getBaseIntent() { + var i = new Intent(getIntentAction()); + i.setPackage(context.getPackageName()); + return i; + } + + public static PendingIntent getPendingIntent( + Class cls, Supplier supplier, @Nullable Bundle args, Context ctx) { + return getInstance(cls, supplier, ctx).getPendingIntent(args); + } + + public PendingIntent getPendingIntent(@Nullable Bundle args) { + if (args == null) { + synchronized (this) { + PendingIntent base = basePendingIntent; + if (base == null) { + base = PendingIntent.getBroadcast(context, 0, getBaseIntent(), PendingIntent.FLAG_IMMUTABLE); + basePendingIntent = base; + } + return base; + } + } else { + long id; + synchronized (this) { + id = prevId++; + } + + var i = getBaseIntent(); + i.setIdentifier(Long.toString(id)); + i.replaceExtras(args); + return PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_IMMUTABLE); + } + } + + @Override + public final void onReceive(Context context, Intent intent) { + Slog.d(TAG, "onReceive: " + intent); + String idStr = intent.getIdentifier(); + Bundle args = Bundle.EMPTY; + if (idStr != null) { + args = intent.getExtras(); + } + onReceive(context, args); + } + + public Handler getScheduler() { + return BackgroundThread.getHandler(); + } + + public abstract void onReceive(Context ctx, Bundle args); +} diff --git a/services/core/java/com/android/server/ext/SseUtils.java b/services/core/java/com/android/server/ext/SseUtils.java new file mode 100644 index 0000000000000..a7a47e817fe58 --- /dev/null +++ b/services/core/java/com/android/server/ext/SseUtils.java @@ -0,0 +1,23 @@ +package com.android.server.ext; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +public class SseUtils { + + public static Notification.Action notifAction(Context ctx, Intent broadcastIntent, int textRes) { + return notifActionBuilder(ctx, broadcastIntent, textRes).build(); + } + + public static Notification.Action.Builder notifActionBuilder(Context ctx, Intent broadcastIntent, int textRes) { + var pi = PendingIntent.getBroadcast(ctx, 0, broadcastIntent, PendingIntent.FLAG_IMMUTABLE); + return new Notification.Action.Builder(null, ctx.getText(textRes), pi); + } + + public static void addNotifAction(Context ctx, PendingIntent intent, int textRes, Notification.Builder dst) { + var nb = new Notification.Action.Builder(null, ctx.getText(textRes), intent); + dst.addAction(nb.build()); + } +} diff --git a/services/core/java/com/android/server/ext/SystemServerExt.java b/services/core/java/com/android/server/ext/SystemServerExt.java new file mode 100644 index 0000000000000..6969e8773928c --- /dev/null +++ b/services/core/java/com/android/server/ext/SystemServerExt.java @@ -0,0 +1,37 @@ +package com.android.server.ext; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Handler; + +import com.android.internal.os.BackgroundThread; +import com.android.server.pm.PackageManagerService; + +public final class SystemServerExt { + + public final Context context; + public final Handler bgHandler; + public final PackageManagerService packageManager; + + private SystemServerExt(Context systemContext, PackageManagerService pm) { + context = systemContext; + bgHandler = BackgroundThread.getHandler(); + packageManager = pm; + } + + /* + Called after system server has completed its initialization, + but before any of the apps are started. + + Call from com.android.server.SystemServer#startOtherServices(), at the end of lambda + that is passed into mActivityManagerService.systemReady() + */ + public static void init(Context systemContext, PackageManagerService pm) { + SystemServerExt sse = new SystemServerExt(systemContext, pm); + sse.bgHandler.post(sse::initBgThread); + } + + void initBgThread() { + + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 56ec27a0ba875..b7faeffd09b51 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -3478,6 +3478,8 @@ private void startOtherServices(@NonNull TimingsTraceAndSlog t) { reportWtf("Triggering OdsignStatsLogger", e); } t.traceEnd(); + + com.android.server.ext.SystemServerExt.init(mSystemContext, mPackageManagerService); }, t); t.traceBegin("LockSettingsThirdPartyAppsStarted"); From a0b018811deed2363d7b9217d8ee2709714ad85f Mon Sep 17 00:00:00 2001 From: flawedworld Date: Wed, 15 Jun 2022 23:42:47 +0300 Subject: [PATCH 055/332] Warn the user if they are using a prototype Pixel device Consumer Pixel devices have "MP1.0" as their hardware revision usually. Devices stolen from Google will have "EVT", "PVT" or "DVT" set in "ro.revision" by the bootloader. Additionally, check the secure boot state prop set by the bootloader, "ro.boot.secure_boot", to ensure it is set to "1", as typically pre-production devices won't have a blown secure boot efuse, completely destroying any concept of verified boot. Check both props, if either check fails, notify the user. Squashed with: 778380bcc524954ed1b71dda8c762c458dfec087 Co-authored-by: Dmitry Muhomor --- core/res/res/values/strings.xml | 5 ++ core/res/res/values/symbols.xml | 2 + proto/src/system_messages.proto | 4 ++ .../server/am/ActivityManagerService.java | 58 +++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 43ba327f40691..0387d5c5fb84e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4021,6 +4021,11 @@ Performance and stability might be impacted. Reboot to disable. If enabled using arm64.memtag.bootctl, set it to "none" beforehand. + + Device is an engineering prototype + + Device security may be affected. + Liquid or debris in USB port diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c8385348ef098..92ac8f8f18b3f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2213,6 +2213,8 @@ + + diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 3a38152825c96..fbdafe9e4fec0 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -318,6 +318,10 @@ message SystemMessage { // Package: android NOTE_WRONG_HSUM_STATUS = 77; + // Inform the user that the device appears to be a prototype. + // Package: android + NOTE_PROTOTYPE_DETECTED = 400; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index df71db91b7ede..f908514993957 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5264,6 +5264,8 @@ public void performReceive(Intent intent, int resultCode, // UART is on if init's console service is running, send a warning notification. showConsoleNotificationIfActive(); showMteOverrideNotificationIfActive(); + // If device is not a mass production (MP) variant, send a warning notification. + showPrototypeNotificationIfPrototype(); t.traceEnd(); } @@ -5287,6 +5289,62 @@ private static boolean isUartEnabled() { return isEnabled; } + private static boolean isPrototypeDevice() { + switch (Build.BRAND) { + case "google": + break; + default: + // Other OEMs may deal with hardware revisions differently + return false; + } + + if (!SystemProperties.get("ro.revision").contains("MP")) { + // MP1.0 is what all 3rd generation and newer Pixel devices have as their mass + // production revision. + // Account for future mass production device revisions by only checking for "MP". + return true; + } + + if (!SystemProperties.get("ro.boot.secure_boot").equals("PRODUCTION")) { + // All devices with a blown secure boot fuse will have the bootloader report + // ro.boot.secure_boot as being in PRODUCTION mode. + // Engineering devices typically don't have a blown secure boot fuse as they are used + // for firmware development. + return true; + } + + return false; + } + + private void showPrototypeNotificationIfPrototype() { + if (!isPrototypeDevice()) { + return; + } + String title = mContext + .getString(com.android.internal.R.string.prototype_device_notification_title); + String message = mContext + .getString(com.android.internal.R.string.prototype_device_notification_message); + Notification notification = + new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER) + .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) + .setWhen(0) + .setOngoing(true) + .setTicker(title) + .setDefaults(0) // please be quiet + .setColor(mContext.getColor( + com.android.internal.R.color + .system_notification_accent_color)) + .setContentTitle(title) + .setContentText(message) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .build(); + + NotificationManager notificationManager = + mContext.getSystemService(NotificationManager.class); + notificationManager.notifyAsUser( + null, SystemMessage.NOTE_PROTOTYPE_DETECTED, notification, UserHandle.ALL); + } + private void showConsoleNotificationIfActive() { if (!isUartEnabled()) { return; From 2484567b7c5998f00990c912cca9f26441dfd486 Mon Sep 17 00:00:00 2001 From: June Date: Sat, 19 Mar 2022 17:10:28 +0200 Subject: [PATCH 056/332] Support forwarding notifications from other users Co-authored-by: inthewaves Co-authored-by: June Co-authored-by: quh4gko8 <88831734+quh4gko8@users.noreply.github.com> Co-authored-by: Pratyush --- core/java/android/provider/Settings.java | 12 + .../SystemNotificationChannels.java | 8 + core/res/AndroidManifest.xml | 1 + core/res/res/values/strings.xml | 5 + core/res/res/values/symbols.xml | 6 + .../NotificationAttentionHelper.java | 2 +- .../NotificationManagerService.java | 404 ++++++++++++++++++ .../server/notification/ZenModeHelper.java | 127 ++++++ 8 files changed, 564 insertions(+), 1 deletion(-) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1297be8094dc9..1e73792e35564 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11111,6 +11111,18 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val public static final String LOCK_SCREEN_NOTIFICATION_MINIMALISM = "lock_screen_notification_minimalism"; + /** + * Indicates whether the notifications for one user should be sent to the + * current user in censored form (app name, name of the user, time received are shown). + *

+ * Type: int (0 for false, 1 for true) + * + * @hide + */ + @Protected(readWrite = KnownSystemPackage.SETTINGS) + public static final String SEND_CENSORED_NOTIFICATIONS_TO_CURRENT_USER = + "send_censored_notifications_to_current_user"; + /** * Indicates whether snooze options should be shown on notifications *

diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index 8fecdc5b52e44..c93e0d592c5b1 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -67,6 +67,7 @@ public class SystemNotificationChannels { */ @Deprecated public static final String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES"; public static final String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS"; + public static final String OTHER_USERS = "OTHER_USERS"; public static final String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION"; public static final String ACCESSIBILITY_HEARING_DEVICE = "ACCESSIBILITY_HEARING_DEVICE"; public static final String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY"; @@ -204,6 +205,13 @@ public static void createAll(Context context) { .build()); channelsList.add(systemChanges); + NotificationChannel otherUsers = new NotificationChannel(OTHER_USERS, + context.getString(R.string.notification_channel_other_users), + NotificationManager.IMPORTANCE_DEFAULT); + otherUsers.setDescription(context.getString(R.string.notification_channel_other_users_description)); + otherUsers.setBlockable(true); + channelsList.add(otherUsers); + final NotificationChannel newFeaturePrompt = new NotificationChannel( ACCESSIBILITY_MAGNIFICATION, context.getString(R.string.notification_channel_accessibility_magnification), diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5a926ac572e47..9be58ea71d847 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -592,6 +592,7 @@ + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 0387d5c5fb84e..cf16f8b27b9e5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6878,4 +6878,9 @@ ul. Tap to review tips to improve your unlocking experience + + Other users + Censored notifications from the lock screens of other users + Notification from %1$s for %2$s + Switch to %1$s diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 92ac8f8f18b3f..298406fd5dae4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5415,6 +5415,12 @@ + + + + + + diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index 0e390b69f174d..d43fd05ec2069 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -1027,7 +1027,7 @@ boolean canShowLightsLocked(final NotificationRecord record, final Signals signa return true; } - private String disableNotificationEffects(NotificationRecord record, int listenerHints) { + String disableNotificationEffects(NotificationRecord record, int listenerHints) { if (mDisableNotificationEffects) { return "booleanState"; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ea1b69cc41e63..ec4bf57928498 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -547,6 +547,10 @@ public class NotificationManagerService extends SystemService { private static final String SCHEME_TIMEOUT = "timeout"; private static final String EXTRA_KEY = "key"; + private static final String ACTION_SWITCH_USER = + NotificationManagerService.class.getSimpleName() + ".SWITCH_USER"; + private static final String EXTRA_SWITCH_USER_USERID = "userid"; + private static final int NOTIFICATION_INSTANCE_ID_MAX = (1 << 13); // States for the review permissions notification @@ -2351,6 +2355,12 @@ public void onReceive(Context context, Intent intent) { mHistoryManager.onUserRemoved(userId); mPreferencesHelper.syncHasPriorityChannels(); handleSavePolicyFile(); + + // Clear censored notifications in case the removed user forwarded any. + final int currentUserId = ActivityManager.getCurrentUser(); + cancelAllNotificationsInt(MY_UID, MY_PID, getContext().getPackageName(), + SystemNotificationChannels.OTHER_USERS, 0, 0, currentUserId, + REASON_USER_STOPPED); } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); @@ -2359,6 +2369,15 @@ public void onReceive(Context context, Intent intent) { mConditionProviders.onUserUnlocked(userId); mListeners.onUserUnlocked(userId); } + } else if (action.equals(Intent.ACTION_USER_BACKGROUND)) { + // This is the user/profile that is going into the background. + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId >= 0) { + // Clear censored notifications on switch. + cancelAllNotificationsInt(MY_UID, MY_PID, getContext().getPackageName(), + SystemNotificationChannels.OTHER_USERS, 0, 0, userId, + REASON_APP_CANCEL_ALL); + } } } @@ -2369,6 +2388,27 @@ private boolean isProfileUnavailable(String action) { } }; + private final BroadcastReceiver mSwitchUserReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (!ACTION_SWITCH_USER.equals(intent.getAction())) { + return; + } + + final boolean canSwitch = mUm.isUserSwitcherEnabled() + && mUm.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK; + + final int userIdToSwitchTo = intent.getIntExtra(EXTRA_SWITCH_USER_USERID, -1); + if (userIdToSwitchTo >= 0 && canSwitch) { + try { + ActivityManager.getService().switchUser(userIdToSwitchTo); + } catch (RemoteException re) { + // Do nothing + } + } + } + }; + private final class SettingsObserver extends ContentObserver { private final Uri NOTIFICATION_BADGING_URI = Secure.getUriFor(Secure.NOTIFICATION_BADGING); @@ -2867,6 +2907,7 @@ void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_UNLOCKED); + filter.addAction(Intent.ACTION_USER_BACKGROUND); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); if (privateSpaceFlagsEnabled()){ filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE); @@ -2921,6 +2962,9 @@ public void onOpChanged(@NonNull String op, @NonNull String packageName, }; mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null, mAppOpsListener); + + IntentFilter switchUserFilter = new IntentFilter(ACTION_SWITCH_USER); + getContext().registerReceiver(mSwitchUserReceiver, switchUserFilter); } /** @@ -9927,6 +9971,18 @@ private boolean postNotification() { } } } + + // Now that the notification is posted, we can now consider sending a + // censored copy of it to the foreground user (if the foreground user + // differs from the intended recipient). + final CensoredSendState state = getCensoredSendStateForNotification(r); + if (state != CensoredSendState.DONT_SEND) { + // Give the information directly so that we can release + // mNotificationLock. + mHandler.post(new EnqueueCensoredNotificationRunnable( + r.getSbn().getPackageName(), r.getUser().getIdentifier(), + r.getSbn().getId(), r.getSbn().getTag(), state)); + } } else { Slog.e(TAG, "Not posting notification without small icon: " + notification); if (old != null && !old.isCanceled) { @@ -9998,6 +10054,354 @@ InstanceId getGroupInstanceId(String groupKey) { return group.getSbn().getInstanceId(); } + enum CensoredSendState { + DONT_SEND, SEND_NORMAL, SEND_QUIET + } + + @GuardedBy("mNotificationLock") + @NonNull + private CensoredSendState getCensoredSendStateForNotification(NotificationRecord record) { + final int userId = record.getUser().getIdentifier(); + // This should cover not sending if it's meant for the current user or a work profile, + // since mUserProfiles updates its cache using UserManager#getProfiles which "Returns list + // of the profiles of userId including userId itself." + if (userId == UserHandle.USER_ALL || mUserProfiles.isCurrentProfile(userId)) { + if (DBG) Slog.d(TAG, "not sending censored notif: current user or a profile"); + return CensoredSendState.DONT_SEND; + } + + if (userId == ActivityManager.getCurrentUser()) { + if (DBG) Slog.d(TAG, "not sending censored notif: notification is coming from (upcoming) foreground user"); + return CensoredSendState.DONT_SEND; + } + + // Sending user has to opt in under Multiple users in Settings. + final boolean userEnabledCensoredSending = Settings.Secure.getIntForUser( + getContext().getContentResolver(), + Settings.Secure.SEND_CENSORED_NOTIFICATIONS_TO_CURRENT_USER, 0, userId) != 0; + if (!userEnabledCensoredSending) { + if (DBG) Slog.d(TAG, "not sending censored notif due to sender setting off"); + return CensoredSendState.DONT_SEND; + } + + // Work profiles already can show their notification to their owner. Also, since these + // notifications have switch user actions, do not show them if the switcher is disabled. + if (!mUm.isUserSwitcherEnabled()) { + if (DBG) Slog.d(TAG, "not sending censored notif since switcher isn't enabled"); + return CensoredSendState.DONT_SEND; + } + + if (record.isHidden()) { + if (DBG) Slog.d(TAG, "not sending censored notif due to hidden"); + return CensoredSendState.DONT_SEND; + } + + // Handles cases where the notification being sent is a censored notification itself. + if (SystemNotificationChannels.OTHER_USERS.equals(record.getChannel().getId())) { + if (DBG) Slog.d(TAG, "not sending censored notif due to original being " + + "censored notification itself"); + return CensoredSendState.DONT_SEND; + } + + // Handles reoccurring update notifications (fixes issues like status update spamming). + if (record.isUpdate && (record.getNotification().flags & FLAG_ONLY_ALERT_ONCE) != 0) { + if (DBG) Slog.d(TAG, "not sending censored notif due to original being " + + "an update that only alerts once"); + return CensoredSendState.DONT_SEND; + } + + // Muted by listener + final String disableEffects = mAttentionHelper.disableNotificationEffects(record, mListenerHints); + if (disableEffects != null) { + if (DBG) Slog.d(TAG, "not sending censored notif due to disableEffects"); + return CensoredSendState.DONT_SEND; + } + + // Suppressed because another notification in its group handles alerting + if (record.getSbn().isGroup()) { + if (record.getNotification().suppressAlertingDueToGrouping()) { + if (DBG) Slog.d(TAG, "not sending censored notif due another" + + "notification in its group handles alerting"); + return CensoredSendState.DONT_SEND; + } + } + + // Check lock screen display settings. + if (!shouldShowNotificationOnKeyguardForUser(userId, record)) { + if (DBG) Slog.d(TAG, "not sending censored notif due lock screen settings"); + return CensoredSendState.DONT_SEND; + } + + // Check do not disturb lock screen state. + // We can't use record.isIntercepted(). That setting is based on the foreground user. + if (DBG) Slog.d(TAG, "processing DND state"); + final CensoredSendState dndState = + mZenModeHelper.getCensoredSendStateFromUserDndOnVisuals(record, userId); + switch (dndState) { + case SEND_QUIET: + if (DBG) Slog.d(TAG, "dndState is SEND_QUIET"); + return CensoredSendState.SEND_QUIET; + case SEND_NORMAL: + if (DBG) Slog.d(TAG, "dndState is SEND_NORMAL"); + if (record.getChannel().getImportance() == IMPORTANCE_LOW + || !record.isInterruptive()) { + if (DBG) Slog.d(TAG, "using SEND_QUIET"); + return CensoredSendState.SEND_QUIET; + } + return CensoredSendState.SEND_NORMAL; + case DONT_SEND: // fall through + default: + if (DBG) Slog.d(TAG, "using DONT_SEND due to dndstate"); + return CensoredSendState.DONT_SEND; + } + } + + /** + * Determines if the notification should show up on the lock screen for the user. + * For use in determining if a notification should be forwarded to the foreground user in + * censored form. + * + * For similar logic, see + * {@link com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl#shouldHideNotifications(int)} + * {@link com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl#shouldShowOnKeyguard(NotificationEntry)} + * These methods aren't static methods and they rely on a lot of their internal functions, + * so we have to extract the logic into here. + */ + @GuardedBy("mNotificationLock") + private boolean shouldShowNotificationOnKeyguardForUser(int userId, NotificationRecord record) { + final NotificationChannel channel = record.getChannel(); + // Guard against notifications channels hiding from lock screen, silent notifications + // that are minimized (ambient notifications), and no-importance notifications. + if (channel.getLockscreenVisibility() == Notification.VISIBILITY_SECRET + || channel.getImportance() == IMPORTANCE_MIN + || channel.getImportance() == IMPORTANCE_NONE) { + if (DBG) { + Slog.d(TAG, "shouldShowNotificationOnKeyguardForUser: " + + "channel.getLockscreenVisibility() == Notification.VISIBILITY_SECRET: " + + (channel.getLockscreenVisibility() == Notification.VISIBILITY_SECRET)); + Slog.d(TAG, " channel.getImportance() == IMPORTANCE_MIN: " + + (channel.getImportance() == IMPORTANCE_MIN)); + Slog.d(TAG, " channel.getImportance() == IMPORTANCE_NONE: " + + (channel.getImportance() == IMPORTANCE_NONE)); + } + return false; + } + + final boolean showByUser = Settings.Secure.getIntForUser(getContext().getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userId) != 0; + if (!showByUser) { + if (DBG) Slog.d(TAG, "shouldShowNotificationOnKeyguardForUser: " + + "LOCK_SCREEN_SHOW_NOTIFICATIONS is false"); + return false; + } + + // Handles lockdown button. + final int strongAuthFlags = new LockPatternUtils(getContext()).getStrongAuthForUser(userId); + if ((strongAuthFlags & LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN) != 0) { + if (DBG) Slog.d(TAG, "shouldShowNotificationOnKeyguardForUser: user in lockdown"); + return false; + } + + if (channel.getImportance() == IMPORTANCE_LOW) { + final boolean isLockScreenShowingSilent = + Settings.Secure.getIntForUser(getContext().getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1, userId) != 0; + if (DBG) Slog.d(TAG, "shouldShowNotificationOnKeyguardForUser: IMPORTANCE_LOW:" + + "are silent notifications shown? " + isLockScreenShowingSilent); + return isLockScreenShowingSilent; + } + + return true; + } + + /** + *

Constructs a censored notification that will be enqueued to be forwarded to the foreground + * user. This Runnable should be posted to a Handler after + * {@link #getCensoredSendStateForNotification} has been run, as this does not enforce any + * checks.

+ * + *
    + *
  • The system sends the notification.
  • + *
  • Only the app's name, the intended user, and time of notification are shown.
  • + *
  • Every user has to opt in to having their notifications forwarded when they are active + * in the background.
  • + *
  • A censored notification comes with an action to switch to the intended user.
  • + *
  • The censored notifications are grouped by user.
  • + *
  • The censored notifications respect the lock screen visibility and the do not disturb + * settings of the original recipient user, but the Runnable does not enforce it.
  • + *
  • The censored notifications will be quiet for the current user if the original + * notification is quiet. This includes silent channels and do not disturb muting.
  • + *
  • The censored notifications are automatically cancelled whenever a user switch occurs + * (i.e. when a broadcast with Intent.ACTION_USER_BACKGROUND is sent) and whenever users + * are removed.
  • + *
+ */ + private class EnqueueCensoredNotificationRunnable implements Runnable { + private final String pkg; + private final int originalUserId; + private final int notificationId; + private final CensoredSendState censoredSendState; + private final String originalTag; + + // these are derived from the original information + private final String notificationGroupKey; + private final int notificationSummaryId; + + /** + * + * @param pkg Package of the app that sent the notification, used to get the name of the app + * @param originalUserId The original recipient of the to-be-censored notification. + * @param notificationId The original notification id of the to-be-censored notification. + * @param tag The original tag of the to-be-censored notification. + * @param state The CensoredSendState as computed by + * {@link #getCensoredSendStateForNotification}. + */ + EnqueueCensoredNotificationRunnable(String pkg, int originalUserId, int notificationId, + String tag, @NonNull CensoredSendState state) { + this.pkg = pkg; + this.originalUserId = originalUserId; + this.censoredSendState = state; + originalTag = tag; + + // Group the censored notifications by user that sent them. + notificationGroupKey = createCensoredNotificationGroupKey(originalUserId); + notificationSummaryId = createCensoredSummaryId(originalUserId); + this.notificationId = createCensoredNotificationId(notificationId, + notificationSummaryId, originalUserId); + } + + @Override + public void run() { + // Sanity check + if (censoredSendState == CensoredSendState.DONT_SEND) return; + + final String username = mUm.getUserInfo(originalUserId).name; + // Follows the way the app name is obtained in + // com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl, + // in the bindRow method. + String appname = pkg; + if (pkg != null) { + try { + final ApplicationInfo info = mPackageManagerClient.getApplicationInfoAsUser( + pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS, + originalUserId); + if (info != null) { + appname = String.valueOf(mPackageManagerClient.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + // Shouldn't be here; the original recipient should have the package! + // We will fallback to the package name. + } + } + + final String title = getContext().getString( + R.string.other_users_notification_title, appname, username); + final String subtext = getContext().getString( + R.string.notification_channel_other_users); + final String actionButtonTitle = getContext().getString( + R.string.other_users_notification_switch_user_action, username); + final int color = getContext().getColor( + com.android.internal.R.color.system_notification_accent_color); + + final Intent intent = new Intent(ACTION_SWITCH_USER) + .putExtra(EXTRA_SWITCH_USER_USERID, originalUserId) + .setPackage(getContext().getPackageName()) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND); + final PendingIntent pendingIntentSwitchUser = PendingIntent.getBroadcast(getContext(), + originalUserId, intent, PendingIntent.FLAG_UPDATE_CURRENT + | PendingIntent.FLAG_IMMUTABLE); + + // We use the group alert behavior and the fact that the summary will never make + // an audible alert to control whether censored notifications will make noise. + final Notification censoredNotification = + new Notification.Builder(getContext(), SystemNotificationChannels.OTHER_USERS) + .addAction(new Notification.Action.Builder(null /* icon */, + actionButtonTitle, pendingIntentSwitchUser).build()) + .setAutoCancel(false) + .setOngoing(false) + .setColor(color) + .setSmallIcon(R.drawable.ic_account_circle) + .setContentTitle(title) + .setSubText(subtext) + .setVisibility(Notification.VISIBILITY_PRIVATE) + .setGroup(notificationGroupKey) + .setGroupAlertBehavior(censoredSendState == CensoredSendState.SEND_QUIET + ? Notification.GROUP_ALERT_SUMMARY + : Notification.GROUP_ALERT_CHILDREN) + .setGroupSummary(false) + .setWhen(System.currentTimeMillis()) + .setShowWhen(true) + .build(); + + if (DBG) { + Slog.d(TAG, "Generated censored notification with id " + notificationId + + ", from user " + originalUserId + + ", for package " + pkg + + ", tag " + createCensoredNotificationTag(originalUserId, pkg, originalTag) + + ", sending to " + ActivityManager.getCurrentUser()); + } + + final int currentUserId = ActivityManager.getCurrentUser(); + enqueueNotificationInternal(getContext().getPackageName(), getContext().getPackageName(), + MY_UID, MY_PID, createCensoredNotificationTag(originalUserId, pkg, originalTag), + notificationId, censoredNotification, currentUserId, false, false); + + // Group the censored notifications per user. + final Notification censoredNotificationSummary = + new Notification.Builder(getContext(), SystemNotificationChannels.OTHER_USERS) + .setSmallIcon(R.drawable.ic_account_circle) + .setColor(color) + .setVisibility(Notification.VISIBILITY_PRIVATE) + .setSubText(subtext) + .setGroup(notificationGroupKey) + .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN) + .setGroupSummary(true) + .setWhen(System.currentTimeMillis()) + .setShowWhen(true) + .build(); + + enqueueNotificationInternal(getContext().getPackageName(), getContext().getPackageName(), + MY_UID, MY_PID, createCensoredSummaryTag(originalUserId), notificationSummaryId, + censoredNotificationSummary, currentUserId, false, false); + } + + /** + * @return a tag for the censored notification derived from the parameters. Note: the + * summary notification does not use this tag; see {@link #createCensoredSummaryTag(int)}. + * This helps uniquely identify this notification to prevent notification id collisions. + */ + private String createCensoredNotificationTag(int originalUserId, String pkg, + @Nullable String originalTag) { + return "other_users_" + + originalUserId + "_" + + pkg + + (originalTag != null ? "_" + originalTag : ""); + } + + private String createCensoredSummaryTag(int originalUserId) { + return "other_users_" + originalUserId; + } + + private int createCensoredNotificationId(int originalNotificationId, int censoredSummaryId, + int originalUserId) { + // Reserve the top integers for summary notification ids + return (originalNotificationId < Integer.MAX_VALUE - 50) + ? originalNotificationId : (censoredSummaryId >> 4) - originalUserId; + } + + private int createCensoredSummaryId(int originalUserId) { + // In the case where userId == 0, save room for auto group summary id, + // which is Integer.MAX_VALUE + return Integer.MAX_VALUE - 1 - originalUserId; + } + + private String createCensoredNotificationGroupKey(int originalUserId) { + return "OTHER_USERS_" + originalUserId; + } + } + /** * If the notification differs enough visually, consider it a new interruptive notification. */ diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 889df512dd601..3d0eac56a54e3 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -48,6 +48,7 @@ import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.notification.Flags.preventZenDeviceEffectsWhileDriving; +import static com.android.server.notification.NotificationManagerService.CensoredSendState; import static java.util.Objects.requireNonNull; @@ -280,6 +281,115 @@ public boolean shouldIntercept(NotificationRecord record) { } } + /** + *

Determines the censored notification sending state depending on an arbitrary user's + * ZenModeConfig. For use in determining if a notification should be forwarded to the foreground + * user in censored form.

+ * + *

Suppose user A is a background user and user B is the foreground user, and that all other + * checks for sending censored notifications have passed.

+ * + *
    + *
  • + * If user A has DND off, then user B will get the censored notifications normally + * ({@link CensoredSendState#SEND_NORMAL}). (This means that user B can get a notification + * that makes noise.) + *
  • + *
  • + * If user A has DND on and isn't hiding notifications from notification shade/lock screen, + * then user B will get muted censored notifications ({@link CensoredSendState#SEND_QUIET}). + *
  • + *
  • + * If user A has DND on and is hiding notifications, then user B will not get any censored + * notifications {@link CensoredSendState#DONT_SEND}, unless user A manually set it so that + * the notification bypasses DND + * + *
    • If it bypasses DND, then user B will get the notification normally + * ({@link CensoredSendState#SEND_NORMAL}).
    + *
+ * + *

No mConfig lock is needed during parsing; we work on a copy of the ZenModeConfig.

+ * + *

See {@link #computeZenMode()} for where the logic for computing zen mode was taken from. + * See {@link #updateConsolidatedPolicy(String)} for where the logic for creating a consolidated + * policy was taken from. Both methods are combined here to be able to generate a consolidated + * policy for an arbitrary ZenModeConfig. We can't use those methods directly, since they + * operate on only the foreground user.

+ * + * @implNote

This doesn't respect automatic rules for background users that change their state + * (i.e. become active or inactive), because the calendar/timers to do so are only checked for + * the foreground user. (e.g., If a background user has DND on due to an automatic rule, and that + * DND rule turns off while the user is still backgrounded, then this method will still treat + * notifications as intercepted by DND.)

+ * + *

We could try to do something like make + * {@link ZenModeConfig#parseAutomaticRuleEndTime(Context, Uri)} public and test that against + * System.currentTimeMillis.

, but that would make this more complicated. + * + * @param record The notification that is checked to see if it should be intercepted by DND. + * @param userId The identifier of the user to check the DND settings for. + * @return The censored sending state derived from the the notification and the user's do not + * disturb settings. + */ + CensoredSendState getCensoredSendStateFromUserDndOnVisuals(NotificationRecord record, + int userId) { + final ZenModeConfig config = getConfigCopyForUser(userId); + if (config == null) { + return CensoredSendState.SEND_NORMAL; + } + + // We create the consolidated policy for the user, and simultaneously compute the zen mode. + // The structure is derived from the other methods (see doc comment). + final ZenPolicy zenPolicy = new ZenPolicy(); + int zenMode = Global.ZEN_MODE_OFF; + boolean isZenModeFromManualConfig = false; + if (config.manualRule != null) { + // Don't replace the zen mode anymore. This mirrors the line + // `if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;` + // from computeZenMode. + isZenModeFromManualConfig = true; + zenMode = config.manualRule.zenMode; + if (zenMode == Global.ZEN_MODE_OFF) { + // zenMode won't be changed again anyway, so it won't be intercepted. Send normally + // to avoid constructing a consolidated policy. + return CensoredSendState.SEND_NORMAL; + } + applyCustomPolicy(mConfig, zenPolicy, config.manualRule, true); + } + + // This is apparently how automatic rules are parsed. + for (ZenRule automaticRule : config.automaticRules.values()) { + if (automaticRule.isActive()) { + applyCustomPolicy(mConfig, zenPolicy, automaticRule, false); + if (!isZenModeFromManualConfig + && zenSeverity(automaticRule.zenMode) > zenSeverity(zenMode)) { + zenMode = automaticRule.zenMode; + } + } + } + + if (zenMode == Global.ZEN_MODE_OFF) { + // Send normally to avoid constructing a consolidated policy. + return CensoredSendState.SEND_NORMAL; + } + + // Create the consolidated policy. (Maybe these can be cached?) + final NotificationManager.Policy policy = config.toNotificationPolicy(zenPolicy); + + if (mFiltering.shouldIntercept(zenMode, policy, record)) { + // Here, notification does not bypass DND + if ((policy.suppressedVisualEffects & + NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0) { + // If user A has DND on and is hiding notifications from notification shade/loc, + // then user B will not get any censored notifications + return CensoredSendState.DONT_SEND; + } + // Otherwise user B will get muted censored notifications + return CensoredSendState.SEND_QUIET; + } + return CensoredSendState.SEND_NORMAL; + } + public void addCallback(Callback callback) { mCallbacks.add(callback); } @@ -2000,6 +2110,23 @@ public ZenModeConfig getConfig() { } } + /** + * For use in determining if a notification should be forwarded to the foreground user in + * censored form. + * + * ZenModeHelper works by only focusing on the *foreground* user's do not disturb settings. + * This is needed to expose a way to get the config and interception results for an arbitrary + * user without getting affected by any changes. + * + * @return a copy of the zen mode configuration for the given userId + */ + private ZenModeConfig getConfigCopyForUser(int userId) { + synchronized (mConfig) { + final ZenModeConfig config = mConfigs.get(userId); + return config != null ? config.copy() : null; + } + } + /** * @return a copy of the zen mode consolidated policy */ From eb660ed427903f98cd7a8b5f700721841005f0ae Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Thu, 7 Jul 2022 09:28:40 +0300 Subject: [PATCH 057/332] DeviceIdleJobsController: don't ignore whitelisted system apps Only user app IDs were written to `mDeviceIdleWhitelistAppIds`, both initially and when `PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED` broadcast was received. All other places that listen to that broadcast retrieve both user and system app IDs. The only place where `mDeviceIdleWhitelistAppIds` array is checked is in `isWhitelistedLocked()`, which is called only by `updateTaskStateLocked()` to check whether the app is on the device idle whitelist. It's not clear why DeviceIdleJobsController ignores system apps. File level comment doesn't mention the distinction between system and user apps: "When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied." Comment for isWhitelistedLocked() does, however: "Checks if the given job's scheduling app id exists in the device idle user whitelist." However, that method is called for both system and user apps, and returns false for system apps because only whitelist of user apps is checked. This leads to long delays for jobs that were submitted by whitelisted system apps when device is in the Doze mode. No such delays happen with whitelisted user apps. Other places use a different naming for array of app IDs that includes only user apps, eg `mDeviceIdleWhitelistUserAppIds`, not `mDeviceIdleWhitelistAppIds`. I've looked through the Git history of DeviceIdleJobsController and JobSchedulerService, but didn't find a reason for this behavior. Perhaps, system apps were exempted from device idle JobScheduler restricitions in some other place previously, or this was a bug from the start. Tested on an emulator with the Messaging app, which uses JobScheduler during processing of incoming SMS: 1. Check that Messaging app is on system deviceidle whitelist: ``` $ dumpsys deviceidle whitelist | grep com.android.messaging system-excidle,com.android.messaging,10090 system,com.android.messaging,10090 ``` 2. Simulate sending an SMS: it appears immediately 3. Simulate Doze mode: `$ dumpsys deviceidle force-idle` 4. Simulate sending an SMS again. Message doesn't appear, even if the Messaging app is open 5. Exit Doze mode: `$ dumpsys deviceidle unforce`. All pending messages appear immediately 6. Add Messaging app to the user whitelist: ``` $ dumpsys deviceidle whitelist +com.android.messaging $ dumpsys deviceidle whitelist | grep com.android.messaging system-excidle,com.android.messaging,10090 system,com.android.messaging,10090 user,com.android.messaging,10090 ``` 7. Simulate Doze mode again: `$ dumpsys deviceidle force-idle` 8. Simulate sending an SMS, note that it appears immediately this time Also made a test system app to make sure that this issue isn't caused by low targetSdk of the Messaging app (it targets SDK 24). Same issue with targetSdk 32 app. In both cases, applying this patch fixes the issue. --- .../java/com/android/server/DeviceIdleInternal.java | 2 +- .../java/com/android/server/DeviceIdleController.java | 6 +++--- .../server/job/controllers/DeviceIdleJobsController.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java index 1fc888b06ffd3..b18f43e0b5380 100644 --- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java @@ -74,7 +74,7 @@ void addPowerSaveTempWhitelistAppDirect(int uid, long duration, boolean isAppOnWhitelist(int appid); - int[] getPowerSaveWhitelistUserAppIds(); + int[] getPowerSaveWhitelistAppIds(); int[] getPowerSaveTempWhitelistAppIds(); diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 41fd4a29cfd1a..04d29860fbabb 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -2389,14 +2389,14 @@ public String[] getFullPowerWhitelistExceptIdle() { } /** - * Returns the array of app ids whitelisted by user. Take care not to + * Returns the array of whitelisted app ids. Take care not to * modify this, as it is a reference to the original copy. But the reference * can change when the list changes, so it needs to be re-acquired when * {@link PowerManager#ACTION_POWER_SAVE_WHITELIST_CHANGED} is sent. */ @Override - public int[] getPowerSaveWhitelistUserAppIds() { - return DeviceIdleController.this.getPowerSaveWhitelistUserAppIds(); + public int[] getPowerSaveWhitelistAppIds() { + return DeviceIdleController.this.getAppIdWhitelistInternal(); } @Override diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java index d52bbc2451579..e8300f1af1b64 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -97,7 +97,7 @@ public void onReceive(Context context, Intent intent) { case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: synchronized (mLock) { mDeviceIdleWhitelistAppIds = - mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds(); + mLocalDeviceIdleController.getPowerSaveWhitelistAppIds(); if (DEBUG) { Slog.d(TAG, "Got whitelist " + Arrays.toString(mDeviceIdleWhitelistAppIds)); @@ -143,7 +143,7 @@ public DeviceIdleJobsController(JobSchedulerService service) { mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mLocalDeviceIdleController = LocalServices.getService(DeviceIdleInternal.class); - mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds(); + mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistAppIds(); mPowerSaveTempWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds(); mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor(); @@ -204,7 +204,7 @@ public void setUidActiveLocked(int uid, boolean active) { } /** - * Checks if the given job's scheduling app id exists in the device idle user whitelist. + * Checks if the given job's scheduling app id exists in the device idle whitelist. */ boolean isWhitelistedLocked(JobStatus job) { return Arrays.binarySearch(mDeviceIdleWhitelistAppIds, From d58f8302ed1a20af7315a5482b89e97ac8cb6281 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Thu, 6 Apr 2023 10:43:06 +0300 Subject: [PATCH 058/332] do not allow disabling app visibility filtering Needed for PackageManagerHooks.shouldFilterApplication() method. --- .../java/com/android/server/pm/AppsFilterImpl.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index 068d68d250173..577472439e6e2 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -291,19 +291,6 @@ public void setAppsFilter(AppsFilterImpl filter) { @Override public void onSystemReady() { - mFeatureEnabled = DeviceConfig.getBoolean( - NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME, - PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT); - DeviceConfig.addOnPropertiesChangedListener( - NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(), - properties -> { - if (properties.getKeyset().contains(FILTERING_ENABLED_NAME)) { - synchronized (FeatureConfigImpl.this) { - mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME, - PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT); - } - } - }); mInjector.getCompatibility().registerListener( PackageManager.FILTER_APPLICATION_QUERY, this); } From 918cb9c06773de39e1f5bf70392016ffca0c2314 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Mon, 27 Mar 2023 16:00:00 +0300 Subject: [PATCH 059/332] add hooks for modifying PackageManagerService behavior --- .../server/ext/PackageManagerHooks.java | 59 +++++++++++++++++++ .../com/android/server/pm/AppsFilterBase.java | 7 +++ .../server/pm/PackageManagerService.java | 16 ++++- .../java/com/android/server/pm/Settings.java | 8 ++- .../PermissionManagerServiceImpl.java | 8 +++ 5 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 services/core/java/com/android/server/ext/PackageManagerHooks.java diff --git a/services/core/java/com/android/server/ext/PackageManagerHooks.java b/services/core/java/com/android/server/ext/PackageManagerHooks.java new file mode 100644 index 0000000000000..cb5038687b6ff --- /dev/null +++ b/services/core/java/com/android/server/ext/PackageManagerHooks.java @@ -0,0 +1,59 @@ +package com.android.server.ext; + +import android.Manifest; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.pm.PackageManagerInternal; +import android.util.ArraySet; + +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.parsing.ParsingPackage; + +public class PackageManagerHooks { + + // Called when package enabled setting for a system package is deserialized from storage + @Nullable + public static Integer maybeOverrideSystemPackageEnabledSetting(String pkgName, @UserIdInt int userId) { + switch (pkgName) { + default: + return null; + } + } + + public static boolean shouldBlockGrantRuntimePermission( + PackageManagerInternal pm, String permName, String packageName, int userId) + { + return false; + } + + // Called when AppsFilter decides whether to restrict package visibility + public static boolean shouldFilterApplication( + @Nullable PackageStateInternal callingPkgSetting, + ArraySet callingSharedPkgSettings, + int callingUserId, + PackageStateInternal targetPkgSetting, int targetUserId + ) { + if (callingPkgSetting != null && restrictedVisibilityPackages.contains(callingPkgSetting.getPackageName())) { + if (!targetPkgSetting.isSystem()) { + return true; + } + } + + if (restrictedVisibilityPackages.contains(targetPkgSetting.getPackageName())) { + if (callingPkgSetting != null) { + return !callingPkgSetting.isSystem(); + } else { + for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) { + if (!callingSharedPkgSettings.valueAt(i).isSystem()) { + return true; + } + } + } + } + return false; + } + + // Packages in this array are restricted from interacting with and being interacted by non-system apps + private static final ArraySet restrictedVisibilityPackages = new ArraySet<>(new String[] { + }); +} diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java index 98b7c96102957..9f7dcef533802 100644 --- a/services/core/java/com/android/server/pm/AppsFilterBase.java +++ b/services/core/java/com/android/server/pm/AppsFilterBase.java @@ -39,6 +39,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.function.QuadFunction; +import com.android.server.ext.PackageManagerHooks; import com.android.server.om.OverlayReferenceMapper; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; @@ -453,6 +454,12 @@ protected boolean shouldFilterApplicationInternal(Computer snapshot, int calling Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + if (PackageManagerHooks.shouldFilterApplication(callingPkgSetting, callingSharedPkgSettings, + UserHandle.getUserId(callingUid), + targetPkgSetting, targetUserId)) { + return true; + } + if (callingPkgSetting != null) { if (callingPkgSetting.getPkg() != null && !mFeatureConfig.packageIsEnabled(callingPkgSetting.getPkg())) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5ed05f6852a6d..3fe60e4cee6e6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7597,7 +7597,21 @@ void grantImplicitAccess(@NonNull Computer snapshot, @UserIdInt int userId, boolean retainOnUpdate) { final AndroidPackage visiblePackage = snapshot.getPackage(visibleUid); final int recipientUid = UserHandle.getUid(userId, recipientAppId); - if (visiblePackage == null || snapshot.getPackage(recipientUid) == null) { + final AndroidPackage recipientPackage = snapshot.getPackage(recipientUid); + if (visiblePackage == null || recipientPackage == null) { + return; + } + + final PackageStateInternal recipientPsi = snapshot.getPackageStateInternal( + recipientPackage.getPackageName(), Process.SYSTEM_UID); + final PackageStateInternal visiblePsi = snapshot.getPackageStateInternal( + visiblePackage.getPackageName(), Process.SYSTEM_UID); + if (recipientPsi == null || visiblePsi == null) { + return; + } + + if (PackageManagerHooks.shouldFilterApplication(recipientPsi, null, userId, + visiblePsi, UserHandle.getUserId(visibleUid))) { return; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 92257f1ee2dd5..7b8617d726a40 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -110,6 +110,7 @@ import com.android.permission.persistence.RuntimePermissionsState; import com.android.server.LocalServices; import com.android.server.backup.PreferredActivityBackupHelper; +import com.android.server.ext.PackageManagerHooks; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.permission.LegacyPermissionDataProvider; @@ -1937,8 +1938,11 @@ void readPackageRestrictionsLPr(int userId, parser.getAttributeBoolean(null, ATTR_INSTANT_APP, false); final boolean virtualPreload = parser.getAttributeBoolean(null, ATTR_VIRTUAL_PRELOAD, false); - final int enabled = parser.getAttributeInt(null, ATTR_ENABLED, - COMPONENT_ENABLED_STATE_DEFAULT); + final Integer enabledOverride = ps.isSystem() ? + PackageManagerHooks.maybeOverrideSystemPackageEnabledSetting(name, userId) : null; + final int enabled = (enabledOverride != null) ? + enabledOverride.intValue() : + parser.getAttributeInt(null, ATTR_ENABLED, COMPONENT_ENABLED_STATE_DEFAULT); final String enabledCaller = parser.getAttributeValue(null, ATTR_ENABLED_CALLER); final String harmfulAppWarning = diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 1004a6e97c134..c538c359e13ff 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -136,6 +136,7 @@ import com.android.server.ServiceThread; import com.android.server.SystemConfig; import com.android.server.Watchdog; +import com.android.server.ext.PackageManagerHooks; import com.android.server.pm.ApexManager; import com.android.server.pm.KnownPackages; import com.android.server.pm.PackageInstallerService; @@ -1391,6 +1392,13 @@ private void grantRuntimePermissionInternal(String packageName, String permName, isRolePermission = permission.isRole(); isSoftRestrictedPermission = permission.isSoftRestricted(); } + + if (PackageManagerHooks.shouldBlockGrantRuntimePermission(mPackageManagerInt, permName, packageName, userId)) { + // this method is called from within system_server and from critical system processes, + // do not throw an exception, just return + return; + } + final boolean mayGrantRolePermission = isRolePermission && mayManageRolePermission(callingUid); final boolean mayGrantSoftRestrictedPermission = isSoftRestrictedPermission From 602f26973213064eefb423a92906ec19c9b182c0 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Tue, 30 Apr 2024 19:14:11 +0300 Subject: [PATCH 060/332] rename AppsFilterImpl.grantImplicitAccess method This is done to have the build break in case grantImplicitAccess() starts to get used in more places, which might weaken AppsFilter-based app isolation via PackageHooks. --- .../core/java/com/android/server/pm/AppsFilterImpl.java | 2 +- .../java/com/android/server/pm/PackageManagerService.java | 2 +- .../src/com/android/server/pm/AppsFilterImplTest.java | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index 577472439e6e2..90ed12358919b 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -449,7 +449,7 @@ public FeatureConfig getFeatureConfig() { * @param retainOnUpdate if the implicit access retained across package updates. * @return {@code true} if implicit access was not already granted. */ - public boolean grantImplicitAccess(int recipientUid, int visibleUid, boolean retainOnUpdate) { + public boolean grantImplicitAccess2(int recipientUid, int visibleUid, boolean retainOnUpdate) { if (recipientUid == visibleUid) { return false; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 3fe60e4cee6e6..c9b34bea06aed 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7628,7 +7628,7 @@ void grantImplicitAccess(@NonNull Computer snapshot, @UserIdInt int userId, accessGranted = mInstantAppRegistry.grantInstantAccess(userId, intent, recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/); } else { - accessGranted = mAppsFilter.grantImplicitAccess(recipientUid, visibleUid, + accessGranted = mAppsFilter.grantImplicitAccess2(recipientUid, visibleUid, retainOnUpdate); } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java index a01df8bf108d7..6ea4ef0b5719f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java @@ -1277,7 +1277,7 @@ public void testWhoCanSee() throws Exception { watcher.verifyNoChangeReported("getVisibility"); // provider read - appsFilter.grantImplicitAccess(hasProviderAppId, queriesProviderAppId, + appsFilter.grantImplicitAccess2(hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */); watcher.verifyChangeReported("grantImplicitAccess"); @@ -1349,7 +1349,7 @@ public void testOnChangeReport() throws Exception { watcher.verifyNoChangeReported("get"); // provider read - appsFilter.grantImplicitAccess( + appsFilter.grantImplicitAccess2( hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */); watcher.verifyChangeReported("grantImplicitAccess"); @@ -1412,7 +1412,7 @@ public void testAppsFilterRead() throws Exception { PackageSetting queriesProvider = simulateAddPackage(appsFilter, pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"), queriesProviderAppId); - appsFilter.grantImplicitAccess( + appsFilter.grantImplicitAccess2( hasProviderAppId, queriesProviderAppId, false /* retainOnUpdate */); AppsFilterSnapshot snapshot = appsFilter.snapshot(); @@ -1508,7 +1508,7 @@ public void testSdkSandbox_implicitAccessGranted_canSeePackage() throws Exceptio appsFilter.shouldFilterApplication(mSnapshot, callingUid, null /* callingSetting */, target, SYSTEM_USER)); - appsFilter.grantImplicitAccess(callingUid, target.getAppId(), false /* retainOnUpdate */); + appsFilter.grantImplicitAccess2(callingUid, target.getAppId(), false /* retainOnUpdate */); watcher.verifyChangeReported("grantImplicitAccess"); // After implicit access was granted the app should be visible to the sdk sandbox uid. From f4decbb43de0f552c7788dd9517bebdf90f2071f Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Fri, 18 Aug 2023 12:21:13 +0300 Subject: [PATCH 061/332] add hooks for modifying ActivityThread behavior --- core/java/android/app/ActivityThread.java | 10 ++++++++-- core/java/android/app/ActivityThreadHooks.java | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 core/java/android/app/ActivityThreadHooks.java diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4987624be7199..7e2effee9504d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5250,8 +5250,14 @@ private void handleCreateService(CreateServiceData data) { } else { cl = packageInfo.getClassLoader(); } - service = packageInfo.getAppFactory() - .instantiateService(cl, data.info.name, data.intent); + { + String className = data.info.name; + service = ActivityThreadHooks.instantiateService(className); + if (service == null) { + service = packageInfo.getAppFactory() + .instantiateService(cl, className, data.intent); + } + } ContextImpl context = ContextImpl.getImpl(service .createServiceBaseContext(this, packageInfo)); if (data.info.splitName != null) { diff --git a/core/java/android/app/ActivityThreadHooks.java b/core/java/android/app/ActivityThreadHooks.java new file mode 100644 index 0000000000000..95342db6f7a93 --- /dev/null +++ b/core/java/android/app/ActivityThreadHooks.java @@ -0,0 +1,17 @@ +package android.app; + +import android.content.Context; +import android.os.Bundle; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Objects; + +class ActivityThreadHooks { + + static Service instantiateService(String className) { + Service res = null; + return res; + } +} From 6739201d5b45762dd397bff0b1d06504521bdf84 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 15 Nov 2023 17:27:18 +0200 Subject: [PATCH 062/332] add helper class for launching non-standard Settings intents --- core/java/android/ext/SettingsIntents.java | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 core/java/android/ext/SettingsIntents.java diff --git a/core/java/android/ext/SettingsIntents.java b/core/java/android/ext/SettingsIntents.java new file mode 100644 index 0000000000000..02e3509839983 --- /dev/null +++ b/core/java/android/ext/SettingsIntents.java @@ -0,0 +1,23 @@ +package android.ext; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +/** @hide */ +public class SettingsIntents { + + public static final String APP_NATIVE_DEBUGGING = "android.settings.OPEN_APP_NATIVE_DEBUGGING_SETTINGS"; + public static final String APP_MEMTAG = "android.settings.OPEN_APP_MEMTAG_SETTINGS"; + public static final String APP_HARDENED_MALLOC = "android.settings.OPEN_APP_HARDENED_MALLOC_SETTINGS"; + public static final String APP_MEMORY_DYN_CODE_LOADING = "android.settings.OPEN_APP_MEMORY_DYN_CODE_LOADING_SETTINGS"; + public static final String APP_STORAGE_DYN_CODE_LOADING = "android.settings.OPEN_APP_STORAGE_DYN_CODE_LOADING_SETTINGS"; + + public static Intent getAppIntent(Context ctx, String action, String pkgName) { + var i = new Intent(action); + i.setData(Uri.fromParts("package", pkgName, null)); + i.setPackage(KnownSystemPackages.get(ctx).settings); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return i; + } +} From cd5f5d6ccb6f6f79fcc301436afc791808e9a719 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Tue, 16 May 2023 14:27:15 +0300 Subject: [PATCH 063/332] AppBindArgs: infrastructure for passing extra args to app process init --- core/java/android/app/ActivityThread.java | 5 +++ .../java/android/app/ActivityThreadHooks.java | 41 +++++++++++++++++ core/java/android/app/AppBindArgs.java | 8 ++++ .../android/content/pm/IPackageManager.aidl | 2 + .../server/ext/PackageManagerHooks.java | 44 +++++++++++++++++++ .../server/pm/PackageManagerService.java | 7 +++ 6 files changed, 107 insertions(+) create mode 100644 core/java/android/app/AppBindArgs.java diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7e2effee9504d..09ff67b343249 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -7712,6 +7712,7 @@ private void handleBindApplication(AppBindData data) { final IActivityManager mgr = ActivityManager.getService(); final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); mConfigurationController.updateLocaleListFromAppContext(appContext); + final Bundle extraAppBindArgs = ActivityThreadHooks.onBind(appContext); // Initialize the default http proxy in this process. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies"); @@ -7771,6 +7772,10 @@ private void handleBindApplication(AppBindData data) { dalvik.system.VMRuntime.getRuntime().clampGrowthLimit(); } + if (extraAppBindArgs != null) { + ActivityThreadHooks.onBind2(appContext, extraAppBindArgs); + } + // Allow disk access during application and provider setup. This could // block processing ordered broadcasts, but later processing would // probably end up doing the same disk access. diff --git a/core/java/android/app/ActivityThreadHooks.java b/core/java/android/app/ActivityThreadHooks.java index 95342db6f7a93..6df2ce428d2c8 100644 --- a/core/java/android/app/ActivityThreadHooks.java +++ b/core/java/android/app/ActivityThreadHooks.java @@ -10,6 +10,47 @@ class ActivityThreadHooks { + private static volatile boolean called; + + // called after the initial app context is constructed + // ActivityThread.handleBindApplication + static Bundle onBind(Context appContext) { + if (called) { + throw new IllegalStateException("onBind called for the second time"); + } + called = true; + + if (Process.isIsolated()) { + return null; + } + + final String pkgName = appContext.getPackageName(); + final String TAG = "AppBindArgs"; + + Bundle args = null; + try { + args = ActivityThread.getPackageManager().getExtraAppBindArgs(pkgName); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + + if (args == null) { + Log.e(TAG, "bundle is null"); + return null; + } + + int[] flags = Objects.requireNonNull(args.getIntArray(AppBindArgs.KEY_FLAGS_ARRAY)); + + return args; + } + + // called after ActivityThread instrumentation is inited, which happens before execution of any + // of app's code + // ActivityThread.handleBindApplication + static void onBind2(Context appContext, Bundle appBindArgs) { + + } + static Service instantiateService(String className) { Service res = null; return res; diff --git a/core/java/android/app/AppBindArgs.java b/core/java/android/app/AppBindArgs.java new file mode 100644 index 0000000000000..bd411cd4232aa --- /dev/null +++ b/core/java/android/app/AppBindArgs.java @@ -0,0 +1,8 @@ +package android.app; + +/** @hide */ +public interface AppBindArgs { + String KEY_FLAGS_ARRAY = "flagsArr"; + + int FLAGS_ARRAY_LEN = 10; +} diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index e6ddbf466cae7..bd13e8023c09e 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -871,4 +871,6 @@ interface IPackageManager { String getPageSizeCompatWarningMessage(in String packageName); List getAllApexDirectories(); + + @nullable Bundle getExtraAppBindArgs(String packageName); } diff --git a/services/core/java/com/android/server/ext/PackageManagerHooks.java b/services/core/java/com/android/server/ext/PackageManagerHooks.java index cb5038687b6ff..8fba0b51ee8d7 100644 --- a/services/core/java/com/android/server/ext/PackageManagerHooks.java +++ b/services/core/java/com/android/server/ext/PackageManagerHooks.java @@ -3,9 +3,19 @@ import android.Manifest; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.AppBindArgs; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.UserHandle; import android.util.ArraySet; +import com.android.server.pm.Computer; +import com.android.server.pm.PackageManagerService; +import com.android.server.pm.permission.Permission; +import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.parsing.ParsingPackage; @@ -26,6 +36,40 @@ public static boolean shouldBlockGrantRuntimePermission( return false; } + @Nullable + public static Bundle getExtraAppBindArgs(PackageManagerService pm, String packageName) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int appId = UserHandle.getAppId(callingUid); + final int userId = UserHandle.getUserId(callingUid); + + Computer pmComputer = pm.snapshotComputer(); + PackageStateInternal pkgState = pmComputer.getPackageStateInternal(packageName); + if (pkgState == null) { + return null; + } + + if (pkgState.getAppId() != appId) { + return null; + } + + AndroidPackage pkg = pkgState.getPkg(); + + if (pkg == null) { + return null; + } + + // isSystem() remains true even if isUpdatedSystemApp() is true + final boolean isUserApp = !pkgState.isSystem(); + + int[] flagsArr = new int[AppBindArgs.FLAGS_ARRAY_LEN]; + + var b = new Bundle(); + b.putIntArray(AppBindArgs.KEY_FLAGS_ARRAY, flagsArr); + + return b; + } + // Called when AppsFilter decides whether to restrict package visibility public static boolean shouldFilterApplication( @Nullable PackageStateInternal callingPkgSetting, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c9b34bea06aed..f9df65581a826 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -217,6 +217,7 @@ import com.android.server.compat.CompatChange; import com.android.server.compat.PlatformCompat; import com.android.server.crashrecovery.CrashRecoveryAdaptor; +import com.android.server.ext.PackageManagerHooks; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.Settings.VersionInfo; import com.android.server.pm.dex.ArtManagerService; @@ -6719,6 +6720,12 @@ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { getPerUidReadTimeouts(snapshot), mSnapshotStatistics ).doDump(snapshot, fd, pw, args); } + + @Nullable + @Override + public Bundle getExtraAppBindArgs(String packageName) { + return PackageManagerHooks.getExtraAppBindArgs(PackageManagerService.this, packageName); + } } private class PackageManagerInternalImpl extends PackageManagerInternalBase { From 86ac0cc213509556a753319022e33bba8d66fe38 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Wed, 20 Dec 2023 13:04:44 +0200 Subject: [PATCH 064/332] infrastructure for custom handling of known packages --- core/api/system-current.txt | 44 ++++ .../java/android/app/ActivityThreadHooks.java | 2 + core/java/android/app/AppGlobals.java | 12 ++ core/java/android/content/IntentFilter.java | 9 + .../android/content/pm/ApplicationInfo.java | 17 ++ core/java/android/ext/AppInfoExt.java | 85 ++++++++ core/java/android/ext/AppInfoExtFlag.java | 20 ++ core/java/android/ext/PackageId.java | 54 +++++ .../android/ext/settings/ExtSettings.java | 11 + core/java/android/provider/Settings.java | 5 +- .../internal/pm/parsing/PackageParser2.java | 4 +- .../pm/parsing/pkg/PackageExtDefault.java | 13 ++ .../pm/parsing/pkg/PackageExtIface.java | 8 + .../internal/pm/parsing/pkg/PackageImpl.java | 39 ++++ .../pm/pkg/component/ParsedServiceUtils.java | 2 + .../pm/pkg/parsing/PackageParsingHooks.java | 70 +++++++ .../pm/pkg/parsing/ParsingPackage.java | 11 + .../pm/pkg/parsing/ParsingPackageUtils.java | 81 +++++++- .../android/server/pm/pkg/AndroidPackage.java | 5 + core/res/res/values/config_ext.xml | 3 + proto/Android.bp | 5 +- services/Android.bp | 1 + .../com/android/server/ext/AppCompatConf.java | 195 ++++++++++++++++++ .../android/server/ext/SystemServerExt.java | 2 + .../com/android/server/pm/ext/PackageExt.java | 65 ++++++ .../android/server/pm/ext/PackageExtInit.java | 148 +++++++++++++ .../server/pm/ext/PackageHooksRegistry.java | 14 ++ .../java/com/android/server/SystemServer.java | 9 + 28 files changed, 927 insertions(+), 7 deletions(-) create mode 100644 core/java/android/ext/AppInfoExt.java create mode 100644 core/java/android/ext/AppInfoExtFlag.java create mode 100644 core/java/android/ext/PackageId.java create mode 100644 core/java/com/android/internal/pm/parsing/pkg/PackageExtDefault.java create mode 100644 core/java/com/android/internal/pm/parsing/pkg/PackageExtIface.java create mode 100644 core/java/com/android/internal/pm/pkg/parsing/PackageParsingHooks.java create mode 100644 services/core/java/com/android/server/ext/AppCompatConf.java create mode 100644 services/core/java/com/android/server/pm/ext/PackageExt.java create mode 100644 services/core/java/com/android/server/pm/ext/PackageExtInit.java create mode 100644 services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index cf40ce1f5dec1..7036838ef5951 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4035,6 +4035,7 @@ package android.content.om { package android.content.pm { public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { + method @NonNull public android.ext.AppInfoExt ext(); method @RequiresPermission(android.Manifest.permission.DELETE_PACKAGES) public boolean hasFragileUserData(); method public boolean isEncryptionAware(); method public boolean isInstantApp(); @@ -4819,6 +4820,22 @@ package android.debug { package android.ext { + public final class AppInfoExt implements android.os.Parcelable { + ctor public AppInfoExt(int, int, long); + method public int describeContents(); + method public static int getInitialPackageId(); + method public int getPackageId(); + method public boolean hasCompatChange(int); + method public boolean hasCompatConfig(); + method public boolean hasFlag(int); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public interface AppInfoExtFlag { + field public static final int HAS_GMSCORE_CLIENT_LIBRARY = 0; // 0x0 + } + public interface KnownSystemPackage { field public static final int SETTINGS = 0; // 0x0 field public static final int SHELL = 1; // 0x1 @@ -4837,6 +4854,33 @@ package android.ext { field @NonNull public final String systemUi; } + public interface PackageId { + field public static final int ANDROID_AUTO = 10; // 0xa + field public static final String ANDROID_AUTO_NAME = "com.google.android.projection.gearhead"; + field public static final int EUICC_SUPPORT_PIXEL = 5; // 0x5 + field public static final String EUICC_SUPPORT_PIXEL_NAME = "com.google.euiccpixel"; + field public static final int GMS_CORE = 2; // 0x2 + field public static final String GMS_CORE_NAME = "com.google.android.gms"; + field public static final String GSF_NAME = "com.google.android.gsf"; + field public static final int G_CAMERA = 8; // 0x8 + field public static final String G_CAMERA_NAME = "com.google.android.GoogleCamera"; + field public static final int G_CARRIER_SETTINGS = 7; // 0x7 + field public static final String G_CARRIER_SETTINGS_NAME = "com.google.android.carrier"; + field public static final int G_EUICC_LPA = 6; // 0x6 + field public static final String G_EUICC_LPA_NAME = "com.google.android.euicc"; + field public static final int G_SEARCH_APP = 4; // 0x4 + field public static final String G_SEARCH_APP_NAME = "com.google.android.googlequicksearchbox"; + field public static final int PIXEL_CAMERA_SERVICES = 9; // 0x9 + field public static final String PIXEL_CAMERA_SERVICES_NAME = "com.google.android.apps.camera.services"; + field public static final int PIXEL_HEALTH = 13; // 0xd + field public static final String PIXEL_HEALTH_NAME = "com.google.android.apps.pixel.health"; + field public static final int PLAY_STORE = 3; // 0x3 + field public static final String PLAY_STORE_NAME = "com.android.vending"; + field public static final int TYCHO = 11; // 0xb + field public static final String TYCHO_NAME = "com.google.android.apps.tycho"; + field public static final int UNKNOWN = 0; // 0x0 + } + } package android.ext.settings { diff --git a/core/java/android/app/ActivityThreadHooks.java b/core/java/android/app/ActivityThreadHooks.java index 6df2ce428d2c8..cbec05808c88a 100644 --- a/core/java/android/app/ActivityThreadHooks.java +++ b/core/java/android/app/ActivityThreadHooks.java @@ -20,6 +20,8 @@ static Bundle onBind(Context appContext) { } called = true; + AppGlobals.setInitialPackageId(appContext.getApplicationInfo().ext().getPackageId()); + if (Process.isIsolated()) { return null; } diff --git a/core/java/android/app/AppGlobals.java b/core/java/android/app/AppGlobals.java index f66bf0d89c37e..3a70e8f03f3ae 100644 --- a/core/java/android/app/AppGlobals.java +++ b/core/java/android/app/AppGlobals.java @@ -18,6 +18,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.IPackageManager; +import android.ext.PackageId; import android.permission.IPermissionManager; /** @@ -43,6 +44,17 @@ public static String getInitialPackage() { return ActivityThread.currentPackageName(); } + private static int initialPackageId = PackageId.UNKNOWN; + + public static void setInitialPackageId(int value) { + initialPackageId = value; + } + + // PackageId of the first APK loaded into the process + public static int getInitialPackageId() { + return initialPackageId; + } + /** * Return the raw interface to the package manager. * @return The package manager. diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index e895d7be1102a..5cdc4aea3968a 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -815,6 +815,15 @@ public final void addAction(String action) { mActions.add(action.intern()); } + /** @hide */ + public final boolean replaceAction(String action, String replacement) { + if (mActions.remove(action)) { + mActions.add(replacement); + return true; + } + return false; + } + /** * Return the number of actions in the filter. */ diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 94784227049d8..caf3850518742 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -31,6 +31,7 @@ import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; +import android.ext.AppInfoExt; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Environment; @@ -2047,6 +2048,7 @@ public ApplicationInfo() { public ApplicationInfo(ApplicationInfo orig) { super(orig); + ext = orig.ext; taskAffinity = orig.taskAffinity; permission = orig.permission; mKnownActivityEmbeddingCerts = orig.mKnownActivityEmbeddingCerts; @@ -2138,6 +2140,7 @@ public void writeToParcel(Parcel dest, int parcelableFlags) { return; } super.writeToParcel(dest, parcelableFlags); + ext.writeToParcel(dest, parcelableFlags); dest.writeString8(taskAffinity); dest.writeString8(permission); dest.writeString8(processName); @@ -2245,6 +2248,7 @@ public ApplicationInfo[] newArray(int size) { @SuppressWarnings("unchecked") private ApplicationInfo(Parcel source) { super(source); + ext = AppInfoExt.CREATOR.createFromParcel(source); taskAffinity = source.readString8(); permission = source.readString8(); processName = source.readString8(); @@ -3018,4 +3022,17 @@ public void setEnableOnBackInvokedCallback(boolean isEnable) { privateFlagsExt &= ~PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; } } + + private AppInfoExt ext = AppInfoExt.DEFAULT; + + /** @hide */ + public void setExt(AppInfoExt ext) { + this.ext = ext; + } + + /** @hide */ + @SystemApi + public @NonNull AppInfoExt ext() { + return ext; + } } diff --git a/core/java/android/ext/AppInfoExt.java b/core/java/android/ext/AppInfoExt.java new file mode 100644 index 0000000000000..4604ff0a7704d --- /dev/null +++ b/core/java/android/ext/AppInfoExt.java @@ -0,0 +1,85 @@ +package android.ext; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.app.AppGlobals; +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +@SystemApi +public final class AppInfoExt implements Parcelable { + /** @hide */ + public static final AppInfoExt DEFAULT = new AppInfoExt(PackageId.UNKNOWN, 0, 0L); + + private final int packageId; + private final int flags; + + /** @hide */ + public static final long HAS_COMPAT_CHANGES = 1L << 63; + private final long compatChanges; + + public AppInfoExt(int packageId, int flags, long compatChanges) { + this.packageId = packageId; + this.flags = flags; + this.compatChanges = compatChanges; + } + + /** + * One of {@link android.ext.PackageId} int constants. + */ + public int getPackageId() { + return packageId; + } + + public static int getInitialPackageId() { + return AppGlobals.getInitialPackageId(); + } + + public boolean hasFlag(@AppInfoExtFlag.Enum int flag) { + return (flags & (1 << flag)) != 0; + } + + public boolean hasCompatConfig() { + return (compatChanges & HAS_COMPAT_CHANGES) != 0; + } + + public boolean hasCompatChange(int flag) { + long mask = (1L << flag) | HAS_COMPAT_CHANGES; + return (compatChanges & mask) == mask; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int parcelFlags) { + boolean def = this == DEFAULT; + dest.writeBoolean(def); + if (def) { + return; + } + + dest.writeInt(packageId); + dest.writeInt(flags); + dest.writeLong(compatChanges); + } + + @NonNull + public static final Creator CREATOR = new Creator<>() { + @Override + public AppInfoExt createFromParcel(@NonNull Parcel p) { + if (p.readBoolean()) { + return DEFAULT; + } + return new AppInfoExt(p.readInt(), p.readInt(), p.readLong()); + } + + @Override + public AppInfoExt[] newArray(int size) { + return new AppInfoExt[size]; + } + }; +} diff --git a/core/java/android/ext/AppInfoExtFlag.java b/core/java/android/ext/AppInfoExtFlag.java new file mode 100644 index 0000000000000..917f247697582 --- /dev/null +++ b/core/java/android/ext/AppInfoExtFlag.java @@ -0,0 +1,20 @@ +package android.ext; + +import android.annotation.IntDef; +import android.annotation.SystemApi; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** @hide */ +@SystemApi +public interface AppInfoExtFlag { + /* SysApi */ int HAS_GMSCORE_CLIENT_LIBRARY = 0; + + /** @hide */ + @IntDef(value = { + HAS_GMSCORE_CLIENT_LIBRARY, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Enum {} +} diff --git a/core/java/android/ext/PackageId.java b/core/java/android/ext/PackageId.java new file mode 100644 index 0000000000000..2db9922539c5b --- /dev/null +++ b/core/java/android/ext/PackageId.java @@ -0,0 +1,54 @@ +package android.ext; + +import android.annotation.SystemApi; + +/** @hide */ +@SystemApi +// Int values that are assigned to packages in this interface can be retrieved at runtime from +// ApplicationInfo.ext().getPackageId() or from AndroidPackage.ext().getPackageId() (in system_server). +// +// PackageIds are assigned to parsed APKs only after they are verified, either by a certificate check +// or by a check that the APK is stored on an immutable OS partition. +public interface PackageId { + int UNKNOWN = 0; + + String GSF_NAME = "com.google.android.gsf"; + // no longer needed: int GSF = 1 + + String GMS_CORE_NAME = "com.google.android.gms"; + int GMS_CORE = 2; + + String PLAY_STORE_NAME = "com.android.vending"; + int PLAY_STORE = 3; + + String G_SEARCH_APP_NAME = "com.google.android.googlequicksearchbox"; + int G_SEARCH_APP = 4; + + String EUICC_SUPPORT_PIXEL_NAME = "com.google.euiccpixel"; + int EUICC_SUPPORT_PIXEL = 5; + + String G_EUICC_LPA_NAME = "com.google.android.euicc"; + int G_EUICC_LPA = 6; + + String G_CARRIER_SETTINGS_NAME = "com.google.android.carrier"; + int G_CARRIER_SETTINGS = 7; + + String G_CAMERA_NAME = "com.google.android.GoogleCamera"; + int G_CAMERA = 8; + + String PIXEL_CAMERA_SERVICES_NAME = "com.google.android.apps.camera.services"; + int PIXEL_CAMERA_SERVICES = 9; + + String ANDROID_AUTO_NAME = "com.google.android.projection.gearhead"; + int ANDROID_AUTO = 10; + + // "Google Fi" + String TYCHO_NAME = "com.google.android.apps.tycho"; + int TYCHO = 11; + + /** @hide */ String G_TEXT_TO_SPEECH_NAME = "com.google.android.tts"; + /** @hide */ int G_TEXT_TO_SPEECH = 12; + + String PIXEL_HEALTH_NAME = "com.google.android.apps.pixel.health"; + int PIXEL_HEALTH = 13; +} diff --git a/core/java/android/ext/settings/ExtSettings.java b/core/java/android/ext/settings/ExtSettings.java index 86fcb940bbd9b..1f362f3fc260b 100644 --- a/core/java/android/ext/settings/ExtSettings.java +++ b/core/java/android/ext/settings/ExtSettings.java @@ -28,6 +28,17 @@ public class ExtSettings { public static final BoolSysProperty EXEC_SPAWNING = new BoolSysProperty( "persist.security.exec_spawn", true); + // AppCompatConfig specifies which hardening features are compatible/incompatible with a + // specific app. + // This setting controls whether incompatible hardening features would be disabled by default + // for that app. In both cases, user will still be able to enable/disable them manually. + // + // Note that hardening features that are marked as compatible are enabled unconditionally by + // default, regardless of this setting. + public static final BoolSetting ALLOW_DISABLING_HARDENING_VIA_APP_COMPAT_CONFIG = new BoolSetting( + Setting.Scope.GLOBAL, Settings.Global.ALLOW_DISABLING_HARDENING_VIA_APP_COMPAT_CONFIG, + defaultBool(R.bool.setting_default_allow_disabling_hardening_via_app_compat_config)); + private ExtSettings() {} public static Function defaultBool(@BoolRes int res) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1e73792e35564..845fba5c6fbb5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13364,7 +13364,10 @@ public static final class Global extends NameValueTable { /** @see android.provider.Settings#getPublicSettingsForClass */ // ExtSettings BEGIN - + /** @hide */ + @Protected(restrictReads = false, readWrite = KnownSystemPackage.SETTINGS) + public static final String ALLOW_DISABLING_HARDENING_VIA_APP_COMPAT_CONFIG = + "allow_automatic_pkg_hardening_config"; // historical name // ExtSettings END diff --git a/core/java/com/android/internal/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java index 2c546728d7125..78439ce4c6292 100644 --- a/core/java/com/android/internal/pm/parsing/PackageParser2.java +++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java @@ -192,8 +192,10 @@ public abstract static class Callback implements ParsingPackageUtils.Callback { public final ParsingPackage startParsingPackage(@NonNull String packageName, @NonNull String baseCodePath, @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) { - return PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray, + var res = PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray, isCoreApp, Callback.this); + res.initPackageParsingHooks(); + return res; } /** diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageExtDefault.java b/core/java/com/android/internal/pm/parsing/pkg/PackageExtDefault.java new file mode 100644 index 0000000000000..09ddc8f0ec074 --- /dev/null +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageExtDefault.java @@ -0,0 +1,13 @@ +package com.android.internal.pm.parsing.pkg; + +import android.ext.AppInfoExt; + +/** @hide */ +public class PackageExtDefault implements PackageExtIface { + public static final PackageExtDefault INSTANCE = new PackageExtDefault(); + + @Override + public AppInfoExt toAppInfoExt(PackageImpl pkg) { + return AppInfoExt.DEFAULT; + } +} diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageExtIface.java b/core/java/com/android/internal/pm/parsing/pkg/PackageExtIface.java new file mode 100644 index 0000000000000..c09bd69b01d65 --- /dev/null +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageExtIface.java @@ -0,0 +1,8 @@ +package com.android.internal.pm.parsing.pkg; + +import android.ext.AppInfoExt; + +/** @hide */ +public interface PackageExtIface { + AppInfoExt toAppInfoExt(PackageImpl pkg); +} diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java index 5ec5762c05332..d358b0d121d04 100644 --- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java @@ -77,6 +77,7 @@ import com.android.internal.pm.pkg.component.ParsedServiceImpl; import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl; +import com.android.internal.pm.pkg.parsing.PackageParsingHooks; import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.pm.pkg.parsing.ParsingPackageHidden; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; @@ -99,8 +100,10 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.function.Function; /** * Extensions to {@link PackageImpl} including fields/state contained in the system server @@ -640,6 +643,10 @@ public PackageImpl addProtectedBroadcast(String protectedBroadcast) { @Override public PackageImpl addProvider(ParsedProvider parsedProvider) { + if (getPackageParsingHooks().shouldSkipProvider(parsedProvider)) { + return this; + } + this.providers = CollectionUtils.add(this.providers, parsedProvider); addMimeGroupsFromComponent(parsedProvider); return this; @@ -2632,6 +2639,7 @@ public PackageImpl sortServices() { public ApplicationInfo toAppInfoWithoutStateWithoutFlags() { ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.setExt(ext.toAppInfoExt(this)); // Lines that are commented below are state related and should not be assigned here. // They are left in as placeholders, since there is no good backwards compatible way to @@ -3938,4 +3946,35 @@ private static class Booleans2 { private static final long APEX = 1L << 1; private static final long UPDATABLE_SYSTEM = 1L << 2; } + + private PackageParsingHooks packageParsingHooks = PackageParsingHooks.DEFAULT; + + public static Function packageParsingHooksSupplier; + + @Override + public void initPackageParsingHooks() { + var supplier = packageParsingHooksSupplier; + packageParsingHooks = supplier != null ? supplier.apply(getPackageName()) : PackageParsingHooks.DEFAULT; + } + + @Override + public PackageParsingHooks getPackageParsingHooks() { + return packageParsingHooks; + } + + private PackageExtIface ext = PackageExtDefault.INSTANCE; + + @Override + public void setPackageExt(@Nullable PackageExtIface ext) { + this.ext = ext; + } + + @Nullable + @Override + public PackageExtIface ext() { + return ext; + } + + public long cachedCompatConfigVersionCode; + public Object cachedCompatConfig; } diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java index c469a7a5bc057..43d4b4bd0cbee 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java @@ -191,6 +191,8 @@ public static ParseResult parseService(String[] separateProcesses service.setExported(hasIntentFilters); } + pkg.getPackageParsingHooks().amendParsedService(service); + return input.success(service); } } diff --git a/core/java/com/android/internal/pm/pkg/parsing/PackageParsingHooks.java b/core/java/com/android/internal/pm/pkg/parsing/PackageParsingHooks.java new file mode 100644 index 0000000000000..6b53358d1f15e --- /dev/null +++ b/core/java/com/android/internal/pm/pkg/parsing/PackageParsingHooks.java @@ -0,0 +1,70 @@ +package com.android.internal.pm.pkg.parsing; + +import android.annotation.Nullable; +import android.content.pm.PackageManager; + +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedServiceImpl; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl; + +import java.util.ArrayList; +import java.util.List; + +public class PackageParsingHooks { + public static final PackageParsingHooks DEFAULT = new PackageParsingHooks(); + + public boolean shouldSkipPermissionDefinition(ParsedPermission p) { + return false; + } + + public boolean shouldSkipUsesPermission(ParsedUsesPermission p) { + return false; + } + + public boolean shouldSkipProvider(ParsedProvider p) { + return false; + } + + @Nullable + public List addUsesPermissions() { + return null; + } + + protected static List createUsesPerms(String... perms) { + int l = perms.length; + var res = new ArrayList(l); + for (int i = 0; i < l; ++i) { + res.add(new ParsedUsesPermissionImpl(perms[i], 0)); + } + return res; + } + + public void amendParsedService(ParsedServiceImpl s) { + + } + + public List addServices(ParsingPackage pkg) { + return null; + } + + // supported return values: + // PackageManager.COMPONENT_ENABLED_STATE_DISABLED + // PackageManager.COMPONENT_ENABLED_STATE_ENABLED + // PackageManager.COMPONENT_ENABLED_STATE_DEFAULT (skip override) + public int overrideDefaultPackageEnabledState() { + return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + } + + public static ParsedServiceImpl createService(ParsingPackage pkg, String className) { + var s = new ParsedServiceImpl(); + s.setPackageName(pkg.getPackageName()); + s.setName(className); + s.setProcessName(pkg.getProcessName()); + s.setDirectBootAware(pkg.isPartiallyDirectBootAware()); + s.setExported(true); + return s; + } +} diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index 5062d58d4dca6..aafcfe8afbd88 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -31,6 +31,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; +import com.android.internal.pm.parsing.pkg.PackageExtIface; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedApexSystemService; @@ -578,4 +579,14 @@ ParsingPackage setResetEnabledSettingsOnAppDataCleared( * Returns the intent matching flags. */ int getIntentMatchingFlags(); + + boolean isPartiallyDirectBootAware(); + + void initPackageParsingHooks(); + + default PackageParsingHooks getPackageParsingHooks() { + return PackageParsingHooks.DEFAULT; + } + + void setPackageExt(@Nullable PackageExtIface ext); } diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index db60e12e50b19..e5c1e32e781fe 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -144,6 +144,7 @@ import java.util.Objects; import java.util.Set; import java.util.StringTokenizer; +import java.util.function.Function; /** * TODO(b/135203078): Differentiate between parse_ methods and some add_ method for whether it @@ -642,9 +643,22 @@ private ParseResult parseBaseApk(ParseInput input, File apkFile, pkg.setVolumeUuid(volumeUuid); + PackageExtInitIface pkgExtInit = null; + PackageExtInitSupplier pkgExtInitSupplier = packageExtInitSupplier; + if (pkgExtInitSupplier != null) { + pkgExtInit = pkgExtInitSupplier.invoke(input, pkg, (flags & PARSE_IS_SYSTEM_DIR) != 0); + if (pkgExtInit != null) { + pkgExtInit.run(); + } + } + if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { - final ParseResult ret = - getSigningDetails(input, pkg, false /*skipVerify*/); + // skip reparsing certificates if they were already parsed by PackageExtInit + ParseResult ret = pkgExtInit != null ? + pkgExtInit.getSigningDetailsParseResult() : null; + if (ret == null) { + ret = parseSigningDetails(input, pkg); + } if (ret.isError()) { return input.error(ret); } @@ -660,6 +674,22 @@ private ParseResult parseBaseApk(ParseInput input, File apkFile, } } + public interface PackageExtInitSupplier { + PackageExtInitIface invoke(ParseInput input, ParsingPackage pkg, boolean isSystem); + } + + @Nullable + public static PackageExtInitSupplier packageExtInitSupplier; + + public interface PackageExtInitIface { + void run(); + ParseResult getSigningDetailsParseResult(); + } + + public static ParseResult parseSigningDetails(ParseInput input, ParsingPackage pkg) { + return getSigningDetails(input, pkg, false /*skipVerify*/); + } + private ParseResult parseSplitApk(ParseInput input, ParsingPackage pkg, int splitIndex, AssetManager assets, int flags) { final String apkPath = pkg.getSplitCodePaths()[splitIndex]; @@ -1055,6 +1085,27 @@ private ParseResult validateBaseApkTags(ParseInput input, Parsin ); } + List usesPermsList = pkg.getUsesPermissions(); + var usesPerms = new java.util.HashSet(usesPermsList.size() + 10); + for (ParsedUsesPermission p : usesPermsList) { + usesPerms.add(p.getName()); + } + + List extraUsesPerms = pkg.getPackageParsingHooks().addUsesPermissions(); + + if (extraUsesPerms != null) { + for (ParsedUsesPermission p : extraUsesPerms) { + String name = p.getName(); + if (!usesPerms.add(name)) { + Slog.w(TAG, "PackageParsingHooks.addUsesPermissions() " + + "tried to add duplicate uses-permission " + name + + " to pkg " + pkg.getPackageName()); + continue; + } + pkg.addUsesPermission(p); + } + } + convertCompatPermissions(pkg); convertSplitPermissions(pkg); @@ -1347,7 +1398,9 @@ private static ParseResult parsePermission(ParseInput input, } ParsedPermission permission = result.getResult(); if (permission != null) { - pkg.addPermission(permission); + if (!pkg.getPackageParsingHooks().shouldSkipPermissionDefinition(permission)) { + pkg.addPermission(permission); + } } return input.success(pkg); } @@ -1502,7 +1555,10 @@ private ParseResult parseUsesPermission(ParseInput input, } if (!found) { - pkg.addUsesPermission(new ParsedUsesPermissionImpl(name, usesPermissionFlags)); + var p = new ParsedUsesPermissionImpl(name, usesPermissionFlags); + if (!pkg.getPackageParsingHooks().shouldSkipUsesPermission(p)) { + pkg.addUsesPermission(p); + } } return success; } finally { @@ -2360,6 +2416,15 @@ private ParseResult parseBaseApplication(ParseInput input, if (hasReceiverOrder) { pkg.sortReceivers(); } + + List extraServices = pkg.getPackageParsingHooks().addServices(pkg); + if (extraServices != null) { + for (var s : extraServices) { + hasServiceOrder |= (s.getOrder() != 0); + pkg.addService(s); + } + } + if (hasServiceOrder) { pkg.sortServices(); } @@ -2466,6 +2531,14 @@ private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) { // CHECKSTYLE:on //@formatter:on + + var hooks = pkg.getPackageParsingHooks(); + int enabledOverride = hooks.overrideDefaultPackageEnabledState(); + if (enabledOverride == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { + pkg.setEnabled(false); + } else if (enabledOverride == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + pkg.setEnabled(true); + } } /** diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java index 5fa8125ced105..ab284c809744e 100644 --- a/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -47,6 +47,7 @@ import android.util.SparseIntArray; import com.android.internal.R; +import com.android.internal.pm.parsing.pkg.PackageExtIface; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.pm.pkg.component.ParsedAttribution; @@ -1550,4 +1551,8 @@ public interface AndroidPackage { * @hide */ int getIntentMatchingFlags(); + + /** @hide */ + @Immutable.Ignore + PackageExtIface ext(); } diff --git a/core/res/res/values/config_ext.xml b/core/res/res/values/config_ext.xml index 5e0b997d6283d..9fd40ab92d232 100644 --- a/core/res/res/values/config_ext.xml +++ b/core/res/res/values/config_ext.xml @@ -1,5 +1,8 @@ + true + + app.grapheneos.apps diff --git a/proto/Android.bp b/proto/Android.bp index feaa6d2e9b73b..98ad96bac6186 100644 --- a/proto/Android.bp +++ b/proto/Android.bp @@ -13,7 +13,10 @@ java_library_static { proto: { type: "nano", }, - srcs: ["src/**/*.proto"], + srcs: [ + "src/**/*.proto", + ":app_compat_config_proto-src", + ], sdk_version: "9", // Pin java_version until jarjar is certified to support later versions. http://b/72703434 java_version: "1.8", diff --git a/services/Android.bp b/services/Android.bp index b863f1470d7fd..044d01529e7a5 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -389,6 +389,7 @@ system_java_library { }, required: [ + "AppCompatConfig", "libukey2_jni_shared", "protolog.conf.json.gz", "core.protolog.pb", diff --git a/services/core/java/com/android/server/ext/AppCompatConf.java b/services/core/java/com/android/server/ext/AppCompatConf.java new file mode 100644 index 0000000000000..eb8e196b1b780 --- /dev/null +++ b/services/core/java/com/android/server/ext/AppCompatConf.java @@ -0,0 +1,195 @@ +package com.android.server.ext; + +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManagerInternal; +import android.content.pm.SigningDetails; +import android.os.PatternMatcher; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.os.BackgroundThread; +import com.android.internal.pm.parsing.pkg.PackageImpl; +import com.android.server.LocalServices; +import com.android.server.os.nano.AppCompatProtos; +import com.android.server.os.nano.AppCompatProtos.AppCompatConfig; +import com.android.server.os.nano.AppCompatProtos.CompatConfig; +import com.android.server.pm.pkg.AndroidPackage; + +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class AppCompatConf { + private static final String TAG = AppCompatConf.class.getSimpleName(); + + private static final String CONFIG_HOLDER_PKG_NAME = "app.grapheneos.AppCompatConfig"; + + public static class Configs { + public final long versionCode; + public final ArrayMap map; + + Configs(long versionCode, ArrayMap map) { + this.versionCode = versionCode; + this.map = map; + } + } + + private static volatile Configs configs; + + public static Configs getParsedConfigs() { + return configs; + } + + @Nullable + private static AndroidPackage getConfigHolderPackage() { + var pm = LocalServices.getService(PackageManagerInternal.class); + AndroidPackage pkg = pm.getPackage(CONFIG_HOLDER_PKG_NAME); + if (pkg == null) { + Slog.w(TAG, "missing " + CONFIG_HOLDER_PKG_NAME); + return null; + } + return pkg; + } + + static void init(Context ctx) { + AndroidPackage pkg = getConfigHolderPackage(); + if (pkg == null) { + Slog.w(TAG, "missing " + CONFIG_HOLDER_PKG_NAME); + // don't register listener + return; + } + + update(pkg); + + var filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED); + filter.addDataScheme("package"); + filter.addDataPath(new PatternMatcher(CONFIG_HOLDER_PKG_NAME, PatternMatcher.PATTERN_LITERAL)); + ctx.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Slog.d(TAG, "received " + intent); + AndroidPackage updatedPkg = getConfigHolderPackage(); + if (updatedPkg == null) { + Slog.e(TAG, "missing config package after update"); + } else { + update(updatedPkg); + } + } + }, filter, null, BackgroundThread.getHandler()); + } + + private static void update(AndroidPackage pkg) { + String apkPath = pkg.getSplits().get(0).getPath(); + // thread-safe: "configs" field is volatile and map itself is immutable after parsing + configs = parseFromApk(pkg.getLongVersionCode(), apkPath); + Slog.d(TAG, "updated from " + apkPath); + } + + @Nullable + public static CompatConfig get(Configs configs, PackageImpl pkg) { + ArrayMap map = configs.map; + + String pkgName = pkg.getPackageName(); + + AppCompatConfig acc = map.get(pkgName); + + if (acc == null) { + return null; + } + + AppCompatProtos.PackageSpec pkgSpec = acc.packageSpec; + + SigningDetails signingDetails = pkg.getSigningDetails(); + + if (signingDetails == SigningDetails.UNKNOWN) { + Slog.w(TAG, "SigningDetails.UNKNOWN for " + pkgName); + return null; + } + + boolean validCert = false; + + for (byte[] cert : pkgSpec.certsSha256) { + if (signingDetails.hasSha256Certificate(cert)) { + validCert = true; + break; + } + } + + if (!validCert) { + Slog.d(TAG, "invalid cert for " + pkgName); + return null; + } + + long version = pkg.getLongVersionCode(); + + for (CompatConfig c : acc.configs) { + long min = c.minVersion; + if (min != 0 && version < min) { + continue; + } + long max = c.maxVersion; + if (max != 0 && version > max) { + continue; + } + return c; + } + + Slog.d(TAG, "unknown version " + version + " of " + pkgName); + return null; + } + + @Nullable + private static Configs parseFromApk(long versionCode, String apkPath) { + try { + byte[] configBytes; + + try (var f = new ZipFile(apkPath)) { + ZipEntry e = f.getEntry("app_compat_configs.pb"); + try (var s = f.getInputStream(e)) { + configBytes = s.readAllBytes(); + } + } + + var configsWrapper = AppCompatProtos.AppCompatConfigs.parseFrom(configBytes); + + AppCompatConfig[] configs = configsWrapper.configs; + + var map = new ArrayMap(configs.length); + for (var e : configs) { + map.put(e.packageSpec.pkgName, e); + } + + return new Configs(versionCode, map); + } catch (Exception e) { + Slog.e(TAG, "", e); + return null; + } + } + + @Nullable + public static AppCompatProtos.CompatConfig get(PackageImpl pkg) { + Configs configs = getParsedConfigs(); + + if (configs == null) { + return null; + } + + synchronized (pkg) { + if (configs.versionCode == pkg.cachedCompatConfigVersionCode) { + return (AppCompatProtos.CompatConfig) pkg.cachedCompatConfig; + } + } + + var config = get(configs, pkg); + + synchronized (pkg) { + pkg.cachedCompatConfigVersionCode = configs.versionCode; + pkg.cachedCompatConfig = config; + } + + return config; + } +} diff --git a/services/core/java/com/android/server/ext/SystemServerExt.java b/services/core/java/com/android/server/ext/SystemServerExt.java index 6969e8773928c..df1138df0e054 100644 --- a/services/core/java/com/android/server/ext/SystemServerExt.java +++ b/services/core/java/com/android/server/ext/SystemServerExt.java @@ -29,6 +29,8 @@ private SystemServerExt(Context systemContext, PackageManagerService pm) { public static void init(Context systemContext, PackageManagerService pm) { SystemServerExt sse = new SystemServerExt(systemContext, pm); sse.bgHandler.post(sse::initBgThread); + + AppCompatConf.init(systemContext); } void initBgThread() { diff --git a/services/core/java/com/android/server/pm/ext/PackageExt.java b/services/core/java/com/android/server/pm/ext/PackageExt.java new file mode 100644 index 0000000000000..733ee9d585fe9 --- /dev/null +++ b/services/core/java/com/android/server/pm/ext/PackageExt.java @@ -0,0 +1,65 @@ +package com.android.server.pm.ext; + +import android.ext.AppInfoExt; +import android.ext.PackageId; +import android.os.Parcel; + +import com.android.internal.pm.parsing.pkg.PackageExtIface; +import com.android.internal.pm.parsing.pkg.PackageImpl; +import com.android.server.ext.AppCompatConf; +import com.android.server.os.nano.AppCompatProtos; +import com.android.server.pm.pkg.AndroidPackage; + +public class PackageExt implements PackageExtIface { + public static final PackageExt DEFAULT = new PackageExt(PackageId.UNKNOWN, 0); + + private final int packageId; + private final int flags; + + public static PackageExt get(AndroidPackage pkg) { + PackageExtIface i = pkg.ext(); + if (i instanceof PackageExt) { + return (PackageExt) i; + } + return DEFAULT; + } + + public PackageExt(int packageId, int flags) { + this.packageId = packageId; + this.flags = flags; + } + + public int getPackageId() { + return packageId; + } + + public AppInfoExt toAppInfoExt(PackageImpl pkg) { + AppCompatProtos.CompatConfig compatConfig = AppCompatConf.get(pkg); + + if (this == DEFAULT && compatConfig == null) { + return AppInfoExt.DEFAULT; + } + + long compatChanges = compatConfig != null ? + compatConfig.compatChanges | AppInfoExt.HAS_COMPAT_CHANGES : 0L; + + return new AppInfoExt(packageId, flags, compatChanges); + } + + public void writeToParcel(Parcel dest) { + boolean def = this == DEFAULT; + dest.writeBoolean(def); + if (def) { + return; + } + dest.writeInt(this.packageId); + dest.writeInt(this.flags); + } + + public static PackageExt createFromParcel(PackageImpl pkg, Parcel p) { + if (p.readBoolean()) { + return DEFAULT; + } + return new PackageExt(p.readInt(), p.readInt()); + } +} diff --git a/services/core/java/com/android/server/pm/ext/PackageExtInit.java b/services/core/java/com/android/server/pm/ext/PackageExtInit.java new file mode 100644 index 0000000000000..0043aa576a6ea --- /dev/null +++ b/services/core/java/com/android/server/pm/ext/PackageExtInit.java @@ -0,0 +1,148 @@ +package com.android.server.pm.ext; + +import android.annotation.Nullable; +import android.content.pm.SigningDetails; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.ext.AppInfoExtFlag; +import android.ext.PackageId; +import android.os.Bundle; +import android.util.Slog; + +import com.android.internal.pm.parsing.pkg.PackageImpl; +import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; + +import libcore.util.HexEncoding; + +import static android.ext.PackageId.*; + +public class PackageExtInit implements ParsingPackageUtils.PackageExtInitIface { + private static final String TAG = PackageExtInit.class.getSimpleName(); + + private final ParseInput input; + private final ParsingPackage parsingPackage; + private final PackageImpl pkg; + private final boolean isSystem; + + @Nullable + private ParseResult signingDetailsParseResult; + + public PackageExtInit(ParseInput input, ParsingPackage parsingPackage, boolean isSystem) { + this.input = input; + this.parsingPackage = parsingPackage; + this.pkg = (PackageImpl) parsingPackage; + this.isSystem = isSystem; + } + + @Nullable + public ParseResult getSigningDetailsParseResult() { + return signingDetailsParseResult; + } + + public void run() { + int packageId = getPackageId(); + + if (packageId != UNKNOWN) { + Slog.d(TAG, "set packageId of " + pkg.getPackageName() + " to " + packageId); + } + + var ext = new PackageExt(packageId, getExtFlags()); + + parsingPackage.setPackageExt(ext); + } + + private int getExtFlags() { + int flags = 0; + + Bundle metadata = pkg.getMetaData(); + if (metadata != null) { + if (metadata.containsKey("com.google.android.gms.version")) { + flags |= (1 << AppInfoExtFlag.HAS_GMSCORE_CLIENT_LIBRARY); + } + } + + return flags; + } + + private int getPackageId() { + return switch (pkg.getPackageName()) { + case GMS_CORE_NAME -> validate(GMS_CORE, 21_00_00_000L, mainGmsCerts()); + case PLAY_STORE_NAME -> validate(PLAY_STORE, 0L, mainGmsCerts()); + case G_SEARCH_APP_NAME -> validate(G_SEARCH_APP, 0L, mainGmsCerts()); + case EUICC_SUPPORT_PIXEL_NAME -> validateSystemPkg(EUICC_SUPPORT_PIXEL); + case G_EUICC_LPA_NAME -> validateSystemPkg(G_EUICC_LPA); + case G_CARRIER_SETTINGS_NAME -> validate(G_CARRIER_SETTINGS, 37L, + "c00409b6524658c2e8eb48975a5952959ea3707dd57bc50fd74d6249262f0e82"); + case G_CAMERA_NAME -> validate(G_CAMERA, 65820000L, + "f0fd6c5b410f25cb25c3b53346c8972fae30f8ee7411df910480ad6b2d60db83", + "1975b2f17177bc89a5dff31f9e64a6cae281a53dc1d1d59b1d147fe1c82afa00"); + case PIXEL_CAMERA_SERVICES_NAME -> validate(PIXEL_CAMERA_SERVICES, 124000L, + "226bb0439d6baeaa5a397c586e7031d8addfaec73c65be212f4a5dbfbf621b92"); + case ANDROID_AUTO_NAME -> validate(ANDROID_AUTO, 11_0_635014L, + "1ca8dcc0bed3cbd872d2cb791200c0292ca9975768a82d676b8b424fb65b5295"); + case TYCHO_NAME -> validate(TYCHO, 3044673L, + "8c4e8f364cb132d41626f67749a6385605f51d365098c0cb5976eb5c1500a3ce"); + case G_TEXT_TO_SPEECH_NAME -> validate(G_TEXT_TO_SPEECH, 2104800_00L, + "7ce83c1b71f3d572fed04c8d40c5cb10ff75e6d87d9df6fbd53f0468c2905053"); + case PIXEL_HEALTH_NAME -> validate(PIXEL_HEALTH, 2224L, + "295499d8d0e93b7ed64f90e8cddffc12e3be23d8806f54e05d1abf415c37f5ba"); + + default -> PackageId.UNKNOWN; + }; + } + + private static String[] mainGmsCerts() { + return new String[] { + // "bd32" SHA256withRSA issued in March 2020 + "7ce83c1b71f3d572fed04c8d40c5cb10ff75e6d87d9df6fbd53f0468c2905053", + // "38d1" MD5withRSA issued in August 2008 + "f0fd6c5b410f25cb25c3b53346c8972fae30f8ee7411df910480ad6b2d60db83", + // "58e1" MD5withRSA issued in April 2008 + "1975b2f17177bc89a5dff31f9e64a6cae281a53dc1d1d59b1d147fe1c82afa00", + }; + } + + private int validateSystemPkg(int packageId) { + if (isSystem) { + return packageId; + } + Slog.w(TAG, "expected " + pkg.getPackageName() + " to be a part of the system image"); + return PackageId.UNKNOWN; + } + + private int validate(int packageId, long minVersionCode, String... validCertificatesSha256) { + if (pkg.getLongVersionCode() < minVersionCode) { + Slog.d(TAG, "minVersionCode check failed, pkgName " + pkg.getPackageName() + "," + + " pkgVersion: " + pkg.getLongVersionCode()); + return PackageId.UNKNOWN; + } + + SigningDetails signingDetails = pkg.getSigningDetails(); + + if (signingDetails == SigningDetails.UNKNOWN) { + final ParseResult result = ParsingPackageUtils.parseSigningDetails(input, parsingPackage); + signingDetailsParseResult = result; + + if (result.isError()) { + Slog.e(TAG, "unable to parse SigningDetails for " + parsingPackage.getPackageName() + + "; code " + result.getErrorCode() + "; msg " + result.getErrorMessage(), + result.getException()); + return PackageId.UNKNOWN; + } + + signingDetails = result.getResult(); + } + + for (String certSha256String : validCertificatesSha256) { + byte[] validCertSha256 = HexEncoding.decode(certSha256String); + if (signingDetails.hasSha256Certificate(validCertSha256)) { + return packageId; + } + } + + Slog.d(TAG, "SigningDetails of " + pkg.getPackageName() + " don't contain any of known certificates"); + + return PackageId.UNKNOWN; + } +} diff --git a/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java b/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java new file mode 100644 index 0000000000000..b720e2edbca75 --- /dev/null +++ b/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java @@ -0,0 +1,14 @@ +package com.android.server.pm.ext; + +import android.ext.PackageId; + +import com.android.internal.pm.pkg.parsing.PackageParsingHooks; + +public class PackageHooksRegistry { + + public static PackageParsingHooks getParsingHooks(String pkgName) { + return switch (pkgName) { + default -> PackageParsingHooks.DEFAULT; + }; + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b7faeffd09b51..7d1d721978a23 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -112,6 +112,8 @@ import com.android.internal.os.RuntimeInit; import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.pm.RoSystemFeatures; +import com.android.internal.pm.parsing.pkg.PackageImpl; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.ProtoLog; import com.android.internal.protolog.ProtoLogConfigurationServiceImpl; @@ -227,6 +229,8 @@ import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; import com.android.server.pm.dex.OdsignStatsLogger; +import com.android.server.pm.ext.PackageExtInit; +import com.android.server.pm.ext.PackageHooksRegistry; import com.android.server.pm.permission.PermissionMigrationHelper; import com.android.server.pm.permission.PermissionMigrationHelperImpl; import com.android.server.pm.verify.domain.DomainVerificationService; @@ -689,6 +693,11 @@ public static void main(String[] args) { new SystemServer().run(); } + static { + PackageImpl.packageParsingHooksSupplier = PackageHooksRegistry::getParsingHooks; + ParsingPackageUtils.packageExtInitSupplier = PackageExtInit::new; + } + public SystemServer() { // Check for factory test mode. mFactoryTestMode = FactoryTest.getMode(); From bdde01bf9aec82207b1e3f816f550450da605a76 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Fri, 28 Mar 2025 19:11:30 +0200 Subject: [PATCH 065/332] SettingsProvider: add support for further restriction of setting access By default, Settings.{Global,Secure,System} that aren't annotated with `@Readable` are readable only by preinstalled apps (with some exceptions, see enforceSettingReadable()). Settings.{Global,Secure} settings are writable only by apps that hold the privileged WRITE_SECURE_SETTINGS permission. Settings.System are also writable by apps that hold the WRITE_SETTINGS app-op permission (it's surfaced as "Modify system settings" in the UI). This commit adds `@Protected` setting annotation, which allows to further restrict settings access by specifying which system apps are allowed to read and/or write them. --- core/java/android/provider/Settings.java | 80 ++++++++++++-- .../providers/settings/SettingsProvider.java | 103 +++++++++++++++++- 2 files changed, 169 insertions(+), 14 deletions(-) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 845fba5c6fbb5..71105d035498b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -60,6 +60,7 @@ import android.database.ContentObserver; import android.database.Cursor; import android.database.SQLException; +import android.ext.KnownSystemPackage; import android.location.ILocationManager; import android.location.LocationManager; import android.media.AudioManager; @@ -3533,7 +3534,9 @@ private NameValueCache(Uri uri, String getCommand, mAllFields = new ArraySet<>(); mReadableFieldsWithMaxTargetSdk = new ArrayMap<>(); getPublicSettingsForClass(callerClass, mAllFields, mReadableFields, - mReadableFieldsWithMaxTargetSdk); + mReadableFieldsWithMaxTargetSdk, + // skip obtaining protectedSettings map, it's not needed for NameValueCache + null); } public boolean putStringForUser(ContentResolver cr, String name, String value, @@ -4045,9 +4048,54 @@ public static boolean canDrawOverlays(Context context) { int maxTargetSdk() default 0; } + @Target({ ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + private @interface Protected { + // read() and readWrite() are required to be empty if immutableValue is non-empty + String immutableValue() default ""; + // Ignored if immutableValue is non-empty + boolean restrictReads() default true; + // IDs of system packages that are allowed read-only access. Should be empty if + // immutableValue is non-empty or restrictReads is false. + @KnownSystemPackage.Enum int[] read() default {}; + // IDs of system packages that are allowed read and write access. Should be empty if + // immutableValue is non-empty + @KnownSystemPackage.Enum int[] readWrite() default {}; + } + + /** @hide */ + public record ProtectedSetting(String key, + @Nullable String immutableValue, + boolean restrictReads, + @KnownSystemPackage.Enum int[] readableBy, + @KnownSystemPackage.Enum int[] readWritableBy) { + + static ProtectedSetting fromAnnotation(String key, Protected anno) { + int[] readableBy = anno.read(); + int[] readWritableBy = anno.readWrite(); + + String immutableValue = anno.immutableValue(); + if (!immutableValue.isEmpty()) { + if (readableBy.length != 0 || readWritableBy.length != 0) { + throw new IllegalArgumentException(key); + } + return new ProtectedSetting(key, immutableValue, false, readableBy, readWritableBy); + } + + boolean restrictReads = anno.restrictReads(); + if (!restrictReads && readableBy.length != 0) { + throw new IllegalArgumentException(key); + } + + return new ProtectedSetting(key, null, anno.restrictReads(), + readableBy, readWritableBy); + } + } + private static void getPublicSettingsForClass( Class callerClass, Set allKeys, Set readableKeys, - ArrayMap keysWithMaxTargetSdk) { + ArrayMap keysWithMaxTargetSdk, + @Nullable ArrayMap protectedSettings) { final Field[] allFields = callerClass.getDeclaredFields(); try { for (int i = 0; i < allFields.length; i++) { @@ -4059,17 +4107,24 @@ private static void getPublicSettingsForClass( if (!value.getClass().equals(String.class)) { continue; } - allKeys.add((String) value); + final String key = (String) value; + allKeys.add(key); final Readable annotation = field.getAnnotation(Readable.class); if (annotation != null) { - final String key = (String) value; final int maxTargetSdk = annotation.maxTargetSdk(); readableKeys.add(key); if (maxTargetSdk != 0) { keysWithMaxTargetSdk.put(key, maxTargetSdk); } } + + if (protectedSettings != null) { + final Protected anno = field.getAnnotation(Protected.class); + if (anno != null) { + protectedSettings.put(key, ProtectedSetting.fromAnnotation(key, anno)); + } + } } } catch (IllegalAccessException ignored) { } @@ -4292,9 +4347,10 @@ public static void clearProviderForTest() { /** @hide */ public static void getPublicSettings(Set allKeys, Set readableKeys, - ArrayMap readableKeysWithMaxTargetSdk) { + ArrayMap readableKeysWithMaxTargetSdk, + ArrayMap protectedSettings) { getPublicSettingsForClass(System.class, allKeys, readableKeys, - readableKeysWithMaxTargetSdk); + readableKeysWithMaxTargetSdk, protectedSettings); } /** @@ -7179,9 +7235,10 @@ public static void clearProviderForTest() { /** @hide */ public static void getPublicSettings(Set allKeys, Set readableKeys, - ArrayMap readableKeysWithMaxTargetSdk) { + ArrayMap readableKeysWithMaxTargetSdk, + ArrayMap protectedSettings) { getPublicSettingsForClass(Secure.class, allKeys, readableKeys, - readableKeysWithMaxTargetSdk); + readableKeysWithMaxTargetSdk, protectedSettings); } /** @@ -18671,14 +18728,15 @@ public static void clearProviderForTest() { /** @hide */ public static void getPublicSettings(Set allKeys, Set readableKeys, - ArrayMap readableKeysWithMaxTargetSdk) { + ArrayMap readableKeysWithMaxTargetSdk, + ArrayMap protectedSettings) { getPublicSettingsForClass(Global.class, allKeys, readableKeys, - readableKeysWithMaxTargetSdk); + readableKeysWithMaxTargetSdk, protectedSettings); // Add Global.Wearable keys on watches. if (ActivityThread.currentApplication().getApplicationContext().getPackageManager() .hasSystemFeature(PackageManager.FEATURE_WATCH)) { getPublicSettingsForClass(Global.Wearable.class, allKeys, readableKeys, - readableKeysWithMaxTargetSdk); + readableKeysWithMaxTargetSdk, protectedSettings); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 70160baa98f46..4b2d770bb7772 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -75,6 +75,7 @@ import android.database.MatrixCursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; +import android.ext.KnownSystemPackages; import android.hardware.camera2.utils.ArrayUtils; import android.media.AudioManager; import android.media.IRingtonePlayer; @@ -346,27 +347,33 @@ public class SettingsProvider extends ContentProvider { private static final Set sReadableSecureSettings = new ArraySet<>(); private static final ArrayMap sReadableSecureSettingsWithMaxTargetSdk = new ArrayMap<>(); + private static final ArrayMap sProtectedSecureSettings = + new ArrayMap<>(); static { Settings.Secure.getPublicSettings(sAllSecureSettings, sReadableSecureSettings, - sReadableSecureSettingsWithMaxTargetSdk); + sReadableSecureSettingsWithMaxTargetSdk, sProtectedSecureSettings); } private static final Set sAllSystemSettings = new ArraySet<>(); private static final Set sReadableSystemSettings = new ArraySet<>(); private static final ArrayMap sReadableSystemSettingsWithMaxTargetSdk = new ArrayMap<>(); + private static final ArrayMap sProtectedSystemSettings = + new ArrayMap<>(); static { Settings.System.getPublicSettings(sAllSystemSettings, sReadableSystemSettings, - sReadableSystemSettingsWithMaxTargetSdk); + sReadableSystemSettingsWithMaxTargetSdk, sProtectedSystemSettings); } private static final Set sAllGlobalSettings = new ArraySet<>(); private static final Set sReadableGlobalSettings = new ArraySet<>(); private static final ArrayMap sReadableGlobalSettingsWithMaxTargetSdk = new ArrayMap<>(); + private static final ArrayMap sProtectedGlobalSettings = + new ArrayMap<>(); static { Settings.Global.getPublicSettings(sAllGlobalSettings, sReadableGlobalSettings, - sReadableGlobalSettingsWithMaxTargetSdk); + sReadableGlobalSettingsWithMaxTargetSdk, sProtectedGlobalSettings); } private final Object mLock = new Object(); @@ -1556,6 +1563,8 @@ private boolean mutateGlobalSetting(String name, String value, String tag, // Make sure the caller can change the settings - treated as secure. enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS); + checkProtectedSettingAccess(/* isRead */ false, name, sProtectedGlobalSettings); + // Resolve the userId on whose behalf the call is made. final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); @@ -1841,6 +1850,8 @@ private boolean mutateSecureSetting(String name, String value, String tag, // Make sure the caller can change the settings. enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS); + checkProtectedSettingAccess(/* isRead */ false, name, sProtectedSecureSettings); + // Resolve the userId on whose behalf the call is made. final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); @@ -2005,6 +2016,8 @@ private boolean mutateSystemSetting(String name, String value, String tag, int r } } + checkProtectedSettingAccess(/* isRead */ false, name, sProtectedSystemSettings); + // Resolve the userId on whose behalf the call is made. final int callingUserId = resolveCallingUserIdEnforcingPermissions(runAsUserId); @@ -2294,6 +2307,8 @@ private List getSettingsNamesLocked(int settingsType, int userId) { } private void enforceSettingReadable(String settingName, int settingsType, int userId) { + checkProtectedSettingAccess(/* isRead */ true, settingName, getProtectedSettings(settingsType)); + if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) { return; } @@ -2336,6 +2351,61 @@ private void enforceSettingReadable(String settingName, int settingsType, int us } } + private void checkProtectedSettingAccess(boolean isRead, String name, ArrayMap map) { + Settings.ProtectedSetting protSetting = map.get(name); + if (protSetting == null) { + return; + } + if (isRead && !protSetting.restrictReads()) { + return; + } + if (Binder.getCallingPid() == Process.myPid()) { + // SettingsProvider runs in system_server process + return; + } + String callingPackage = getCallingPackage(); + if (callingPackage == null) { + if (Build.IS_DEBUGGABLE) { + if (Binder.getCallingUid() == ROOT_UID) { + Slog.d(LOG_TAG, "allowed root to access protected setting " + protSetting.key()); + return; + } + } + throw new SecurityException("callingPackage is null when accessing protected setting " + protSetting.key()); + } + var ksp = KnownSystemPackages.get(requireContext()); + + for (int id : protSetting.readWritableBy()) { + if (callingPackage.equals(ksp.getById(id))) { + return; + } + } + if (isRead) { + for (int id : protSetting.readableBy()) { + if (callingPackage.equals(ksp.getById(id))) { + return; + } + } + } + + if (ksp.shell.equals(callingPackage)) { + // ADB is used for testing + Slog.d(LOG_TAG, "allowed shell to access protected setting " + protSetting.key()); + return; + } + + throw new SecurityException(callingPackage + " is not allowed to access protected setting " + protSetting.key()); + } + + private static ArrayMap getProtectedSettings(int settingsType) { + return switch (settingsType) { + case SETTINGS_TYPE_GLOBAL -> sProtectedGlobalSettings; + case SETTINGS_TYPE_SECURE -> sProtectedSecureSettings; + case SETTINGS_TYPE_SYSTEM -> sProtectedSystemSettings; + default -> throw new IllegalArgumentException(); + }; + } + /** * Check if the target settings key is readable. Reject if the caller app is trying to access a * settings key defined in the Settings.Secure, Settings.System or Settings.Global and is not @@ -3347,6 +3417,33 @@ public boolean ensureSettingsForUserLocked(int userId) { // Upgrade the settings to the latest version. UpgradeController upgrader = new UpgradeController(userId); upgrader.upgradeIfNeededLocked(); + + // Init immutable settings + int[] typesToInit = userId == UserHandle.USER_SYSTEM ? + new int[] { SETTINGS_TYPE_GLOBAL, SETTINGS_TYPE_SECURE, SETTINGS_TYPE_SYSTEM, } : + new int[] { SETTINGS_TYPE_SECURE, }; + + for (int type : typesToInit) { + SettingsState settingsState = SettingsRegistry.this.getSettingsLocked(type, userId); + ArrayMap protectedSettings = getProtectedSettings(type); + String typeStr = SettingsState.settingTypeToString(type); + if (type == SETTINGS_TYPE_SECURE) { + typeStr += " (user " + userId + ")"; + } + for (int i = 0; i < protectedSettings.size(); ++i) { + Settings.ProtectedSetting setting = protectedSettings.valueAt(i); + String immutableValue = setting.immutableValue(); + if (immutableValue == null) { + continue; + } + settingsState.insertSettingLocked(setting.key(), immutableValue, + /* tag */ null, /* makeDefault */ false, + SettingsState.SYSTEM_PACKAGE_NAME); + + Slog.d(LOG_TAG, typeStr + ": initialized " + setting.key() + + " with immutable value " + immutableValue); + } + } return true; } From a6d35832341a358107ee1d777b27f52e578bebfa Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 31 Jul 2022 15:29:26 +0300 Subject: [PATCH 066/332] gosps: support for per-app GrapheneOS-specific persistent state Squashed with 0a99fc28522c93149f6d4d4e611ffc1d78a4d7e1 by quh4gko8 <88831734+quh4gko8@users.noreply.github.com> --- core/api/system-current.txt | 60 +++ core/java/Android.bp | 5 + .../android/app/ActivityManagerInternal.java | 3 + core/java/android/app/ActivityThread.java | 7 + .../java/android/app/ActivityThreadHooks.java | 8 + core/java/android/app/AppBindArgs.java | 1 + core/java/android/app/IApplicationThread.aidl | 2 + .../android/content/pm/GosPackageState.aidl | 3 + .../android/content/pm/GosPackageState.java | 344 ++++++++++++++++ .../content/pm/GosPackageStateFlag.java | 70 ++++ .../android/content/pm/IPackageManager.aidl | 4 + core/java/android/ext/DerivedPackageFlag.java | 53 +++ .../content/pm/PackageManagerInternal.java | 3 + .../server/am/ActivityManagerService.java | 8 + .../com/android/server/am/ProcessList.java | 22 + .../server/ext/PackageManagerHooks.java | 15 + .../server/pm/DeletePackageHelper.java | 11 + .../server/pm/GosPackageStatePermission.java | 245 ++++++++++++ .../server/pm/GosPackageStatePermissions.java | 231 +++++++++++ .../server/pm/GosPackageStatePersistence.java | 121 ++++++ .../server/pm/GosPackageStatePmHooks.java | 377 ++++++++++++++++++ .../server/pm/GosPackageStateUtils.java | 38 ++ .../server/pm/PackageManagerService.java | 29 ++ .../server/pm/PackageManagerShellCommand.java | 2 + .../com/android/server/pm/PackageSetting.java | 23 +- .../java/com/android/server/pm/Settings.java | 16 +- .../android/server/pm/SharedUserSetting.java | 65 +++ .../server/pm/pkg/PackageUserState.java | 6 + .../pm/pkg/PackageUserStateDefault.java | 7 + .../server/pm/pkg/PackageUserStateImpl.java | 16 + .../pm/pkg/PackageUserStateInternal.java | 4 + .../android/server/pm/pkg/SharedUserApi.java | 2 + .../pm/PackageManagerSettingsTests.java | 17 +- 33 files changed, 1815 insertions(+), 3 deletions(-) create mode 100644 core/java/android/content/pm/GosPackageState.aidl create mode 100644 core/java/android/content/pm/GosPackageState.java create mode 100644 core/java/android/content/pm/GosPackageStateFlag.java create mode 100644 core/java/android/ext/DerivedPackageFlag.java create mode 100644 services/core/java/com/android/server/pm/GosPackageStatePermission.java create mode 100644 services/core/java/com/android/server/pm/GosPackageStatePermissions.java create mode 100644 services/core/java/com/android/server/pm/GosPackageStatePersistence.java create mode 100644 services/core/java/com/android/server/pm/GosPackageStatePmHooks.java create mode 100644 services/core/java/com/android/server/pm/GosPackageStateUtils.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 7036838ef5951..b69983e982b74 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4060,6 +4060,46 @@ package android.content.pm { method @NonNull public final int getType(); } + public final class GosPackageState implements android.os.Parcelable { + method @NonNull public android.content.pm.GosPackageState.Editor createEditor(@NonNull String, @NonNull android.os.UserHandle); + method @NonNull public android.content.pm.GosPackageState.Editor createEditor(@NonNull String, int); + method public int describeContents(); + method @NonNull public static android.content.pm.GosPackageState.Editor edit(@NonNull String, @NonNull android.os.UserHandle); + method @NonNull public static android.content.pm.GosPackageState.Editor edit(@NonNull String, int); + method @NonNull public static android.content.pm.GosPackageState get(@NonNull String, @NonNull android.os.UserHandle); + method @NonNull public static android.content.pm.GosPackageState get(@NonNull String, int); + method @NonNull public static android.content.pm.GosPackageState getForSelf(@NonNull android.content.Context); + method public boolean hasDerivedFlag(int); + method public boolean hasDerivedFlags(int); + method public boolean hasFlag(int); + method public boolean hasPackageFlag(int); + method public boolean isNone(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field @Nullable public final byte[] contactScopes; + field @Nullable public final byte[] storageScopes; + } + + public static class GosPackageState.Editor { + method @NonNull public android.content.pm.GosPackageState.Editor addFlag(int); + method @NonNull public android.content.pm.GosPackageState.Editor addPackageFlag(int); + method public boolean apply(); + method @NonNull public android.content.pm.GosPackageState.Editor clearFlag(int); + method @NonNull public android.content.pm.GosPackageState.Editor clearPackageFlag(int); + method @NonNull public android.content.pm.GosPackageState.Editor killUidAfterApply(); + method @NonNull public android.content.pm.GosPackageState.Editor setContactScopes(@Nullable byte[]); + method @NonNull public android.content.pm.GosPackageState.Editor setFlagState(int, boolean); + method @NonNull public android.content.pm.GosPackageState.Editor setKillUidAfterApply(boolean); + method @NonNull public android.content.pm.GosPackageState.Editor setNotifyUidAfterApply(boolean); + method @NonNull public android.content.pm.GosPackageState.Editor setPackageFlagState(int, boolean); + method @NonNull public android.content.pm.GosPackageState.Editor setStorageScopes(@Nullable byte[]); + } + + public interface GosPackageStateFlag { + field public static final int CONTACT_SCOPES_ENABLED = 5; // 0x5 + field public static final int STORAGE_SCOPES_ENABLED = 0; // 0x0 + } + public final class InstallationFile { method public long getLengthBytes(); method public int getLocation(); @@ -4836,6 +4876,26 @@ package android.ext { field public static final int HAS_GMSCORE_CLIENT_LIBRARY = 0; // 0x0 } + public interface DerivedPackageFlag { + field public static final int DFLAGS_SET = 1; // 0x1 + field public static final int EXPECTS_ACCESS_TO_MEDIA_FILES_ONLY = 4; // 0x4 + field public static final int EXPECTS_ALL_FILES_ACCESS = 2; // 0x2 + field public static final int EXPECTS_LEGACY_EXTERNAL_STORAGE = 8192; // 0x2000 + field public static final int EXPECTS_STORAGE_WRITE_ACCESS = 8; // 0x8 + field public static final int HAS_ACCESS_MEDIA_LOCATION_DECLARATION = 256; // 0x100 + field public static final int HAS_GET_ACCOUNTS_DECLARATION = 4194304; // 0x400000 + field public static final int HAS_MANAGE_EXTERNAL_STORAGE_DECLARATION = 64; // 0x40 + field public static final int HAS_MANAGE_MEDIA_DECLARATION = 128; // 0x80 + field public static final int HAS_READ_CONTACTS_DECLARATION = 1048576; // 0x100000 + field public static final int HAS_READ_EXTERNAL_STORAGE_DECLARATION = 16; // 0x10 + field public static final int HAS_READ_MEDIA_AUDIO_DECLARATION = 512; // 0x200 + field public static final int HAS_READ_MEDIA_IMAGES_DECLARATION = 1024; // 0x400 + field public static final int HAS_READ_MEDIA_VIDEO_DECLARATION = 2048; // 0x800 + field public static final int HAS_READ_MEDIA_VISUAL_USER_SELECTED_DECLARATION = 4096; // 0x1000 + field public static final int HAS_WRITE_CONTACTS_DECLARATION = 2097152; // 0x200000 + field public static final int HAS_WRITE_EXTERNAL_STORAGE_DECLARATION = 32; // 0x20 + } + public interface KnownSystemPackage { field public static final int SETTINGS = 0; // 0x0 field public static final int SHELL = 1; // 0x1 diff --git a/core/java/Android.bp b/core/java/Android.bp index 7123ee7b5777f..ac727f164c80f 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -203,6 +203,11 @@ filegroup { visibility: ["//frameworks/base/test-mock"], } +filegroup { + name: "GosPackageStateFlags", + srcs: [ "android/content/pm/GosPackageStateFlag.java", ], +} + filegroup { name: "libincident_aidl", srcs: [ diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index e5f7889859c14..fdcbbdc678d4c 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -36,6 +36,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ActivityPresentationInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; import android.content.pm.IPackageDataObserver; import android.content.pm.UserInfo; import android.net.Uri; @@ -1385,4 +1386,6 @@ public abstract void getExecutableMethodFileOffsets(@NonNull String processName, * @hide */ public abstract void addCreatorToken(Intent intent, String creatorPackage); + + public abstract void onGosPackageStateChanged(int uid, GosPackageState state); } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 09ff67b343249..04d4b175122c7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -92,6 +92,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; +import android.content.pm.GosPackageState; import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; @@ -2305,6 +2306,12 @@ public void getExecutableMethodFileOffsets( throw e.rethrowFromSystemServer(); } } + + @Override + public void onGosPackageStateChanged(GosPackageState state) { + // this is a oneway method, caller (ActivityManager) will not be blocked + ActivityThreadHooks.onGosPackageStateChanged(mInitialApplication, state, false); + } } private @NonNull SafeCancellationTransport createSafeCancellationTransport( diff --git a/core/java/android/app/ActivityThreadHooks.java b/core/java/android/app/ActivityThreadHooks.java index cbec05808c88a..17e07a6382d90 100644 --- a/core/java/android/app/ActivityThreadHooks.java +++ b/core/java/android/app/ActivityThreadHooks.java @@ -1,6 +1,8 @@ package android.app; +import android.annotation.Nullable; import android.content.Context; +import android.content.pm.GosPackageState; import android.os.Bundle; import android.os.Process; import android.os.RemoteException; @@ -50,7 +52,13 @@ static Bundle onBind(Context appContext) { // of app's code // ActivityThread.handleBindApplication static void onBind2(Context appContext, Bundle appBindArgs) { + GosPackageState gosPs = appBindArgs.getParcelable(AppBindArgs.KEY_GOS_PACKAGE_STATE, + GosPackageState.class); + onGosPackageStateChanged(appContext, gosPs, true); + } + // called from both main and worker threads + static void onGosPackageStateChanged(Context ctx, GosPackageState state, boolean fromBind) { } static Service instantiateService(String className) { diff --git a/core/java/android/app/AppBindArgs.java b/core/java/android/app/AppBindArgs.java index bd411cd4232aa..14a4900033631 100644 --- a/core/java/android/app/AppBindArgs.java +++ b/core/java/android/app/AppBindArgs.java @@ -2,6 +2,7 @@ /** @hide */ public interface AppBindArgs { + String KEY_GOS_PACKAGE_STATE = "gosPs"; String KEY_FLAGS_ARRAY = "flagsArr"; int FLAGS_ARRAY_LEN = 10; diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 063501bf82a20..adaf9650c7e69 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -187,4 +187,6 @@ oneway interface IApplicationThread { void schedulePing(in RemoteCallback pong); void getExecutableMethodFileOffsets(in MethodDescriptor methodDescriptor, in IOffsetCallback resultCallback); + + void onGosPackageStateChanged(in android.content.pm.GosPackageState state); } diff --git a/core/java/android/content/pm/GosPackageState.aidl b/core/java/android/content/pm/GosPackageState.aidl new file mode 100644 index 0000000000000..c7a48e85d0783 --- /dev/null +++ b/core/java/android/content/pm/GosPackageState.aidl @@ -0,0 +1,3 @@ +package android.content.pm; + +parcelable GosPackageState; diff --git a/core/java/android/content/pm/GosPackageState.java b/core/java/android/content/pm/GosPackageState.java new file mode 100644 index 0000000000000..91daf0b273226 --- /dev/null +++ b/core/java/android/content/pm/GosPackageState.java @@ -0,0 +1,344 @@ +package android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.UserIdInt; +import android.app.ActivityThread; +import android.app.PropertyInvalidatedCache; +import android.content.Context; +import android.ext.DerivedPackageFlag; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.UserHandle; +import android.permission.PermissionManager; + +import java.util.Arrays; +import java.util.Objects; + +/** + * GrapheneOS-specific persistent package state, stored in per-user PackageUserState. + *

+ * GosPackageState has a special handling for sharedUid packages. All packages in a given sharedUid + * share the same GosPackageState. This was done because in some cases (e.g. when an app accesses + * MediaProvider via FUSE) there's no way to retrieve the package name, only UID is available. + * Manually merging GosPackageStates of sharedUid members would be too complex. + * + * @hide + */ +@SystemApi +public final class GosPackageState implements Parcelable { + /** @hide */ public final long flagStorage1; + // flags that have package-specific meaning + /** @hide */ public final long packageFlagStorage; + @Nullable + public final byte[] storageScopes; + @Nullable + public final byte[] contactScopes; + /** + * These flags are lazily derived from persistent state. They are intentionally skipped from + * equals() and hashCode(). derivedFlags are stored here for performance reasons, to avoid + * performing separate IPC to fetch them. + *

+ * Note that calculation of derived flags is skipped unless a GosPackageState flag is set that + * depends on derived flags, @see {@link com.android.server.pm.GosPackageStatePmHooks#maybeDeriveFlags} + *

+ * If package is part of sharedUid, then its derivedFlags are calculated across all + * sharedUid member packages. See GosPackageState javadoc for reasoning. + * @hide + */ + @DerivedPackageFlag.Enum public int derivedFlags; + + /** @hide */ public static final GosPackageState DEFAULT = createEmpty(); + + /** + * A sentinel value that is returned when the package is not installed (e.g. when it was racily + * uninstalled) and when the caller doesn't have access to the actual GosPackageState. + * + * @hide + */ + public static final GosPackageState NONE = createEmpty(); + + /** @hide */ + public GosPackageState(long flagStorage1, long packageFlagStorage, + @Nullable byte[] storageScopes, @Nullable byte[] contactScopes) { + this.flagStorage1 = flagStorage1; + this.packageFlagStorage = packageFlagStorage; + this.storageScopes = storageScopes; + this.contactScopes = contactScopes; + } + + private static GosPackageState createEmpty() { + return new GosPackageState(0L, 0L, null, null); + } + + private static final int TYPE_NONE = 0; + private static final int TYPE_DEFAULT = 1; + private static final int TYPE_REGULAR = 2; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + int type = TYPE_REGULAR; + if (this == DEFAULT) { + type = TYPE_DEFAULT; + } else if (this == NONE) { + type = TYPE_NONE; + } + dest.writeInt(type); + if (type != TYPE_REGULAR) { + return; + } + dest.writeLong(this.flagStorage1); + dest.writeLong(this.packageFlagStorage); + dest.writeByteArray(storageScopes); + dest.writeByteArray(contactScopes); + dest.writeInt(derivedFlags); + } + + @NonNull + public static final Creator CREATOR = new Creator<>() { + @Override + public GosPackageState createFromParcel(Parcel in) { + switch (in.readInt()) { + case TYPE_DEFAULT: return DEFAULT; + case TYPE_NONE: return NONE; + }; + var res = new GosPackageState(in.readLong(), in.readLong(), + in.createByteArray(), in.createByteArray()); + res.derivedFlags = in.readInt(); + return res; + } + + @Override + public GosPackageState[] newArray(int size) { + return new GosPackageState[size]; + } + }; + + @Override + public int hashCode() { + return Long.hashCode(flagStorage1) + Arrays.hashCode(storageScopes) + Arrays.hashCode(contactScopes) + Long.hashCode(packageFlagStorage); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof GosPackageState o)) { + return false; + } + if (this == o) { + return true; + } + if (flagStorage1 != o.flagStorage1) { + return false; + } + if (!Arrays.equals(storageScopes, o.storageScopes)) { + return false; + } + if (!Arrays.equals(contactScopes, o.contactScopes)) { + return false; + } + if (packageFlagStorage != o.packageFlagStorage) { + return false; + } + return true; + } + + @Override + public int describeContents() { + return 0; + } + + public boolean hasFlag(@GosPackageStateFlag.Enum int flag) { + return (this.flagStorage1 & (1L << flag)) != 0; + } + + public boolean hasPackageFlag(int packageFlag) { + return (this.packageFlagStorage & (1L << packageFlag)) != 0; + } + + public boolean hasDerivedFlag(@DerivedPackageFlag.Enum int flag) { + return (derivedFlags & flag) != 0; + } + + public boolean hasDerivedFlags(@DerivedPackageFlag.Enum int flags) { + return (derivedFlags & flags) == flags; + } + + /** @see #NONE */ + public boolean isNone() { + return this == NONE; + } + + @NonNull + public static GosPackageState getForSelf(@NonNull Context context) { + return get(context.getPackageName(), context.getUserId()); + } + + @NonNull + @SuppressLint("UserHandleName") + public static GosPackageState get(@NonNull String packageName, @NonNull UserHandle user) { + return get(packageName, user.getIdentifier()); + } + + @NonNull + public static GosPackageState get(@NonNull String packageName, @UserIdInt int userId) { + return Objects.requireNonNull(sCache.query(new CacheQuery(packageName, userId))); + } + + private record CacheQuery(String packageName, int userId) {} + + // invalidated by PackageManager#invalidatePackageInfoCache() (e.g. when + // PackageManagerService#setGosPackageState succeeds) + private static volatile PropertyInvalidatedCache sCache = + new PropertyInvalidatedCache<>(256, PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE, + "getGosPackageState") { + @Override + public GosPackageState recompute(CacheQuery query) { + try { + return ActivityThread.getPackageManager().getGosPackageState(query.packageName, query.userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + }; + + @NonNull + @SuppressLint("UserHandleName") + public Editor createEditor(@NonNull String packageName, @NonNull UserHandle user) { + return createEditor(packageName, user.getIdentifier()); + } + + @NonNull + public Editor createEditor(@NonNull String packageName, @UserIdInt int userId) { + return new Editor(this, packageName, userId); + } + + @NonNull + @SuppressLint("UserHandleName") + public static Editor edit(@NonNull String packageName, @NonNull UserHandle user) { + return edit(packageName, user.getIdentifier()); + } + + @NonNull + public static Editor edit(@NonNull String packageName, @UserIdInt int userId) { + return GosPackageState.get(packageName, userId).createEditor(packageName, userId); + } + + /** @hide */ public static final int EDITOR_FLAG_KILL_UID_AFTER_APPLY = 1; + /** @hide */ public static final int EDITOR_FLAG_NOTIFY_UID_AFTER_APPLY = 1 << 1; + + public static class Editor { + private final String packageName; + private final int userId; + private long flagStorage1; + private long packageFlagStorage; + private byte[] storageScopes; + private byte[] contactScopes; + private int editorFlags; + + /** @hide */ + public Editor(GosPackageState s, String packageName, int userId) { + this.packageName = packageName; + this.userId = userId; + this.flagStorage1 = s.flagStorage1; + this.packageFlagStorage = s.packageFlagStorage; + this.storageScopes = s.storageScopes; + this.contactScopes = s.contactScopes; + } + + @NonNull + public Editor setFlagState(@GosPackageStateFlag.Enum int flag, boolean state) { + if (state) { + addFlag(flag); + } else { + clearFlag(flag); + } + return this; + } + + @NonNull + public Editor addFlag(@GosPackageStateFlag.Enum int flag) { + this.flagStorage1 |= (1L << flag); + return this; + } + + @NonNull + public Editor clearFlag(@GosPackageStateFlag.Enum int flag) { + this.flagStorage1 &= ~(1L << flag); + return this; + } + + @NonNull + public Editor addPackageFlag(int flag) { + this.packageFlagStorage |= (1L << flag); + return this; + } + + @NonNull + public Editor clearPackageFlag(int flag) { + this.packageFlagStorage &= ~(1L << flag); + return this; + } + + @NonNull + public Editor setPackageFlagState(int flag, boolean state) { + if (state) { + addPackageFlag(flag); + } else { + clearPackageFlag(flag); + } + return this; + } + + @NonNull + public Editor setStorageScopes(@Nullable byte[] storageScopes) { + this.storageScopes = storageScopes; + return this; + } + + @NonNull + public Editor setContactScopes(@Nullable byte[] contactScopes) { + this.contactScopes = contactScopes; + return this; + } + + @NonNull + public Editor killUidAfterApply() { + return setKillUidAfterApply(true); + } + + @NonNull + public Editor setKillUidAfterApply(boolean v) { + if (v) { + this.editorFlags |= EDITOR_FLAG_KILL_UID_AFTER_APPLY; + } else { + this.editorFlags &= ~EDITOR_FLAG_KILL_UID_AFTER_APPLY; + } + return this; + } + + @NonNull + public Editor setNotifyUidAfterApply(boolean v) { + if (v) { + this.editorFlags |= EDITOR_FLAG_NOTIFY_UID_AFTER_APPLY; + } else { + this.editorFlags &= ~EDITOR_FLAG_NOTIFY_UID_AFTER_APPLY; + } + return this; + } + + // Returns true if the update was successfully applied and is scheduled to be written back + // to storage. Actual writeback is performed asynchronously. + public boolean apply() { + try { + return ActivityThread.getPackageManager().setGosPackageState(packageName, userId, + new GosPackageState(flagStorage1, packageFlagStorage, storageScopes, contactScopes), + editorFlags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/core/java/android/content/pm/GosPackageStateFlag.java b/core/java/android/content/pm/GosPackageStateFlag.java new file mode 100644 index 0000000000000..21abe5f9398d0 --- /dev/null +++ b/core/java/android/content/pm/GosPackageStateFlag.java @@ -0,0 +1,70 @@ +package android.content.pm; + +import android.annotation.IntDef; +import android.annotation.SystemApi; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** @hide */ +@SystemApi +public interface GosPackageStateFlag { + /* SysApi */ int STORAGE_SCOPES_ENABLED = 0; + /** @hide */ int ALLOW_ACCESS_TO_OBB_DIRECTORY = 1; + // flags 2, 3, 4 were used previously, do not reuse them + /* SysApi */ int CONTACT_SCOPES_ENABLED = 5; + /** @hide */ int BLOCK_NATIVE_DEBUGGING_NON_DEFAULT = 6; + /** @hide */ int BLOCK_NATIVE_DEBUGGING = 7; + /** @hide */ int BLOCK_NATIVE_DEBUGGING_SUPPRESS_NOTIF = 8; + /** @hide */ int RESTRICT_MEMORY_DYN_CODE_LOADING_NON_DEFAULT = 9; + /** @hide */ int RESTRICT_MEMORY_DYN_CODE_LOADING = 10; + /** @hide */ int RESTRICT_MEMORY_DYN_CODE_LOADING_SUPPRESS_NOTIF = 11; + /** @hide */ int RESTRICT_STORAGE_DYN_CODE_LOADING_NON_DEFAULT = 12; + /** @hide */ int RESTRICT_STORAGE_DYN_CODE_LOADING = 13; + /** @hide */ int RESTRICT_STORAGE_DYN_CODE_LOADING_SUPPRESS_NOTIF = 14; + /** @hide */ int RESTRICT_WEBVIEW_DYN_CODE_LOADING_NON_DEFAULT = 15; + /** @hide */ int RESTRICT_WEBVIEW_DYN_CODE_LOADING = 16; + /** @hide */ int USE_HARDENED_MALLOC_NON_DEFAULT = 17; + /** @hide */ int USE_HARDENED_MALLOC = 18; + /** @hide */ int USE_EXTENDED_VA_SPACE_NON_DEFAULT = 19; + /** @hide */ int USE_EXTENDED_VA_SPACE = 20; + /** @hide */ int FORCE_MEMTAG_NON_DEFAULT = 21; + /** @hide */ int FORCE_MEMTAG = 22; + /** @hide */ int FORCE_MEMTAG_SUPPRESS_NOTIF = 23; + /** @hide */ int ENABLE_EXPLOIT_PROTECTION_COMPAT_MODE = 24; + // flag 25 was used previously, do not reuse it + /** @hide */ int PLAY_INTEGRITY_API_USED_AT_LEAST_ONCE = 26; + /** @hide */ int SUPPRESS_PLAY_INTEGRITY_API_NOTIF = 27; + /** @hide */ int BLOCK_PLAY_INTEGRITY_API = 28; + + /** @hide */ + @IntDef(value = { + STORAGE_SCOPES_ENABLED, + ALLOW_ACCESS_TO_OBB_DIRECTORY, + CONTACT_SCOPES_ENABLED, + BLOCK_NATIVE_DEBUGGING_NON_DEFAULT, + BLOCK_NATIVE_DEBUGGING, + BLOCK_NATIVE_DEBUGGING_SUPPRESS_NOTIF, + RESTRICT_MEMORY_DYN_CODE_LOADING_NON_DEFAULT, + RESTRICT_MEMORY_DYN_CODE_LOADING, + RESTRICT_MEMORY_DYN_CODE_LOADING_SUPPRESS_NOTIF, + RESTRICT_STORAGE_DYN_CODE_LOADING_NON_DEFAULT, + RESTRICT_STORAGE_DYN_CODE_LOADING, + RESTRICT_STORAGE_DYN_CODE_LOADING_SUPPRESS_NOTIF, + RESTRICT_WEBVIEW_DYN_CODE_LOADING_NON_DEFAULT, + RESTRICT_WEBVIEW_DYN_CODE_LOADING, + USE_HARDENED_MALLOC_NON_DEFAULT, + USE_HARDENED_MALLOC, + USE_EXTENDED_VA_SPACE_NON_DEFAULT, + USE_EXTENDED_VA_SPACE, + FORCE_MEMTAG_NON_DEFAULT, + FORCE_MEMTAG, + FORCE_MEMTAG_SUPPRESS_NOTIF, + ENABLE_EXPLOIT_PROTECTION_COMPAT_MODE, + PLAY_INTEGRITY_API_USED_AT_LEAST_ONCE, + SUPPRESS_PLAY_INTEGRITY_API_NOTIF, + BLOCK_PLAY_INTEGRITY_API, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Enum {} +} diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index bd13e8023c09e..30422d52398e9 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -873,4 +873,8 @@ interface IPackageManager { List getAllApexDirectories(); @nullable Bundle getExtraAppBindArgs(String packageName); + + android.content.pm.GosPackageState getGosPackageState(String packageName, int userId); + + boolean setGosPackageState(String packageName, int userId, in android.content.pm.GosPackageState updatedPs, int editorFlags); } diff --git a/core/java/android/ext/DerivedPackageFlag.java b/core/java/android/ext/DerivedPackageFlag.java new file mode 100644 index 0000000000000..5258acf924327 --- /dev/null +++ b/core/java/android/ext/DerivedPackageFlag.java @@ -0,0 +1,53 @@ +package android.ext; + +import android.annotation.IntDef; +import android.annotation.SystemApi; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** @hide */ +@SystemApi +public interface DerivedPackageFlag { + // to distinguish between the case when no dflags are set and the case when dflags weren't calculated yet + int DFLAGS_SET = 1; + int EXPECTS_ALL_FILES_ACCESS = 1 << 1; + int EXPECTS_ACCESS_TO_MEDIA_FILES_ONLY = 1 << 2; + int EXPECTS_STORAGE_WRITE_ACCESS = 1 << 3; + int HAS_READ_EXTERNAL_STORAGE_DECLARATION = 1 << 4; + int HAS_WRITE_EXTERNAL_STORAGE_DECLARATION = 1 << 5; + int HAS_MANAGE_EXTERNAL_STORAGE_DECLARATION = 1 << 6; + int HAS_MANAGE_MEDIA_DECLARATION = 1 << 7; + int HAS_ACCESS_MEDIA_LOCATION_DECLARATION = 1 << 8; + int HAS_READ_MEDIA_AUDIO_DECLARATION = 1 << 9; + int HAS_READ_MEDIA_IMAGES_DECLARATION = 1 << 10; + int HAS_READ_MEDIA_VIDEO_DECLARATION = 1 << 11; + int HAS_READ_MEDIA_VISUAL_USER_SELECTED_DECLARATION = 1 << 12; + int EXPECTS_LEGACY_EXTERNAL_STORAGE = 1 << 13; + int HAS_READ_CONTACTS_DECLARATION = 1 << 20; + int HAS_WRITE_CONTACTS_DECLARATION = 1 << 21; + int HAS_GET_ACCOUNTS_DECLARATION = 1 << 22; + + /** @hide */ + @IntDef(flag = true, value = { + DFLAGS_SET, + EXPECTS_ALL_FILES_ACCESS, + EXPECTS_ACCESS_TO_MEDIA_FILES_ONLY, + EXPECTS_STORAGE_WRITE_ACCESS, + HAS_READ_EXTERNAL_STORAGE_DECLARATION, + HAS_WRITE_EXTERNAL_STORAGE_DECLARATION, + HAS_MANAGE_EXTERNAL_STORAGE_DECLARATION, + HAS_MANAGE_MEDIA_DECLARATION, + HAS_ACCESS_MEDIA_LOCATION_DECLARATION, + HAS_READ_MEDIA_AUDIO_DECLARATION, + HAS_READ_MEDIA_IMAGES_DECLARATION, + HAS_READ_MEDIA_VIDEO_DECLARATION, + HAS_READ_MEDIA_VISUAL_USER_SELECTED_DECLARATION, + EXPECTS_LEGACY_EXTERNAL_STORAGE, + HAS_READ_CONTACTS_DECLARATION, + HAS_WRITE_CONTACTS_DECLARATION, + HAS_GET_ACCOUNTS_DECLARATION, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Enum {} +} diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index b0dae6a1f306e..e5543f8a237cf 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1442,4 +1442,7 @@ public abstract void sendPackageDataClearedBroadcast(@NonNull String packageName * Returns true if the device is upgrading from an SDK version lower than the one specified. */ public abstract boolean isUpgradingFromLowerThan(int sdkVersion); + + @NonNull + public abstract GosPackageState getGosPackageState(String packageName, int userId); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f908514993957..abd7edcddd618 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -287,6 +287,7 @@ import android.content.pm.ActivityPresentationInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy; +import android.content.pm.GosPackageState; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageManager; import android.content.pm.IncrementalStatesInfo; @@ -18214,6 +18215,13 @@ public void getExecutableMethodFileOffsets(@NonNull String processName, public void addCreatorToken(Intent intent, String creatorPackage) { ActivityManagerService.this.addCreatorToken(intent, creatorPackage); } + + @Override + public void onGosPackageStateChanged(int uid, GosPackageState state) { + synchronized (mProcLock) { + mProcessList.onGosPackageStateChangedLOSP(uid, state); + } + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index ad47e67b93327..7e54b9745cbea 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -89,6 +89,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -3644,6 +3645,27 @@ void handleAllTrustStorageUpdateLOSP() { } } + @GuardedBy(anyOf = {"mService", "mProcLock"}) + void onGosPackageStateChangedLOSP(int uid, GosPackageState state) { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { + ProcessRecord r = mLruProcesses.get(i); + if (r.uid != uid) { + // isolated and "sdk sandbox" processes are skipped intentionally (they run in + // separate UIDs) + continue; + } + final IApplicationThread thread = r.getThread(); + if (thread != null) { + try { + thread.onGosPackageStateChanged(state); + } catch (RemoteException ex) { + Slog.d(TAG, "onGosPackageStateChanged failed; uid " + uid + + ", processName " + r.info.processName); + } + } + } + } + @GuardedBy({"mService", "mProcLock"}) private int offerLruProcessInternalLSP(ProcessRecord app, long now, String what, Object obj, ProcessRecord srcApp) { diff --git a/services/core/java/com/android/server/ext/PackageManagerHooks.java b/services/core/java/com/android/server/ext/PackageManagerHooks.java index 8fba0b51ee8d7..9cd50fd5b6cb1 100644 --- a/services/core/java/com/android/server/ext/PackageManagerHooks.java +++ b/services/core/java/com/android/server/ext/PackageManagerHooks.java @@ -4,6 +4,9 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppBindArgs; +import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; +import android.content.pm.GosPackageStateFlag; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Binder; @@ -13,12 +16,15 @@ import android.util.ArraySet; import com.android.server.pm.Computer; +import com.android.server.pm.GosPackageStatePmHooks; import com.android.server.pm.PackageManagerService; import com.android.server.pm.permission.Permission; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.parsing.ParsingPackage; +import static java.util.Objects.requireNonNull; + public class PackageManagerHooks { // Called when package enabled setting for a system package is deserialized from storage @@ -62,9 +68,18 @@ public static Bundle getExtraAppBindArgs(PackageManagerService pm, String packag // isSystem() remains true even if isUpdatedSystemApp() is true final boolean isUserApp = !pkgState.isSystem(); + GosPackageState unfilteredGosPs = pkgState.getUserStateOrDefault(userId).getGosPackageState(); + // GosPackageState that is filtered for the target app + GosPackageState gosPs = GosPackageStatePmHooks.getFiltered(pmComputer, pkgState, unfilteredGosPs, + callingUid, callingPid, userId); + + ApplicationInfo appInfo = + requireNonNull(pmComputer.getApplicationInfo(packageName, 0L, userId)); + int[] flagsArr = new int[AppBindArgs.FLAGS_ARRAY_LEN]; var b = new Bundle(); + b.putParcelable(AppBindArgs.KEY_GOS_PACKAGE_STATE, gosPs); b.putIntArray(AppBindArgs.KEY_FLAGS_ARRAY, flagsArr); return b; diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index bbee77ce58ad0..c01031ec1f371 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -43,6 +43,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.Flags; +import android.content.pm.GosPackageState; import android.content.pm.IPackageDeleteObserver2; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -622,6 +623,15 @@ private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user ? 0 : ps.getUserStateOrDefault(nextUserId).getFirstInstallTimeMillis(); + // Preserve GosPackageState if archiving an app. + final android.content.pm.GosPackageState gosPackageState; + if ((flags & (PackageManager.DELETE_KEEP_DATA | PackageManager.DELETE_ARCHIVE)) + == (PackageManager.DELETE_KEEP_DATA | PackageManager.DELETE_ARCHIVE)) { + gosPackageState = ps.getUserStateOrDefault(nextUserId).getGosPackageState(); + } else { + gosPackageState = GosPackageState.DEFAULT; + } + ps.setUserState(nextUserId, ps.getCeDataInode(nextUserId), ps.getDeDataInode(nextUserId), @@ -630,6 +640,7 @@ private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user true /*stopped*/, true /*notLaunched*/, false /*hidden*/, + gosPackageState, 0 /*distractionFlags*/, null /*suspendParams*/, false /*instantApp*/, diff --git a/services/core/java/com/android/server/pm/GosPackageStatePermission.java b/services/core/java/com/android/server/pm/GosPackageStatePermission.java new file mode 100644 index 0000000000000..1013ec825b420 --- /dev/null +++ b/services/core/java/com/android/server/pm/GosPackageStatePermission.java @@ -0,0 +1,245 @@ +package com.android.server.pm; + +import android.annotation.AppIdInt; +import android.annotation.IntDef; +import android.content.pm.GosPackageState; +import android.content.pm.GosPackageStateFlag; +import android.os.Build; +import android.os.UserHandle; +import android.util.Slog; + +import com.android.server.pm.pkg.PackageStateInternal; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import static com.android.internal.util.Preconditions.checkState; + +class GosPackageStatePermission { + static final String TAG = "GosPackageStatePermission"; + + // bitmask of flags that can be read/written + private final long readFlagStorage1; + private final long writeFlagStorage1; + + static final int FIELD_STORAGE_SCOPES = 0; + static final int FIELD_CONTACT_SCOPES = 1; + static final int FIELD_PACKAGE_FLAGS = 2; + + @IntDef(prefix = "FIELD_", value = { + FIELD_STORAGE_SCOPES, FIELD_CONTACT_SCOPES, FIELD_PACKAGE_FLAGS + }) + @Retention(RetentionPolicy.SOURCE) + @interface Field {} + + // bitmask of fields that can be read/written + private final int readFields; + private final int writeFields; + + private final int crossUserPermissions; + + static final int ALLOW_CROSS_USER_PROFILE_READS = 0; + static final int ALLOW_CROSS_USER_PROFILE_WRITES = 1; + static final int ALLOW_CROSS_ANY_USER_READS = 2; + static final int ALLOW_CROSS_ANY_USER_WRITES = 3; + + @IntDef(prefix = "ALLOW_CROSS_", value = { + ALLOW_CROSS_USER_PROFILE_READS, ALLOW_CROSS_USER_PROFILE_WRITES, + ALLOW_CROSS_ANY_USER_READS, ALLOW_CROSS_ANY_USER_WRITES, + }) + @interface CrossUserPermission {} + + private GosPackageStatePermission(long readFlagStorage1, long writeFlagStorage1, + int readFields, int writeFields, + int crossUserPermissions) { + this.readFlagStorage1 = readFlagStorage1; + this.writeFlagStorage1 = writeFlagStorage1; + this.readFields = readFields; + this.writeFields = writeFields; + this.crossUserPermissions = crossUserPermissions; + } + + static GosPackageStatePermission createFull() { + return new GosPackageStatePermission(-1, -1, -1, -1, -1); + } + + boolean canReadField(@Field int field) { + return (readFields & (1 << field)) != 0; + } + + boolean canWriteField(@Field int field) { + return (writeFields & (1 << field)) != 0; + } + + boolean hasCrossUserPermission(@CrossUserPermission int perm) { + return (crossUserPermissions & (1 << perm)) != 0; + } + + static class Builder { + private long readFlagStorage1; + private long writeFlagStorage1; + + private int readFields; + private int writeFields; + + private int crossUserPerms; + + Builder readFlag(@GosPackageStateFlag.Enum int flag) { + readFlagStorage1 |= (1L << flag); + return this; + } + + Builder readFlags(@GosPackageStateFlag.Enum int... flags) { + for (int flag : flags) { + readFlagStorage1 |= (1L << flag); + } + return this; + } + + Builder readWriteFlag(@GosPackageStateFlag.Enum int flag) { + readFlagStorage1 |= (1L << flag); + writeFlagStorage1 |= (1L << flag); + return this; + } + + Builder readWriteFlags(@GosPackageStateFlag.Enum int... flags) { + for (int flag : flags) { + readFlagStorage1 |= (1L << flag); + writeFlagStorage1 |= (1L << flag); + } + return this; + } + + Builder readField(@Field int field) { + readFields |= (1 << field); + return this; + } + + Builder readFields(@Field int... fields) { + for (int field : fields) { + readFields |= (1 << field); + } + return this; + } + + Builder readWriteField(@Field int field) { + readFields |= (1 << field); + writeFields |= (1 << field); + return this; + } + + Builder readWriteFields(@Field int... fields) { + for (int field : fields) { + readFields |= (1 << field); + writeFields |= (1 << field); + } + return this; + } + + Builder crossUserPermission(@CrossUserPermission int perm) { + crossUserPerms |= (1 << perm); + return this; + } + + Builder crossUserPermissions(@CrossUserPermission int... perms) { + for (int perm : perms) { + crossUserPerms |= (1 << perm); + } + return this; + } + + GosPackageStatePermission create() { + return new GosPackageStatePermission(readFlagStorage1, writeFlagStorage1, readFields, writeFields, + crossUserPerms); + } + + void apply(String pkgName, Computer computer) { + PackageStateInternal psi = computer.getPackageStateInternal(pkgName); + if (psi == null || !psi.isSystem()) { + String msg = pkgName + " is not a system package"; + Slog.d(TAG, msg); + if (Build.IS_DEBUGGABLE) { + throw new IllegalStateException(msg); + } + return; + } + apply(psi.getAppId()); + } + + void apply(@AppIdInt int appId) { + if (Build.IS_DEBUGGABLE) { + checkState(GosPackageStatePermissions.grantedPermissions.get(appId) == null); + } + GosPackageStatePermissions.grantedPermissions.put(appId, create()); + } + } + + boolean canWrite() { + return writeFlagStorage1 != 0L || writeFields != 0L; + } + + boolean checkCrossUserPermissions(int callingUid, int targetUserId, boolean forWrite) { + int callingUserId = UserHandle.getUserId(callingUid); + + if (targetUserId == callingUserId) { + // caller and target are in the same userId + return true; + } + + final int crossAnyUserFlag = forWrite? + ALLOW_CROSS_ANY_USER_WRITES : ALLOW_CROSS_ANY_USER_READS; + + if (hasCrossUserPermission(crossAnyUserFlag)) { + // caller is allowed to access any user + return true; + } + + final int crossProfileFlag = forWrite? + ALLOW_CROSS_USER_PROFILE_WRITES : ALLOW_CROSS_USER_PROFILE_READS; + + if (hasCrossUserPermission(crossProfileFlag)) { + if (GosPackageStatePermissions.userManager.getProfileParentId(targetUserId) == callingUserId) { + // caller is allowed to access its child profile + return true; + } + } + + Slog.d(TAG, "not allowed to access userId " + targetUserId + " from uid " + callingUid); + return false; + } + + GosPackageState filterRead(GosPackageState ps) { + var default_ = GosPackageState.DEFAULT; + var res = new GosPackageState(ps.flagStorage1 & readFlagStorage1 + , canReadField(FIELD_PACKAGE_FLAGS) ? ps.packageFlagStorage : default_.packageFlagStorage + , canReadField(FIELD_STORAGE_SCOPES) ? ps.storageScopes : default_.storageScopes + , canReadField(FIELD_CONTACT_SCOPES) ? ps.contactScopes : default_.contactScopes + ); + if (default_.equals(res)) { + return default_; + } + // derivedFlags are intentionally not filtered, see its javadoc + res.derivedFlags = ps.derivedFlags; + return res; + } + + GosPackageState filterWrite(GosPackageState current, GosPackageState update) { + long flagStorage1 = (current.flagStorage1 & ~writeFlagStorage1) | (update.flagStorage1 & writeFlagStorage1); + + // flags that can't be unset + long oneWayFlags1 = (1L << GosPackageStateFlag.PLAY_INTEGRITY_API_USED_AT_LEAST_ONCE); + flagStorage1 |= current.flagStorage1 & oneWayFlags1; + + var res = new GosPackageState( + flagStorage1 + , canWriteField(FIELD_PACKAGE_FLAGS) ? update.packageFlagStorage : current.packageFlagStorage + , canWriteField(FIELD_STORAGE_SCOPES) ? update.storageScopes : current.storageScopes + , canWriteField(FIELD_CONTACT_SCOPES) ? update.contactScopes : current.contactScopes + ); + var default_ = GosPackageState.DEFAULT; + if (default_.equals(res)) { + return default_; + } + return res; + } +} diff --git a/services/core/java/com/android/server/pm/GosPackageStatePermissions.java b/services/core/java/com/android/server/pm/GosPackageStatePermissions.java new file mode 100644 index 0000000000000..60d9093a8bc51 --- /dev/null +++ b/services/core/java/com/android/server/pm/GosPackageStatePermissions.java @@ -0,0 +1,231 @@ +package com.android.server.pm; + +import android.annotation.Nullable; +import android.app.PropertyInvalidatedCache; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.GosPackageStateFlag; +import android.ext.KnownSystemPackages; +import android.os.Build; +import android.os.Process; +import android.os.UserHandle; +import android.permission.PermissionManager; +import android.util.EmptyArray; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.gmscompat.GmsCompatApp; +import com.android.server.LocalServices; + +import java.util.Objects; + +import static android.content.pm.GosPackageStateFlag.ALLOW_ACCESS_TO_OBB_DIRECTORY; +import static android.content.pm.GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING; +import static android.content.pm.GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING_NON_DEFAULT; +import static android.content.pm.GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING_SUPPRESS_NOTIF; +import static android.content.pm.GosPackageStateFlag.BLOCK_PLAY_INTEGRITY_API; +import static android.content.pm.GosPackageStateFlag.CONTACT_SCOPES_ENABLED; +import static android.content.pm.GosPackageStateFlag.ENABLE_EXPLOIT_PROTECTION_COMPAT_MODE; +import static android.content.pm.GosPackageStateFlag.FORCE_MEMTAG; +import static android.content.pm.GosPackageStateFlag.FORCE_MEMTAG_NON_DEFAULT; +import static android.content.pm.GosPackageStateFlag.FORCE_MEMTAG_SUPPRESS_NOTIF; +import static android.content.pm.GosPackageStateFlag.PLAY_INTEGRITY_API_USED_AT_LEAST_ONCE; +import static android.content.pm.GosPackageStateFlag.RESTRICT_MEMORY_DYN_CODE_LOADING; +import static android.content.pm.GosPackageStateFlag.RESTRICT_MEMORY_DYN_CODE_LOADING_NON_DEFAULT; +import static android.content.pm.GosPackageStateFlag.RESTRICT_MEMORY_DYN_CODE_LOADING_SUPPRESS_NOTIF; +import static android.content.pm.GosPackageStateFlag.RESTRICT_STORAGE_DYN_CODE_LOADING; +import static android.content.pm.GosPackageStateFlag.RESTRICT_STORAGE_DYN_CODE_LOADING_NON_DEFAULT; +import static android.content.pm.GosPackageStateFlag.RESTRICT_STORAGE_DYN_CODE_LOADING_SUPPRESS_NOTIF; +import static android.content.pm.GosPackageStateFlag.RESTRICT_WEBVIEW_DYN_CODE_LOADING; +import static android.content.pm.GosPackageStateFlag.RESTRICT_WEBVIEW_DYN_CODE_LOADING_NON_DEFAULT; +import static android.content.pm.GosPackageStateFlag.STORAGE_SCOPES_ENABLED; +import static android.content.pm.GosPackageStateFlag.SUPPRESS_PLAY_INTEGRITY_API_NOTIF; +import static android.content.pm.GosPackageStateFlag.USE_EXTENDED_VA_SPACE; +import static android.content.pm.GosPackageStateFlag.USE_EXTENDED_VA_SPACE_NON_DEFAULT; +import static android.content.pm.GosPackageStateFlag.USE_HARDENED_MALLOC; +import static android.content.pm.GosPackageStateFlag.USE_HARDENED_MALLOC_NON_DEFAULT; +import static com.android.server.pm.GosPackageStatePermission.ALLOW_CROSS_USER_PROFILE_READS; +import static com.android.server.pm.GosPackageStatePermission.ALLOW_CROSS_USER_PROFILE_WRITES; +import static com.android.server.pm.GosPackageStatePermission.FIELD_CONTACT_SCOPES; +import static com.android.server.pm.GosPackageStatePermission.FIELD_PACKAGE_FLAGS; +import static com.android.server.pm.GosPackageStatePermission.FIELD_STORAGE_SCOPES; + +class GosPackageStatePermissions { + private static final String TAG = "GosPackageStatePermissions"; + // Permission that each package has for accessing its own GosPackageState + private static GosPackageStatePermission selfAccessPermission; + private static GosPackageStatePermission fullPermission; + private static int myPid; + // Maps app's appId to its permission. + // Written only during PackageManager init, no need to synchronize reads + static SparseArray grantedPermissions; + static UserManagerInternal userManager; + + static final int UNKNOWN_CALLING_PID = 0; + + static void init(PackageManagerService pm) { + myPid = Process.myPid(); + + @GosPackageStateFlag.Enum int[] playIntegrityFlags = { + PLAY_INTEGRITY_API_USED_AT_LEAST_ONCE, + BLOCK_PLAY_INTEGRITY_API, + SUPPRESS_PLAY_INTEGRITY_API_NOTIF, + }; + + selfAccessPermission = builder() + .readFlags(STORAGE_SCOPES_ENABLED, ALLOW_ACCESS_TO_OBB_DIRECTORY, + CONTACT_SCOPES_ENABLED) + .readFlags(playIntegrityFlags) + .readWriteFlag(PLAY_INTEGRITY_API_USED_AT_LEAST_ONCE) + .create(); + + grantedPermissions = new SparseArray<>(); + + GosPackageStatePermission full = GosPackageStatePermission.createFull(); + fullPermission = full; + + grantedPermissions.put(Process.SHELL_UID, full); + if (Build.isDebuggable()) { + // for root adb + grantedPermissions.put(Process.ROOT_UID, full); + } + Computer computer = pm.snapshotComputer(); + + KnownSystemPackages ksp = KnownSystemPackages.get(pm.getContext()); + builder() + .readFlag(STORAGE_SCOPES_ENABLED) + .readField(FIELD_STORAGE_SCOPES) + .apply(ksp.mediaProvider, computer); + builder() + .readFlag(CONTACT_SCOPES_ENABLED) + .readField(FIELD_CONTACT_SCOPES) + .apply(ksp.contactsProvider, computer); + builder() + .readFlags(STORAGE_SCOPES_ENABLED, CONTACT_SCOPES_ENABLED) + // user profiles are handled by the launcher instance in profile parent user + .crossUserPermission(ALLOW_CROSS_USER_PROFILE_READS) + .apply(ksp.launcher, computer); + builder() + .readWriteFlags(STORAGE_SCOPES_ENABLED, CONTACT_SCOPES_ENABLED) + .readWriteFields(FIELD_STORAGE_SCOPES, FIELD_CONTACT_SCOPES, + FIELD_PACKAGE_FLAGS) + // in some cases PermissionController handles user profile from profile parent user + .crossUserPermission(ALLOW_CROSS_USER_PROFILE_READS) + .apply(ksp.permissionController, computer); + builder() + .readFlags(playIntegrityFlags) + .readWriteFlag(SUPPRESS_PLAY_INTEGRITY_API_NOTIF) + .apply(GmsCompatApp.PKG_NAME, computer); + + @GosPackageStateFlag.Enum int[] settingsReadWriteFlags = { + ALLOW_ACCESS_TO_OBB_DIRECTORY, + BLOCK_NATIVE_DEBUGGING_NON_DEFAULT, + BLOCK_NATIVE_DEBUGGING, + BLOCK_NATIVE_DEBUGGING_SUPPRESS_NOTIF, + RESTRICT_MEMORY_DYN_CODE_LOADING_NON_DEFAULT, + RESTRICT_MEMORY_DYN_CODE_LOADING, + RESTRICT_MEMORY_DYN_CODE_LOADING_SUPPRESS_NOTIF, + RESTRICT_STORAGE_DYN_CODE_LOADING_NON_DEFAULT, + RESTRICT_STORAGE_DYN_CODE_LOADING, + RESTRICT_STORAGE_DYN_CODE_LOADING_SUPPRESS_NOTIF, + RESTRICT_WEBVIEW_DYN_CODE_LOADING_NON_DEFAULT, + RESTRICT_WEBVIEW_DYN_CODE_LOADING, + USE_HARDENED_MALLOC_NON_DEFAULT, + USE_HARDENED_MALLOC, + USE_EXTENDED_VA_SPACE_NON_DEFAULT, + USE_EXTENDED_VA_SPACE, + FORCE_MEMTAG_NON_DEFAULT, + FORCE_MEMTAG, + FORCE_MEMTAG_SUPPRESS_NOTIF, + ENABLE_EXPLOIT_PROTECTION_COMPAT_MODE, + }; + builder() + .readWriteFlags(settingsReadWriteFlags) + .readWriteFlags(playIntegrityFlags) + .readFlags(STORAGE_SCOPES_ENABLED, CONTACT_SCOPES_ENABLED) + .readFields(FIELD_PACKAGE_FLAGS) + .crossUserPermission(ALLOW_CROSS_USER_PROFILE_READS) + .crossUserPermission(ALLOW_CROSS_USER_PROFILE_WRITES) + // note that this applies to all packages that run in the android.uid.system sharedUserId, + // not just the Settings app. + .apply(ksp.settings, computer); + + userManager = Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class)); + + registerDevModeReceiver(pm); + } + + @Nullable + static GosPackageStatePermission get(int callingUid, int callingPid, int targetAppId, int targetUserId, boolean forWrite) { + if (callingUid == android.os.Process.SYSTEM_UID && callingPid == myPid) { + return fullPermission; + } + int callingAppId = UserHandle.getAppId(callingUid); + GosPackageStatePermission permission = grantedPermissions.get(callingAppId); + + if (permission == null) { + if (targetAppId == callingAppId) { + permission = selfAccessPermission; + } else { + Slog.d(TAG, "uid " + callingUid + " doesn't have permission to " + + "access GosPackageState of other packages"); + return null; + } + } + if (forWrite && !permission.canWrite()) { + return null; + } + if (!permission.checkCrossUserPermissions(callingUid, targetUserId, forWrite)) { + return null; + } + return permission; + } + + private static void registerDevModeReceiver(PackageManagerService pm) { + if (!Build.IS_DEBUGGABLE) { + return; + } + + var receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) { + builder() + .readFlags(flagsArr(i, "readFlags")) + .readWriteFlags(flagsArr(i, "readWriteFlags")) + .readFields(intArr(i, "readFields")) + .readWriteFields(intArr(i, "readWriteFields")) + .crossUserPermissions(intArr(i, "crossUserPermissions")) + .apply(i.getStringExtra("pkgName"), pm.snapshotComputer()); + + PackageManagerService.invalidatePackageInfoCache(); + Slog.d(TAG, "granted permission " + i.getExtras()); + } + + private static int[] flagsArr(Intent intent, String name) { + String[] strings = intent.getStringArrayExtra(name); + if (strings == null) { + return EmptyArray.INT; + } + + int[] res = new int[strings.length]; + for (int i = 0; i < strings.length; ++i) { + res[i] = GosPackageStateUtils.parseFlag(strings[i]); + } + return res; + } + + private static int[] intArr(Intent i, String name) { + int[] res = i.getIntArrayExtra(name); + return res != null ? res : EmptyArray.INT; + } + }; + pm.getContext().registerReceiver(receiver, new IntentFilter("GosPackageState.grant_permission"), + Context.RECEIVER_EXPORTED); + } + + private static GosPackageStatePermission.Builder builder() { + return new GosPackageStatePermission.Builder(); + } +} diff --git a/services/core/java/com/android/server/pm/GosPackageStatePersistence.java b/services/core/java/com/android/server/pm/GosPackageStatePersistence.java new file mode 100644 index 0000000000000..ee90810b457cc --- /dev/null +++ b/services/core/java/com/android/server/pm/GosPackageStatePersistence.java @@ -0,0 +1,121 @@ +package com.android.server.pm; + +import android.annotation.Nullable; +import android.content.pm.GosPackageState; +import android.content.pm.GosPackageStateFlag; +import android.util.Slog; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.pm.pkg.PackageUserStateInternal; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class GosPackageStatePersistence { + private static final String TAG = "GosPackageStatePersistence"; + + public static final String TAG_GOS_PACKAGE_STATE = "GrapheneOS-package-state"; + private static final String ATTR_FLAG_STORAGE_1 = "flags-1"; + private static final String ATTR_PACKAGE_FLAG_STORAGE = "package-flags"; + private static final String ATTR_STORAGE_SCOPES = "storage-scopes"; + private static final String ATTR_CONTACT_SCOPES = "contact-scopes"; + + /** @see Settings#writePackageRestrictions */ + static void serialize(PackageUserStateInternal packageUserState, TypedXmlSerializer serializer) throws IOException { + GosPackageState ps = packageUserState.getGosPackageState(); + if (GosPackageState.DEFAULT.equals(ps)) { + return; + } + serializer.startTag(null, TAG_GOS_PACKAGE_STATE); + serializeInner(ps, serializer); + serializer.endTag(null, TAG_GOS_PACKAGE_STATE); + } + + private static void serializeInner(GosPackageState ps, TypedXmlSerializer serializer) throws IOException { + long flagStorage1 = ps.flagStorage1; + if (flagStorage1 != 0L) { + serializer.attributeLong(null, ATTR_FLAG_STORAGE_1, flagStorage1); + } + if (ps.hasFlag(GosPackageStateFlag.STORAGE_SCOPES_ENABLED)) { + byte[] s = ps.storageScopes; + if (s != null) { + serializer.attributeBytesHex(null, ATTR_STORAGE_SCOPES, s); + } + } + if (ps.hasFlag(GosPackageStateFlag.CONTACT_SCOPES_ENABLED)) { + byte[] s = ps.contactScopes; + if (s != null) { + serializer.attributeBytesHex(null, ATTR_CONTACT_SCOPES, s); + } + } + long packageFlagStorage = ps.packageFlagStorage; + if (packageFlagStorage != 0L) { + serializer.attributeLong(null, ATTR_PACKAGE_FLAG_STORAGE, ps.packageFlagStorage); + } + } + + static GosPackageState deserialize(TypedXmlPullParser parser) throws XmlPullParserException { + long flagStorage1 = 0L; + long packageFlagStorage = 0L; + byte[] storageScopes = null; + byte[] contactScopes = null; + + for (int i = 0, numAttr = parser.getAttributeCount(); i < numAttr; ++i) { + String attr = parser.getAttributeName(i); + switch (attr) { + case ATTR_FLAG_STORAGE_1 -> + flagStorage1 = parser.getAttributeLong(i); + case ATTR_PACKAGE_FLAG_STORAGE -> + packageFlagStorage = parser.getAttributeLong(i); + case ATTR_STORAGE_SCOPES -> + storageScopes = parser.getAttributeBytesHex(i); + case ATTR_CONTACT_SCOPES -> + contactScopes = parser.getAttributeBytesHex(i); + default -> + Slog.e(TAG, "deserialize: unknown attribute " + attr); + } + } + return new GosPackageState(flagStorage1, packageFlagStorage, storageScopes, contactScopes); + } + + // Compatibility with legacy serialized GosPackageState. + @Nullable + static GosPackageState maybeDeserializeLegacy(TypedXmlPullParser parser) { + int legacyFlags = parser.getAttributeInt(null, "GrapheneOS-flags", 0); + if (legacyFlags == 0) { + return null; + } + long flagStorage1 = migrateLegacyFlags(legacyFlags); + long packageFlagStorage = parser.getAttributeLong(null, "GrapheneOS-package-flags", 0L); + byte[] storageScopes = parser.getAttributeBytesHex(null, "GrapheneOS-storage-scopes", null); + byte[] contactScopes = parser.getAttributeBytesHex(null, "GrapheneOS-contact-scopes", null); + return new GosPackageState(flagStorage1, packageFlagStorage, storageScopes, contactScopes); + } + + private static long migrateLegacyFlags(int flags) { + final int FLAG_DISABLE_HARDENED_MALLOC = 1 << 2; + if ((flags & FLAG_DISABLE_HARDENED_MALLOC) != 0) { + flags &= ~(1 << GosPackageStateFlag.USE_HARDENED_MALLOC); + flags |= (1 << GosPackageStateFlag.USE_HARDENED_MALLOC_NON_DEFAULT); + } + + final int FLAG_ENABLE_COMPAT_VA_39_BIT = 1 << 3; + if ((flags & FLAG_ENABLE_COMPAT_VA_39_BIT) != 0) { + flags &= ~(1 << GosPackageStateFlag.USE_EXTENDED_VA_SPACE); + flags |= (1 << GosPackageStateFlag.USE_EXTENDED_VA_SPACE_NON_DEFAULT); + } + + final int FLAG_DO_NOT_SHOW_RELAX_APP_HARDENING_NOTIFICATION = 1 << 4; + final int FLAG_HAS_PACKAGE_FLAGS = 1 << 25; + + final int unusedFlags = FLAG_DISABLE_HARDENED_MALLOC + | FLAG_ENABLE_COMPAT_VA_39_BIT + | FLAG_DO_NOT_SHOW_RELAX_APP_HARDENING_NOTIFICATION + | FLAG_HAS_PACKAGE_FLAGS; + + // clear unused flags + return flags & ~unusedFlags; + } +} diff --git a/services/core/java/com/android/server/pm/GosPackageStatePmHooks.java b/services/core/java/com/android/server/pm/GosPackageStatePmHooks.java new file mode 100644 index 0000000000000..b461bd67c1e01 --- /dev/null +++ b/services/core/java/com/android/server/pm/GosPackageStatePmHooks.java @@ -0,0 +1,377 @@ +package com.android.server.pm; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.content.pm.GosPackageState; +import android.content.pm.GosPackageStateFlag; +import android.ext.KnownSystemPackages; +import android.ext.DerivedPackageFlag; +import android.os.Binder; +import android.os.Process; +import android.os.RemoteException; +import android.os.ShellCommand; +import android.os.UserHandle; +import android.util.Slog; + +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.server.LocalServices; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateInternal; +import com.android.server.pm.pkg.SharedUserApi; +import com.android.server.utils.Slogf; + +import java.util.List; + +import static android.content.pm.GosPackageState.*; +import static com.android.server.pm.GosPackageStateUtils.parseFlag; + +public class GosPackageStatePmHooks { + private static final String TAG = "GosPackageStatePmHooks"; + + static void init(PackageManagerService pm) { + GosPackageStatePermissions.init(pm); + } + + @NonNull + public static GosPackageState getUnfiltered(PackageManagerService pm, String packageName, int userId) { + return getUnfiltered(pm.snapshotComputer(), packageName, userId); + } + + @NonNull + public static GosPackageState getUnfiltered(Computer snapshot, String packageName, int userId) { + PackageStateInternal psi = snapshot.getPackageStates().get(packageName); + if (psi == null) { + return NONE; + } + return psi.getUserStateOrDefault(userId).getGosPackageState(); + } + + @NonNull + public static GosPackageState getFiltered(PackageManagerService pm, int callingUid, int callingPid, + String packageName, int userId) { + Computer pmComputer = pm.snapshotComputer(); + PackageStateInternal packageState = pmComputer.getPackageStates().get(packageName); + if (packageState == null) { + // the package was likely racily uninstalled + return NONE; + } + return getFiltered(pmComputer, packageState, + packageState.getUserStateOrDefault(userId).getGosPackageState(), + callingUid, callingPid, userId); + } + + @NonNull + public static GosPackageState getFiltered(Computer pmComputer, + PackageStateInternal packageState, GosPackageState gosPs, + int callingUid, int callingPid, int userId) { + final int appId = packageState.getAppId(); + + GosPackageStatePermission permission = GosPackageStatePermissions.get(callingUid, callingPid, appId, userId, false); + if (permission == null) { + return NONE; + } + + maybeDeriveFlags(pmComputer, gosPs, packageState); + return permission.filterRead(gosPs); + } + + static boolean set(PackageManagerService pm, + final int callingUid, final int callingPid, + String packageName, int userId, + GosPackageState update, int editorFlags) { + final int appId; + + synchronized (pm.mLock) { + PackageSetting packageSetting = pm.mSettings.getPackageLPr(packageName); + if (packageSetting == null) { + Slogf.d(TAG, "set: no packageSetting for %s", packageName); + return false; + } + + appId = packageSetting.getAppId(); + + // Packages with this appId use the "android.uid.system" sharedUserId, which is expensive + // to deal with due to the large number of packages that it includes (see GosPackageState + // doc). These packages have no need for GosPackageState. + if (appId == Process.SYSTEM_UID) { + Slogf.d(TAG, "set: appId of %s == SYSTEM_UID", packageName); + return false; + } + + GosPackageStatePermission permission = GosPackageStatePermissions.get( + callingUid, callingPid, appId, userId, true); + + if (permission == null) { + Slog.d(TAG, "no write permission"); + return false; + } + + PackageUserStateInternal userState = packageSetting.getUserStates().get(userId); + if (userState == null) { + Slog.d(TAG, "no user state"); + return false; + } + + GosPackageState currentGosPs = userState.getGosPackageState(); + GosPackageState updatedGosPs = permission.filterWrite(currentGosPs, update); + + SharedUserSetting sharedUser = pm.mSettings.getSharedUserSettingLPr(packageSetting); + + if (sharedUser != null) { + List sharedPkgs = sharedUser.getPackages(); + + // see GosPackageState doc + for (AndroidPackage sharedPkg : sharedPkgs) { + PackageSetting sharedPkgSetting = pm.mSettings.getPackageLPr(sharedPkg.getPackageName()); + if (sharedPkgSetting != null) { + sharedPkgSetting.setGosPackageState(userId, updatedGosPs); + } + } + } else { + packageSetting.setGosPackageState(userId, updatedGosPs); + } + + // will invalidate app-side caches (GosPackageState.sCache) + pm.scheduleWritePackageRestrictions(userId); + } + + if ((editorFlags & EDITOR_FLAG_KILL_UID_AFTER_APPLY) != 0) { + final long token = Binder.clearCallingIdentity(); + try { + // important to call outside the 'synchronized (pm.mLock)' section, may deadlock otherwise + ActivityManager.getService().killUid(appId, userId, "GosPackageState"); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + if ((editorFlags & EDITOR_FLAG_NOTIFY_UID_AFTER_APPLY) != 0) { + int uid = UserHandle.getUid(userId, appId); + + // get GosPackageState as the target app + GosPackageState ps = getFiltered(pm, uid, GosPackageStatePermissions.UNKNOWN_CALLING_PID, packageName, userId); + + final long token = Binder.clearCallingIdentity(); + try { + var am = LocalServices.getService(ActivityManagerInternal.class); + am.onGosPackageStateChanged(uid, ps); + } finally { + Binder.restoreCallingIdentity(token); + } + } + return true; + } + + private static void maybeDeriveFlags(Computer snapshot, GosPackageState gosPs, PackageStateInternal pkgState) { + if ((gosPs.derivedFlags & DerivedPackageFlag.DFLAGS_SET) != 0) { + return; + } + + if (!gosPs.hasFlag(GosPackageStateFlag.STORAGE_SCOPES_ENABLED) && !gosPs.hasFlag(GosPackageStateFlag.CONTACT_SCOPES_ENABLED)) { + return; + } + + AndroidPackageInternal pkg = pkgState.getPkg(); + if (pkg == null) { + // see AndroidPackage.pkg javadoc for an explanation + return; + } + + SharedUserApi sharedUser = null; + if (pkgState.hasSharedUser()) { + sharedUser = snapshot.getSharedUser(pkgState.getSharedUserAppId()); + } + + int flags; + if (sharedUser != null) { + flags = 0; + for (AndroidPackage sharedPkg : sharedUser.getPackages()) { + // see GosPackageState doc + flags = deriveFlags(flags, sharedPkg); + } + } else { + flags = deriveFlags(0, pkg); + } + gosPs.derivedFlags = flags | DerivedPackageFlag.DFLAGS_SET; + } + + private static int deriveFlags(int flags, AndroidPackage pkg) { + List list = pkg.getUsesPermissions(); + for (int i = 0, m = list.size(); i < m; ++i) { + ParsedUsesPermission perm = list.get(i); + String name = perm.getName(); + switch (name) { + case Manifest.permission.READ_EXTERNAL_STORAGE: + case Manifest.permission.WRITE_EXTERNAL_STORAGE: { + boolean writePerm = name.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE); + flags |= writePerm ? + DerivedPackageFlag.HAS_WRITE_EXTERNAL_STORAGE_DECLARATION : + DerivedPackageFlag.HAS_READ_EXTERNAL_STORAGE_DECLARATION; + + int targetSdk = pkg.getTargetSdkVersion(); + + boolean legacy = targetSdk < 29 + || (targetSdk == 29 && pkg.isRequestLegacyExternalStorage()); + + if (writePerm && legacy) { + // when app doesn't have "legacy external storage", WRITE_EXTERNAL_STORAGE + // doesn't grant write access + flags |= DerivedPackageFlag.EXPECTS_STORAGE_WRITE_ACCESS; + } + + if ((flags & DerivedPackageFlag.EXPECTS_ALL_FILES_ACCESS) == 0) { + if (legacy) { + flags |= (DerivedPackageFlag.EXPECTS_ALL_FILES_ACCESS + | DerivedPackageFlag.EXPECTS_LEGACY_EXTERNAL_STORAGE); + } else { + flags |= DerivedPackageFlag.EXPECTS_ACCESS_TO_MEDIA_FILES_ONLY; + } + } + continue; + } + + case Manifest.permission.MANAGE_EXTERNAL_STORAGE: + flags &= ~DerivedPackageFlag.EXPECTS_ACCESS_TO_MEDIA_FILES_ONLY; + flags |= DerivedPackageFlag.EXPECTS_ALL_FILES_ACCESS + | DerivedPackageFlag.EXPECTS_STORAGE_WRITE_ACCESS + | DerivedPackageFlag.HAS_MANAGE_EXTERNAL_STORAGE_DECLARATION; + continue; + + case Manifest.permission.MANAGE_MEDIA: + flags |= DerivedPackageFlag.HAS_MANAGE_MEDIA_DECLARATION; + continue; + + case Manifest.permission.ACCESS_MEDIA_LOCATION: + flags |= DerivedPackageFlag.HAS_ACCESS_MEDIA_LOCATION_DECLARATION; + continue; + + case Manifest.permission.READ_MEDIA_AUDIO: + flags |= DerivedPackageFlag.HAS_READ_MEDIA_AUDIO_DECLARATION; + continue; + + case Manifest.permission.READ_MEDIA_IMAGES: + flags |= DerivedPackageFlag.HAS_READ_MEDIA_IMAGES_DECLARATION; + continue; + + case Manifest.permission.READ_MEDIA_VIDEO: + flags |= DerivedPackageFlag.HAS_READ_MEDIA_VIDEO_DECLARATION; + continue; + + case Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED: + flags |= DerivedPackageFlag.HAS_READ_MEDIA_VISUAL_USER_SELECTED_DECLARATION; + continue; + + case Manifest.permission.READ_CONTACTS: + flags |= DerivedPackageFlag.HAS_READ_CONTACTS_DECLARATION; + continue; + + case Manifest.permission.WRITE_CONTACTS: + flags |= DerivedPackageFlag.HAS_WRITE_CONTACTS_DECLARATION; + continue; + + case Manifest.permission.GET_ACCOUNTS: + flags |= DerivedPackageFlag.HAS_GET_ACCOUNTS_DECLARATION; + continue; + } + } + + if ((flags & DerivedPackageFlag.HAS_MANAGE_MEDIA_DECLARATION) != 0) { + if ((flags & (DerivedPackageFlag.HAS_READ_EXTERNAL_STORAGE_DECLARATION + | DerivedPackageFlag.HAS_READ_MEDIA_AUDIO_DECLARATION + | DerivedPackageFlag.HAS_READ_MEDIA_IMAGES_DECLARATION + | DerivedPackageFlag.HAS_READ_MEDIA_VIDEO_DECLARATION + | DerivedPackageFlag.HAS_MANAGE_EXTERNAL_STORAGE_DECLARATION)) == 0) + { + flags &= ~DerivedPackageFlag.HAS_MANAGE_MEDIA_DECLARATION; + } + } + + if ((flags & DerivedPackageFlag.HAS_MANAGE_MEDIA_DECLARATION) != 0) { + flags |= DerivedPackageFlag.EXPECTS_STORAGE_WRITE_ACCESS; + } + + if ((flags & DerivedPackageFlag.HAS_ACCESS_MEDIA_LOCATION_DECLARATION) != 0) { + if ((flags & (DerivedPackageFlag.HAS_READ_EXTERNAL_STORAGE_DECLARATION + | DerivedPackageFlag.HAS_READ_MEDIA_IMAGES_DECLARATION + | DerivedPackageFlag.HAS_READ_MEDIA_VIDEO_DECLARATION + | DerivedPackageFlag.HAS_MANAGE_EXTERNAL_STORAGE_DECLARATION)) == 0) + { + flags &= ~DerivedPackageFlag.HAS_ACCESS_MEDIA_LOCATION_DECLARATION; + } + } + + return flags; + } + + /** @see PackageManagerService.IPackageManagerImpl#clearApplicationUserData */ + public static void onClearApplicationUserData(PackageManagerService pm, String packageName, int userId) { + if (packageName.equals(KnownSystemPackages.get(pm.getContext()).contactsProvider)) { + // discard IDs that refer to entries in the contacts provider database + clearContactScopesStorage(pm, userId); + } + } + + private static void clearContactScopesStorage(PackageManagerService pm, int userId) { + for (PackageStateInternal ps : pm.snapshotComputer().getPackageStates().values()) { + PackageUserStateInternal us = ps.getUserStateOrDefault(userId); + GosPackageState gosPs = us.getGosPackageState(); + if (gosPs.contactScopes != null) { + gosPs.createEditor(ps.getPackageName(), userId) + .setContactScopes(null) + .apply(); + } + } + } + + static int runShellCommand(PackageManagerShellCommand cmd) { + String packageName = cmd.getNextArgRequired(); + int userId = Integer.parseInt(cmd.getNextArgRequired()); + + GosPackageState.Editor ed = GosPackageState.edit(packageName, userId); + boolean updatePermissionState = false; + + for (;;) { + String arg = cmd.getNextArg(); + if (arg == null) { + if (!ed.apply()) { + return 1; + } + if (updatePermissionState) { + cmd.mPermissionManager.updatePermissionState(packageName, userId); + } + return 0; + } + switch (arg) { + case "add-flag", "clear-flag" -> + ed.setFlagState(parseFlag(cmd.getNextArgRequired()), "add-flag".equals(arg)); + case "add-package-flag", "clear-package-flag" -> + ed.setPackageFlagState(Integer.parseInt(cmd.getNextArgRequired()), + "add-package-flag".equals(arg)); + case "set-storage-scopes" -> + ed.setStorageScopes(getByteArrArg(cmd)); + case "set-contact-scopes" -> + ed.setContactScopes(getByteArrArg(cmd)); + case "set-kill-uid-after-apply" -> + ed.setKillUidAfterApply(Boolean.parseBoolean(cmd.getNextArgRequired())); + case "set-notify-uid-after-apply" -> + ed.setNotifyUidAfterApply(Boolean.parseBoolean(cmd.getNextArgRequired())); + case "update-permission-state" -> + updatePermissionState = true; + default -> + throw new IllegalArgumentException(arg); + } + } + } + + @Nullable + private static byte[] getByteArrArg(ShellCommand cmd) { + String s = cmd.getNextArgRequired(); + return "null".equals(s) ? null : libcore.util.HexEncoding.decode(s); + } +} diff --git a/services/core/java/com/android/server/pm/GosPackageStateUtils.java b/services/core/java/com/android/server/pm/GosPackageStateUtils.java new file mode 100644 index 0000000000000..d93a2e473b0c1 --- /dev/null +++ b/services/core/java/com/android/server/pm/GosPackageStateUtils.java @@ -0,0 +1,38 @@ +package com.android.server.pm; + +import android.content.pm.GosPackageStateFlag; + +import java.lang.reflect.Field; +import java.util.BitSet; + +class GosPackageStateUtils { + private static volatile BitSet knownFlagsCache; + + static @GosPackageStateFlag.Enum int parseFlag(String s) { + if (Character.isDigit(s.charAt(0))) { + int value = Integer.parseInt(s); + BitSet knownFlags = knownFlagsCache; + if (knownFlags == null) { + knownFlags = new BitSet(); + for (Field f : GosPackageStateFlag.class.getDeclaredFields()) { + try { + knownFlags.set(f.getInt(null)); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException(e); + } + } + knownFlagsCache = knownFlags; + } + if (!knownFlags.get(value)) { + throw new IllegalArgumentException(s); + } + return value; + } + + try { + return GosPackageStateFlag.class.getDeclaredField(s).getInt(null); + } catch (ReflectiveOperationException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f9df65581a826..884f5a41998cb 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -90,6 +90,7 @@ import android.content.pm.FallbackCategoryProvider; import android.content.pm.FeatureInfo; import android.content.pm.Flags; +import android.content.pm.GosPackageState; import android.content.pm.IDexModuleRegisterCallback; import android.content.pm.IOnChecksumsReadyListener; import android.content.pm.IPackageDataObserver; @@ -4392,6 +4393,8 @@ public void onReceive(Context context, Intent intent) { if (dexUseManager != null) { dexUseManager.systemReady(); } + + GosPackageStatePmHooks.init(this); } public PackageFreezer freezePackage(String packageName, @CanBeALL @UserIdInt int userId, @@ -4824,6 +4827,10 @@ public void clearApplicationUserData(final String packageName, mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); + + GosPackageStatePmHooks.onClearApplicationUserData( + PackageManagerService.this, packageName, userId); + final boolean succeeded; try (PackageFreezer freezer = freezePackage(packageName, USER_ALL, "clearApplicationUserData", @@ -6726,6 +6733,22 @@ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { public Bundle getExtraAppBindArgs(String packageName) { return PackageManagerHooks.getExtraAppBindArgs(PackageManagerService.this, packageName); } + + @Override + public GosPackageState getGosPackageState(@NonNull String packageName, int userId) { + int callingUid = Binder.getCallingUid(); + int callingPid = Binder.getCallingPid(); + return GosPackageStatePmHooks.getFiltered(PackageManagerService.this, callingUid, callingPid, packageName, userId); + } + + @Override + public boolean setGosPackageState(@NonNull String packageName, int userId, + @NonNull GosPackageState updatedPs, int editorFlags) { + int callingUid = Binder.getCallingUid(); + int callingPid = Binder.getCallingPid(); + return GosPackageStatePmHooks.set(PackageManagerService.this, callingUid, callingPid, packageName, userId, + updatedPs, editorFlags); + } } private class PackageManagerInternalImpl extends PackageManagerInternalBase { @@ -7309,6 +7332,12 @@ public boolean isUpgradingFromLowerThan(int sdkVersion) { final boolean isUpgrading = mPriorSdkVersion != -1; return isUpgrading && mPriorSdkVersion < sdkVersion; } + + @NonNull + @Override + public GosPackageState getGosPackageState(String packageName, int userId) { + return GosPackageStatePmHooks.getUnfiltered(PackageManagerService.this, packageName, userId); + } } private void setEnabledOverlayPackages(@UserIdInt int userId, diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 62264dd73795e..691bb5dce62b3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -224,6 +224,8 @@ public int onCommand(String cmd) { case "help": onHelp(); return 0; + case "edit-gos-package-state": + return GosPackageStatePmHooks.runShellCommand(this); case "path": return runPath(); case "dump": diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index a902f5ff372f2..426fb2f05b241 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -29,7 +29,9 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; @@ -51,6 +53,7 @@ import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; +import com.android.server.LocalServices; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.permission.LegacyPermissionState; @@ -64,6 +67,7 @@ import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedLibrary; import com.android.server.pm.pkg.SharedLibraryWrapper; +import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.SuspendParams; import com.android.server.utils.SnapshotCache; import com.android.server.utils.WatchedArraySet; @@ -408,6 +412,11 @@ public PackageSetting setFirstInstallTime(long firstInstallTime, int userId) { return this; } + public void setGosPackageState(@UserIdInt int userId, @NonNull GosPackageState state) { + modifyUserState(userId).setGosPackageState(state); + onChanged(); + } + public PackageSetting setForceQueryableOverride(boolean forceQueryableOverride) { setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, forceQueryableOverride); onChanged(); @@ -914,6 +923,16 @@ int getEnabled(int userId) { void setInstalled(boolean inst, int userId) { modifyUserState(userId).setInstalled(inst); + if (inst) { + int sharedUserAppId = getSharedUserAppId(); + if (sharedUserAppId > 0) { + var pmi = LocalServices.getService(PackageManagerInternal.class); + SharedUserApi sharedUser = pmi.getSharedUserApi(sharedUserAppId); + if (sharedUser != null) { + sharedUser.syncGosPackageState(); + } + } + } onChanged(); } @@ -1098,6 +1117,7 @@ void setVirtualPreload(boolean virtualPreload, int userId) { void setUserState(int userId, long ceDataInode, long deDataInode, int enabled, boolean installed, boolean stopped, boolean notLaunched, boolean hidden, + GosPackageState gosPackageState, int distractionFlags, ArrayMap suspendParams, boolean instantApp, boolean virtualPreload, String lastDisableAppCaller, ArraySet enabledComponents, ArraySet disabledComponents, @@ -1113,6 +1133,7 @@ void setUserState(int userId, long ceDataInode, long deDataInode, int enabled, .setStopped(stopped) .setNotLaunched(notLaunched) .setHidden(hidden) + .setGosPackageState(gosPackageState) .setDistractionFlags(distractionFlags) .setLastDisableAppCaller(lastDisableAppCaller) .setEnabledComponents(enabledComponents) @@ -1132,7 +1153,7 @@ void setUserState(int userId, long ceDataInode, long deDataInode, int enabled, void setUserState(int userId, PackageUserStateInternal otherState) { setUserState(userId, otherState.getCeDataInode(), otherState.getDeDataInode(), otherState.getEnabledState(), otherState.isInstalled(), otherState.isStopped(), - otherState.isNotLaunched(), otherState.isHidden(), otherState.getDistractionFlags(), + otherState.isNotLaunched(), otherState.isHidden(), otherState.getGosPackageState(), otherState.getDistractionFlags(), otherState.getSuspendParams() == null ? null : otherState.getSuspendParams().untrackedStorage(), otherState.isInstantApp(), otherState.isVirtualPreload(), diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 7b8617d726a40..e296a7164d0e8 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -44,6 +44,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.Flags; +import android.content.pm.GosPackageState; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -1146,6 +1147,7 @@ void pruneSharedUsersLPw() { true /*stopped*/, true /*notLaunched*/, false /*hidden*/, + GosPackageState.DEFAULT, 0 /*distractionFlags*/, null /*suspendParams*/, instantApp, @@ -1846,6 +1848,7 @@ void readPackageRestrictionsLPr(int userId, false /*stopped*/, false /*notLaunched*/, false /*hidden*/, + GosPackageState.DEFAULT, 0 /*distractionFlags*/, null /*suspendParams*/, false /*instantApp*/, @@ -1963,6 +1966,11 @@ void readPackageRestrictionsLPr(int userId, ATTR_MIN_ASPECT_RATIO, PackageManager.USER_MIN_ASPECT_RATIO_UNSET); + GosPackageState gosPackageState = GosPackageStatePersistence.maybeDeserializeLegacy(parser); + if (gosPackageState == null) { + gosPackageState = GosPackageState.DEFAULT; + } + ArraySet enabledComponents = null; ArraySet disabledComponents = null; SuspendDialogInfo oldSuspendDialogInfo = null; @@ -2011,6 +2019,9 @@ void readPackageRestrictionsLPr(int userId, case TAG_ARCHIVE_STATE: archiveState = parseArchiveState(parser); break; + case GosPackageStatePersistence.TAG_GOS_PACKAGE_STATE: + gosPackageState = GosPackageStatePersistence.deserialize(parser); + break; default: Slog.wtf(TAG, "Unknown tag " + parser.getName() + " under tag " + TAG_PACKAGE); @@ -2037,7 +2048,7 @@ void readPackageRestrictionsLPr(int userId, } ps.setUserState( userId, ceDataInode, deDataInode, enabled, installed, stopped, - notLaunched, hidden, distractionFlags, suspendParamsMap, instantApp, + notLaunched, hidden, gosPackageState, distractionFlags, suspendParamsMap, instantApp, virtualPreload, enabledCaller, enabledComponents, disabledComponents, installReason, uninstallReason, harmfulAppWarning, splashScreenTheme, @@ -2460,6 +2471,9 @@ void writePackageRestrictions(int userId, long startTime, boolean sync) { serializer.attributeInt(null, ATTR_MIN_ASPECT_RATIO, ustate.getMinAspectRatio()); } + + GosPackageStatePersistence.serialize(ustate, serializer); + if (ustate.isSuspended()) { for (int i = 0; i < ustate.getSuspendParams().size(); i++) { final UserPackage suspendingPackage = diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 5c0a15a282859..abb0f39d967c1 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -19,10 +19,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; import android.content.pm.SigningDetails; import android.service.pm.PackageServiceDumpProto; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.pm.pkg.component.ComponentMutateUtils; @@ -32,6 +34,7 @@ import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; @@ -174,10 +177,57 @@ void addProcesses(Map newProcs) { } } + @Override + public void syncGosPackageState() { + syncGosPackageState(mPackages); + } + + // Makes GosPackageState the same for all packages in this SharedUser in each userId. + // See GosPackageState class comment for more info. + private static void syncGosPackageState(WatchedArraySet packages) { + final int numPkgs = packages.size(); + // userId -> GosPackageState + SparseArray userGosPs = null; + + for (int pkgIdx = 0; pkgIdx < numPkgs; ++pkgIdx) { + PackageSetting s = packages.valueAt(pkgIdx); + SparseArray userStates = s.getUserStates(); + for (int userIndex = 0, numUsers = userStates.size(); userIndex < numUsers; ++userIndex) { + int userId = userStates.keyAt(userIndex); + for (int i = 0; i < numPkgs; ++i) { + GosPackageState gosPs = packages.valueAt(i).getUserStateOrDefault(userId).getGosPackageState(); + if (!gosPs.equals(GosPackageState.DEFAULT)) { + if (userGosPs == null) { + userGosPs = new SparseArray<>(); + } + userGosPs.set(userId, gosPs); + break; + } + } + } + } + + if (userGosPs == null) { + return; + } + + for (int idx = 0, numUsers = userGosPs.size(); idx < numUsers; ++idx) { + int userId = userGosPs.keyAt(idx); + GosPackageState ps = userGosPs.valueAt(idx); + for (int pkgIdx = 0; pkgIdx < numPkgs; ++pkgIdx) { + PackageSetting s = packages.valueAt(pkgIdx); + s.setGosPackageState(userId, ps); + } + } + } + boolean removePackage(PackageSetting packageSetting) { if (!mPackages.remove(packageSetting)) { return false; } + + clearGosPackageStateCachedDerivedFlags(); + // recalculate the pkgFlags for this shared user if needed if ((this.getFlags() & packageSetting.getFlags()) != 0) { int aggregatedFlags = uidFlags; @@ -210,6 +260,10 @@ void addPackage(PackageSetting packageSetting) { if (mPackages.add(packageSetting)) { setFlags(this.getFlags() | packageSetting.getFlags()); setPrivateFlags(this.getPrivateFlags() | packageSetting.getPrivateFlags()); + + clearGosPackageStateCachedDerivedFlags(); + syncGosPackageState(); + onChanged(); } if (packageSetting.getPkg() != null) { @@ -411,4 +465,15 @@ public ArrayMap getProcesses() { public LegacyPermissionState getSharedUserLegacyPermissionState() { return super.getLegacyPermissionState(); } + + // invalidate derived flags when sharedUid members are added/removed + private void clearGosPackageStateCachedDerivedFlags() { + for (int pkgIdx = 0; pkgIdx < mPackages.size(); pkgIdx++) { + PackageSetting ps = mPackages.valueAt(pkgIdx); + SparseArray userStates = ps.getUserStates(); + for (int userStateIdx = 0; userStateIdx < userStates.size(); ++userStateIdx) { + userStates.valueAt(userStateIdx).getGosPackageState().derivedFlags = 0; + } + } + } } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index 2a81a86d20f61..c790f107bb3b3 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.pm.GosPackageState; import android.content.pm.PackageManager; import android.content.pm.overlay.OverlayPaths; import android.os.UserHandle; @@ -122,6 +123,11 @@ public interface PackageUserState { @PackageManager.EnabledState int getEnabledState(); + /** @hide */ + @Immutable.Ignore + @NonNull + GosPackageState getGosPackageState(); + /** * @hide * @see PackageManager#setHarmfulAppWarning(String, CharSequence) diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index 15b693cf72f8c..b09a8a8d0caa6 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; +import android.content.pm.GosPackageState; import android.content.pm.PackageManager; import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; @@ -156,6 +157,12 @@ public long getFirstInstallTimeMillis() { return 0; } + @NonNull + @Override + public GosPackageState getGosPackageState() { + return GosPackageState.DEFAULT; + } + @Override public boolean isComponentEnabled(String componentName) { return false; diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index b32943704dc45..54c026fe0b4e0 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; +import android.content.pm.GosPackageState; import android.content.pm.PackageManager; import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; @@ -129,6 +130,9 @@ private boolean getBoolean(@Booleans.Flags int flag) { private @CurrentTimeMillisLong long mFirstInstallTimeMillis; + @NonNull + private GosPackageState mGosPackageState = GosPackageState.DEFAULT; + // TODO(b/239050028): Remove, enforce notifying parent through PMS commit method @Nullable private Watchable mWatchable; @@ -164,6 +168,7 @@ public PackageUserStateImpl(@NonNull Watchable watchable) { } public PackageUserStateImpl(@NonNull Watchable watchable, PackageUserStateImpl other) { + mGosPackageState = other.mGosPackageState; mWatchable = watchable; mBooleans = other.mBooleans; mDisabledComponentsWatched = other.mDisabledComponentsWatched == null @@ -607,6 +612,12 @@ public PackageUserStateImpl removeSuspension(@NonNull UserPackage suspendingPack return this; } + public @NonNull PackageUserStateImpl setGosPackageState(@NonNull GosPackageState gosPackageState) { + mGosPackageState = gosPackageState; + onChanged(); + return this; + } + /** * Sets the value for {@link #getArchiveState()}. */ @@ -804,6 +815,11 @@ public int getDistractionFlags() { return mArchiveState; } + @DataClass.Generated.Member + public @NonNull GosPackageState getGosPackageState() { + return mGosPackageState; + } + @DataClass.Generated.Member public @NonNull SnapshotCache getSnapshot() { return mSnapshot; diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java index f8d745cb7fbfe..7ae5748eae3ef 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; +import android.content.pm.GosPackageState; import android.content.pm.UserPackage; import android.content.pm.pkg.FrameworkPackageUserState; import android.util.Pair; @@ -49,4 +50,7 @@ public interface PackageUserStateInternal extends PackageUserState, FrameworkPac @Nullable Pair getOverrideLabelIconForComponent(@NonNull ComponentName componentName); + + @NonNull + GosPackageState getGosPackageState(); } diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java index 411bdede315f7..37d8c5970a636 100644 --- a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java +++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java @@ -69,4 +69,6 @@ public interface SharedUserApi { @NonNull LegacyPermissionState getSharedUserLegacyPermissionState(); + + void syncGosPackageState(); } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index c272430d5c781..dbb224263e712 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -47,6 +47,7 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.Flags; +import android.content.pm.GosPackageState; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.SuspendDialogInfo; @@ -109,7 +110,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HexFormat; import java.util.List; +import java.util.Objects; import java.util.PriorityQueue; import java.util.Set; import java.util.UUID; @@ -1419,7 +1422,7 @@ public void testPackageStateCopy02() { .setPrimaryCpuAbi("x86_64") .setSecondaryCpuAbi("x86") .setLongVersionCode(INITIAL_VERSION_CODE); - origPkgSetting01.setUserState(0, 100, 100, 1, true, false, false, false, 0, null, false, + origPkgSetting01.setUserState(0, 100, 100, 1, true, false, false, false, createTestGosPackageState(), 0, null, false, false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}), new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning", "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET, null); @@ -1454,6 +1457,15 @@ public void testPackageStateCopy02() { testPkgSetting01.readUserState(0)); } + private static GosPackageState createTestGosPackageState() { + var hf = HexFormat.ofDelimiter(" "); + // argument values are random + return new GosPackageState(0xf0_bc_06_f1_f1_67_2e_b8L, 0xf4_93_53_00_98_c8_f0_0cL, + hf.parseHex("2d f6 37 f2 90 39 da ef"), + hf.parseHex("8b 9d 61 a3 3e 45 12") + ); + } + /** Update package */ @Test public void testUpdatePackageSetting01() throws PackageManagerException { @@ -2191,6 +2203,8 @@ private void verifyUserStatesCopy(PackageUserStateInternal origPus, SuspendParams testSuspendParams = testPus.getSuspendParams().valueAt(0); assertThat(origSuspendParams.getDialogInfo().equals(testSuspendParams.getDialogInfo()), is(true)); + assertEquals(origPus.getGosPackageState(), createTestGosPackageState()); + assertEquals(testPus.getGosPackageState(), createTestGosPackageState()); assertThat(BaseBundle.kindofEquals( origSuspendParams.getAppExtras(), testSuspendParams.getAppExtras()), is(true)); assertThat(BaseBundle.kindofEquals(origSuspendParams.getLauncherExtras(), @@ -2223,6 +2237,7 @@ private boolean userStateEquals(PackageUserState userState, PackageUserState old && userState.getCeDataInode() == oldUserState.getCeDataInode() && userState.getDistractionFlags() == oldUserState.getDistractionFlags() && userState.getFirstInstallTimeMillis() == oldUserState.getFirstInstallTimeMillis() + && Objects.equals(userState.getGosPackageState(), oldUserState.getGosPackageState()) && userState.getEnabledState() == oldUserState.getEnabledState() && userState.getHarmfulAppWarning().equals(oldUserState.getHarmfulAppWarning()) && userState.getInstallReason() == oldUserState.getInstallReason() From 5ab31f1ba27cb37a085cf4879962dc8126dc5afc Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 2 Jun 2024 14:50:57 +0300 Subject: [PATCH 067/332] extend excludedMethods list in AndroidPackageTest --- .../server/pm/test/parsing/parcelling/AndroidPackageTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index 31f03704a756d..27ba69c2ce4a5 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -71,6 +71,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag override val excludedMethods = listOf( // Internal methods + "initPackageParsingHooks", "getPackageParsingHooks", + "setPackageExt", "ext", "toAppInfoToString", "toAppInfoWithoutState", "toAppInfoWithoutStateWithoutFlags", From 682444e1bcd906498b714f93d522ac89846249d3 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Mon, 18 Dec 2023 14:56:03 +0200 Subject: [PATCH 068/332] add method for updating cached package permission state Package permission state is updated automatically for all packages after events that might impact it, e.g. after package install or uninstall, after storage volume mount, after OS update etc. On GrapheneOS, per-package permission policy can be changed via GosPackageState packageFlags. This new method is needed for updating the cached permission state manually after packageFlags change. --- core/api/system-current.txt | 1 + .../android/permission/IPermissionManager.aidl | 2 ++ .../android/permission/PermissionManager.java | 8 ++++++++ .../pm/permission/PermissionManagerService.java | 15 +++++++++++++++ .../permission/PermissionManagerServiceImpl.java | 5 +++++ .../PermissionManagerServiceInterface.java | 2 ++ .../PermissionManagerServiceLoggingDecorator.java | 6 ++++++ .../PermissionManagerServiceTestingShim.java | 6 ++++++ .../PermissionManagerServiceTracingDecorator.java | 10 ++++++++++ .../access/permission/PermissionService.kt | 5 +++++ 10 files changed, 60 insertions(+) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b69983e982b74..026ddc446d673 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -12272,6 +12272,7 @@ package android.permission { method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, long, int, int); method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void stopOneTimePermissionSession(@NonNull String); method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public void updatePermissionFlags(@NonNull String, @NonNull String, @NonNull String, int, int); + method public void updatePermissionState(@NonNull String, int); field @RequiresPermission(android.Manifest.permission.START_REVIEW_PERMISSION_DECISIONS) public static final String ACTION_REVIEW_PERMISSION_DECISIONS = "android.permission.action.REVIEW_PERMISSION_DECISIONS"; field public static final String EXTRA_PERMISSION_USAGES = "android.permission.extra.PERMISSION_USAGES"; field public static final int PERMISSION_GRANTED = 0; // 0x0 diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index b37581260bb14..fd7b3522d028f 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -110,6 +110,8 @@ interface IPermissionManager { Map getAllPermissionStates(String packageName, String persistentDeviceId, int userId); int getPermissionRequestState(String packageName, String permissionName, int deviceId); + + void updatePermissionState(String packageName, int userId); } /** diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 7fe97f344be69..07b2b74ea9f4e 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -2296,4 +2296,12 @@ public String toString() { + '}'; } } + + public void updatePermissionState(@NonNull String packageName, int userId) { + try { + mPermissionManager.updatePermissionState(packageName, userId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index fbf81b9accadb..6a71a83540245 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -504,6 +504,21 @@ private List getPackagesWithAutoRevokePolicy(int autoRevokePolicy, int u return result; } + @Override + public void updatePermissionState(String packageName, int userId) { + mContext.enforceCallingPermission( + android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "updatePermissionState"); + + AndroidPackage pkg = mPackageManagerInt.getPackage(packageName); + if (pkg == null) { + Slog.w(LOG_TAG, "updatePermissionState: no AndroidPackage for " + packageName); + return; + } + + mPermissionManagerServiceImpl.updatePermissions(pkg, userId); + } + /* Start of delegate methods to PermissionManagerServiceInterface */ @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index c538c359e13ff..c54706de0d823 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -3918,6 +3918,11 @@ private void updatePermissions(@NonNull String packageName, @Nullable AndroidPac packageName, pkg, getVolumeUuidForPackage(pkg), flags, mDefaultPermissionCallback); } + @Override + public void updatePermissions(@NonNull AndroidPackage pkg, int userId) { + restorePermissionState(pkg, false, null, null, userId); + } + /** * Update all permissions for all apps. * diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index f2491d949e6b5..526848ebc444b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -664,4 +664,6 @@ void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId, void onPackageUninstalled(@NonNull String packageName, int appId, @NonNull PackageState packageState, @Nullable AndroidPackage pkg, @NonNull List sharedUserPkgs, @CanBeALL @UserIdInt int userId); + + void updatePermissions(@NonNull AndroidPackage pkg, int userId); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java index f5764006e7660..16b006deb34a6 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java @@ -461,4 +461,10 @@ public void onPackageUninstalled(@NonNull String packageName, int appId, mService.onPackageUninstalled(packageName, appId, packageState, pkg, sharedUserPkgs, userId); } + + @Override + public void updatePermissions(@NonNull AndroidPackage pkg, int userId) { + Log.i(LOG_TAG, "updatePermissions(pkgName = " + pkg.getPackageName() + ", userId " + userId); + mService.updatePermissions(pkg, userId); + } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java index 21a357025cfbe..13d30e3e80181 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java @@ -587,4 +587,10 @@ public void onPackageUninstalled(@NonNull String packageName, int appId, mNewImplementation.onPackageUninstalled(packageName, appId, packageState, pkg, sharedUserPkgs, userId); } + + @Override + public void updatePermissions(@NonNull AndroidPackage pkg, int userId) { + mOldImplementation.updatePermissions(pkg, userId); + mNewImplementation.updatePermissions(pkg, userId); + } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java index e51afb0f66c5a..e82f6b77992cc 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java @@ -687,4 +687,14 @@ public void onPackageUninstalled(@NonNull String packageName, int appId, Trace.traceEnd(TRACE_TAG); } } + + @Override + public void updatePermissions(@NonNull AndroidPackage pkg, int userId) { + Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#updatePermissions"); + try { + mService.updatePermissions(pkg, userId); + } finally { + Trace.traceEnd(TRACE_TAG); + } + } } diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 7128af5464e87..3b4866c91fa60 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -2929,4 +2929,9 @@ class PermissionService(private val service: AccessCheckingService) : fun getFullerPermission(permissionName: String): String? = FULLER_PERMISSIONS[permissionName] } + + override fun updatePermissions(pkg: AndroidPackage, userId: Int) { + // this new version of PermissionService is WIP and is not used yet + TODO() + } } From 5c1c082834059636cb90bd63a5e1f4e0c864475a Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Mon, 18 Dec 2023 19:28:01 +0200 Subject: [PATCH 069/332] PackageHooks: infrastructure for per-package hooks in system_server --- .../com/android/server/am/ActiveServices.java | 5 + .../server/am/ActiveServicesHooks.java | 25 +++++ .../server/ext/PackageManagerHooks.java | 9 ++ .../com/android/server/pm/ext/PackageExt.java | 7 ++ .../android/server/pm/ext/PackageHooks.java | 92 +++++++++++++++++++ .../server/pm/ext/PackageHooksRegistry.java | 6 ++ .../PermissionManagerServiceImpl.java | 51 ++++++++++ 7 files changed, 195 insertions(+) create mode 100644 services/core/java/com/android/server/am/ActiveServicesHooks.java create mode 100644 services/core/java/com/android/server/pm/ext/PackageHooks.java diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index c15915ba39a43..1c1646baf942a 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -8539,6 +8539,11 @@ boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String c ret = REASON_DEVICE_OWNER; } } + if (ret == REASON_DENIED) { + if (ActiveServicesHooks.shouldAllowFgsWhileInUsePermission(this, callingUid)) { + ret = REASON_ALLOWLISTED_PACKAGE; + } + } return ret; } diff --git a/services/core/java/com/android/server/am/ActiveServicesHooks.java b/services/core/java/com/android/server/am/ActiveServicesHooks.java new file mode 100644 index 0000000000000..b2f648efacde1 --- /dev/null +++ b/services/core/java/com/android/server/am/ActiveServicesHooks.java @@ -0,0 +1,25 @@ +package com.android.server.am; + +import android.content.pm.PackageManagerInternal; +import android.os.UserHandle; +import android.util.Slog; + +import com.android.server.pm.ext.PackageExt; +import com.android.server.pm.pkg.AndroidPackage; + +class ActiveServicesHooks { + static final String TAG = ActiveServicesHooks.class.getSimpleName(); + + static boolean shouldAllowFgsWhileInUsePermission(ActiveServices activeServices, int uid) { + PackageManagerInternal pm = activeServices.mAm.getPackageManagerInternal(); + AndroidPackage pkg = pm.getPackage(uid); + if (pkg == null) { + return false; + } + boolean res = PackageExt.get(pkg).hooks().shouldAllowFgsWhileInUsePermission(pm, UserHandle.getUserId(uid)); + if (res) { + Slog.d(TAG, "shouldAllowFgsWhileInUsePermission for " + pkg.getPackageName() + " returned true"); + } + return res; + } +} diff --git a/services/core/java/com/android/server/ext/PackageManagerHooks.java b/services/core/java/com/android/server/ext/PackageManagerHooks.java index 9cd50fd5b6cb1..7e28591ba5066 100644 --- a/services/core/java/com/android/server/ext/PackageManagerHooks.java +++ b/services/core/java/com/android/server/ext/PackageManagerHooks.java @@ -18,6 +18,7 @@ import com.android.server.pm.Computer; import com.android.server.pm.GosPackageStatePmHooks; import com.android.server.pm.PackageManagerService; +import com.android.server.pm.ext.PackageHooks; import com.android.server.pm.permission.Permission; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; @@ -109,6 +110,14 @@ public static boolean shouldFilterApplication( } } } + + if (PackageHooks.shouldBlockAppsFilterVisibility( + callingPkgSetting, callingSharedPkgSettings, callingUserId, + targetPkgSetting, targetUserId) + ) { + return true; + } + return false; } diff --git a/services/core/java/com/android/server/pm/ext/PackageExt.java b/services/core/java/com/android/server/pm/ext/PackageExt.java index 733ee9d585fe9..a8c0142b53951 100644 --- a/services/core/java/com/android/server/pm/ext/PackageExt.java +++ b/services/core/java/com/android/server/pm/ext/PackageExt.java @@ -16,6 +16,8 @@ public class PackageExt implements PackageExtIface { private final int packageId; private final int flags; + private final PackageHooks hooks; + public static PackageExt get(AndroidPackage pkg) { PackageExtIface i = pkg.ext(); if (i instanceof PackageExt) { @@ -27,12 +29,17 @@ public static PackageExt get(AndroidPackage pkg) { public PackageExt(int packageId, int flags) { this.packageId = packageId; this.flags = flags; + this.hooks = PackageHooksRegistry.getHooks(packageId); } public int getPackageId() { return packageId; } + public PackageHooks hooks() { + return hooks; + } + public AppInfoExt toAppInfoExt(PackageImpl pkg) { AppCompatProtos.CompatConfig compatConfig = AppCompatConf.get(pkg); diff --git a/services/core/java/com/android/server/pm/ext/PackageHooks.java b/services/core/java/com/android/server/pm/ext/PackageHooks.java new file mode 100644 index 0000000000000..5f42cfad02603 --- /dev/null +++ b/services/core/java/com/android/server/pm/ext/PackageHooks.java @@ -0,0 +1,92 @@ +package com.android.server.pm.ext; + +import android.annotation.Nullable; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.util.ArraySet; + +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateInternal; + +public class PackageHooks { + static final PackageHooks DEFAULT = new PackageHooks(); + + public static boolean isDefault(PackageHooks hooks) { + return hooks == DEFAULT; + } + + protected static final int NO_PERMISSION_OVERRIDE = -8; + public static final int PERMISSION_OVERRIDE_GRANT = PackageManager.PERMISSION_GRANTED; + public static final int PERMISSION_OVERRIDE_REVOKE = PackageManager.PERMISSION_DENIED; + + public int overridePermissionState(String permission, int userId) { + return NO_PERMISSION_OVERRIDE; + } + + /** + * @param isSelfToOther direction of visibility: from self to other package or from other + * package to self + */ + public boolean shouldBlockPackageVisibility(int userId, PackageStateInternal otherPkg, boolean isSelfToOther) { + return shouldBlockPackageVisibility(userId, otherPkg); + } + + public boolean shouldBlockPackageVisibility(int userId, PackageStateInternal otherPkg) { + return false; + } + + public static boolean shouldBlockAppsFilterVisibility( + @Nullable PackageStateInternal callingPkgSetting, + ArraySet callingSharedPkgSettings, + int callingUserId, + PackageStateInternal targetPkgSetting, int targetUserId) { + if (callingPkgSetting != null) { + return shouldBlockPackageVisibilityTwoWay( + callingPkgSetting, callingUserId, + targetPkgSetting, targetUserId); + } + + for (int i = callingSharedPkgSettings.size() - 1; i >= 0; --i) { + boolean res = shouldBlockPackageVisibilityTwoWay( + callingSharedPkgSettings.valueAt(i), callingUserId, + targetPkgSetting, targetUserId); + if (res) { + return true; + } + } + + return false; + } + + private static boolean shouldBlockPackageVisibilityTwoWay( + PackageStateInternal pkgSetting, int pkgUserId, + PackageStateInternal otherPkgSetting, int otherPkgUserId) { + boolean res = shouldBlockPackageVisibilityInner(pkgSetting, pkgUserId, otherPkgSetting, true); + if (!res) { + res = shouldBlockPackageVisibilityInner(otherPkgSetting, otherPkgUserId, pkgSetting, false); + } + return res; + } + + private static boolean shouldBlockPackageVisibilityInner( + PackageStateInternal pkgSetting, int pkgUserId, PackageStateInternal otherPkgSetting, + boolean isSelfToOther) { + AndroidPackage pkg = pkgSetting.getPkg(); + if (pkg != null) { + return PackageExt.get(pkg).hooks() + .shouldBlockPackageVisibility(pkgUserId, otherPkgSetting, isSelfToOther); + } + + return false; + } + + protected static boolean isUserInstalledPkg(PackageState ps) { + return !ps.isSystem(); + } + + public boolean shouldAllowFgsWhileInUsePermission(PackageManagerInternal pm, int userId) { + return false; + } +} diff --git a/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java b/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java index b720e2edbca75..aca3bd31f42e6 100644 --- a/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java +++ b/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java @@ -11,4 +11,10 @@ public static PackageParsingHooks getParsingHooks(String pkgName) { default -> PackageParsingHooks.DEFAULT; }; } + + public static PackageHooks getHooks(int packageId) { + return switch (packageId) { + default -> PackageHooks.DEFAULT; + }; + } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index c54706de0d823..b3326ad28ad74 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -143,11 +143,14 @@ import com.android.server.pm.PackageManagerTracedLock; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; +import com.android.server.pm.ext.PackageExt; +import com.android.server.pm.ext.PackageHooks; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; @@ -1636,6 +1639,10 @@ private void revokeRuntimePermissionInternal(String packageName, String permName return; } + if (PackageExt.get(pkg).hooks().overridePermissionState(permName, userId) == PackageHooks.PERMISSION_OVERRIDE_GRANT) { + throw new IllegalArgumentException(permName + " is granted by PackageHooks for " + packageName); + } + final int flags = uidState.getPermissionFlags(permName); // Only the system may revoke SYSTEM_FIXED permissions. if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0 @@ -2630,8 +2637,12 @@ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, } } + final PackageHooks pkgHooks = PackageExt.get(pkg).hooks(); + synchronized (mLock) { for (final int userId : userIds) { + PackageUserStateInternal pkgUserState = ps.getUserStateOrDefault(userId); + final UserPermissionState userState = mState.getOrCreateUserState(userId); final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId()); @@ -2932,6 +2943,46 @@ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, Slog.wtf(LOG_TAG, "Unknown permission protection " + bp.getProtection() + " for permission " + bp.getName()); } + + { + final int override = pkgHooks.overridePermissionState(bp.getName(), userId); + boolean uidStateChanged = false; + int flags = -1; + if (override == PackageHooks.PERMISSION_OVERRIDE_GRANT) { + uidStateChanged |= uidState.grantPermission(bp); + flags = FLAG_PERMISSION_SYSTEM_FIXED; + } else if (override == PackageHooks.PERMISSION_OVERRIDE_REVOKE) { + boolean revoke = true; + PermissionState s = uidState.getPermissionState(bp.getName()); + if (s != null) { + revoke = (s.getFlags() & FLAG_PERMISSION_USER_SET) == 0; + } + + if (revoke) { + uidStateChanged |= uidState.revokePermission(bp); + flags = 0; + } + } + + if (flags != -1) { + int mask = FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_USER_SET + | FLAG_PERMISSION_USER_FIXED + | FLAG_PERMISSION_POLICY_FIXED + | FLAG_PERMISSION_GRANTED_BY_DEFAULT + | FLAG_PERMISSION_GRANTED_BY_ROLE + | FLAG_PERMISSION_REVOKED_COMPAT + | FLAG_PERMISSION_ONE_TIME + | FLAG_PERMISSION_AUTO_REVOKED + ; + uidStateChanged |= uidState.updatePermissionFlags(bp, mask, flags); + } + + if (uidStateChanged) { + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + } + } + } if ((installPermissionsChangedForUser || replace) From 347b9dc01b7835e30bac3f48b8029f3d761cb1a1 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 24 Sep 2023 13:12:49 +0300 Subject: [PATCH 070/332] add base class for complex per-app switches --- .../android/ext/settings/app/AppSwitch.java | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 core/java/android/ext/settings/app/AppSwitch.java diff --git a/core/java/android/ext/settings/app/AppSwitch.java b/core/java/android/ext/settings/app/AppSwitch.java new file mode 100644 index 0000000000000..d4b172e7212e6 --- /dev/null +++ b/core/java/android/ext/settings/app/AppSwitch.java @@ -0,0 +1,174 @@ +package android.ext.settings.app; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; +import android.content.pm.GosPackageStateFlag; +import android.ext.AppInfoExt; +import android.ext.settings.ExtSettings; + +/** @hide */ +public abstract class AppSwitch { + // optional GosPackageState flag that indicates that non-default value is set, ignored if 0 + @GosPackageStateFlag.Enum int gosPsFlagNonDefault; + // GosPackageState flag that indicates whether the switch is on or off, ignored if + // non-default flag is not set + @GosPackageStateFlag.Enum int gosPsFlag; + // invert meaning of gosPsFlag, i.e. true is off, false is on + boolean gosPsFlagInverted; + // optional GosPackageState flag to suppress switch-related notification (e.g. after + // "Don't show again" notification action) + @GosPackageStateFlag.Enum int gosPsFlagSuppressNotif; + + int compatChangeToDisableHardening = -1; + + // immutability reasons + public static final int IR_UNKNOWN = 0; + public static final int IR_IS_SYSTEM_APP = 1; + public static final int IR_NO_NATIVE_CODE = 2; + public static final int IR_NON_64_BIT_NATIVE_CODE = 3; + public static final int IR_OPTED_IN_VIA_MANIFEST = 4; + public static final int IR_IS_DEBUGGABLE_APP = 5; + public static final int IR_EXPLOIT_PROTECTION_COMPAT_MODE = 6; + public static final int IR_REQUIRED_BY_HARDENED_MALLOC = 7; + + // default value reasons + public static final int DVR_UNKNOWN = 0; + public static final int DVR_DEFAULT_SETTING = 1; + public static final int DVR_APP_COMPAT_CONFIG_HARDENING_OPT_IN = 2; + public static final int DVR_APP_COMPAT_CONFIG_HARDENING_OPT_OUT = 3; + public static final int DVR_APP_IS_CLIENT_OF_GMSCORE = 4; + + public static class StateInfo { + // use it only if StateInfo is not needed, it's not thread-safe to read from this variable + static final StateInfo PLACEHOLDER = new StateInfo(); + + boolean isImmutable; + int immutabilityReason = IR_UNKNOWN; + + boolean isUsingDefaultValue; + int defaultValueReason = DVR_UNKNOWN; + + public boolean isImmutable() { + return isImmutable; + } + + public int getImmutabilityReason() { + return immutabilityReason; + } + + public boolean isUsingDefaultValue() { + return isUsingDefaultValue; + } + + public int getDefaultValueReason() { + return defaultValueReason; + } + } + + public final boolean isImmutable(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps) { + return getImmutableValue(ctx, userId, appInfo, ps) != null; + } + + public final Boolean getImmutableValue(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps) { + return getImmutableValue(ctx, userId, appInfo, ps, StateInfo.PLACEHOLDER); + } + + // returns null if value is currently mutable + public Boolean getImmutableValue(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps, StateInfo si) { + return null; + } + + public final boolean getDefaultValue(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps) { + return getDefaultValue(ctx, userId, appInfo, ps, StateInfo.PLACEHOLDER); + } + + public final boolean getDefaultValue(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps, StateInfo si) { + int compatChangeForOff = this.compatChangeToDisableHardening; + if (compatChangeForOff >= 0) { + AppInfoExt aie = appInfo.ext(); + if (aie.hasCompatConfig()) { + boolean res = !aie.hasCompatChange(compatChangeForOff); + if (res || ExtSettings.ALLOW_DISABLING_HARDENING_VIA_APP_COMPAT_CONFIG.get(ctx, userId)) { + si.defaultValueReason = res ? + DVR_APP_COMPAT_CONFIG_HARDENING_OPT_IN : DVR_APP_COMPAT_CONFIG_HARDENING_OPT_OUT; + return res; + } + } + } + + return getDefaultValueInner(ctx, userId, appInfo, ps, si); + } + + protected abstract boolean getDefaultValueInner(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps, StateInfo si); + + + public final boolean get(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps) { + return get(ctx, userId, appInfo, ps, StateInfo.PLACEHOLDER); + } + + public final boolean get(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps, StateInfo si) { + Boolean immValue = getImmutableValue(ctx, userId, appInfo, ps, si); + + boolean res; + if (immValue != null) { + si.isImmutable = true; + res = immValue.booleanValue(); + } else if (isUsingDefaultValue(ps)) { + si.isUsingDefaultValue = true; + res = getDefaultValue(ctx, userId, appInfo, ps, si); + } else { + res = ps.hasFlag(gosPsFlag); + if (gosPsFlagInverted) { + res = !res; + } + } + + return res; + } + + public final void set(GosPackageState.Editor ed, boolean on) { + if (gosPsFlagNonDefault != 0) { + ed.addFlag(gosPsFlagNonDefault); + } + + if (gosPsFlagInverted) { + ed.setFlagState(gosPsFlag, !on); + } else { + ed.setFlagState(gosPsFlag, on); + } + } + + private boolean isUsingDefaultValue(GosPackageState ps) { + return gosPsFlagNonDefault != 0 && !ps.hasFlag(gosPsFlagNonDefault); + } + + public final void setUseDefaultValue(GosPackageState.Editor ed) { + ed.clearFlag(gosPsFlagNonDefault); + ed.clearFlag(gosPsFlag); + } + + public final boolean hasNotification() { + return gosPsFlagSuppressNotif != 0; + } + + public final boolean isNotificationEnabled(GosPackageState ps) { + int flag = gosPsFlagSuppressNotif; + if (flag == 0) { + return false; + } + return !ps.hasFlag(flag); + } + + public final void setNotificationEnabled(GosPackageState.Editor ed, boolean enabled) { + ed.setFlagState(gosPsFlagSuppressNotif, !enabled); + } +} From 7cb6eab2e449fb5a6e106256c7dff86ed5065c32 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Tue, 30 Jul 2024 15:22:30 +0300 Subject: [PATCH 071/332] SettingsLib: add CategoryKey for Exploit protection screen --- .../src/com/android/settingslib/drawer/CategoryKey.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java index fb384ff9fdd03..7281e2f79bd6b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java @@ -70,6 +70,8 @@ public final class CategoryKey { "com.android.settings.category.ia.smart_battery_settings"; public static final String CATEGORY_COMMUNAL_SETTINGS = "com.android.settings.category.ia.communal"; + public static final String CATEGORY_EXPLOIT_PROTECTION_SETTINGS = + "com.android.settings.category.ia.exploit_protection_settings"; public static final String CATEGORY_MORE_SECURITY_PRIVACY_SETTINGS = "com.android.settings.category.ia.more_security_privacy_settings"; public static final String CATEGORY_SUPERVISION = From 3800f92e00db5276a8dff6d96bf34261d18d0640 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 31 Jul 2022 18:24:34 +0300 Subject: [PATCH 072/332] infrastructure for spoofing self permission checks --- core/api/system-current.txt | 5 ++ core/java/android/app/AppOpsManager.java | 39 ++++++++++- .../content/pm/AppPermissionUtils.java | 65 +++++++++++++++++++ .../android/permission/PermissionManager.java | 27 +++++++- 4 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 core/java/android/content/pm/AppPermissionUtils.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 026ddc446d673..32cc8e3771def 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4034,6 +4034,11 @@ package android.content.om { package android.content.pm { + public class AppPermissionUtils { + method public static boolean shouldSkipPermissionRequestDialog(@NonNull android.content.pm.GosPackageState, @NonNull String); + method public static boolean shouldSpoofPermissionRequestResult(@NonNull android.content.pm.GosPackageState, @NonNull String); + } + public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { method @NonNull public android.ext.AppInfoExt ext(); method @RequiresPermission(android.Manifest.permission.DELETE_PACKAGES) public boolean hasFragileUserData(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1864d4a55f2ed..21fe23ac815d4 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -48,6 +48,7 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.AppPermissionUtils; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; @@ -9435,6 +9436,11 @@ private int checkOpRawNoThrow(int op, int uid, @NonNull String packageName, mode = mService.checkOperationRawForDevice( op, uid, packageName, attributionTag, virtualDeviceId); } + if (mode != MODE_ALLOWED && uid == Process.myUid()) { + if (AppPermissionUtils.shouldSpoofSelfAppOpCheck(op)) { + return MODE_ALLOWED; + } + } return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -9712,7 +9718,15 @@ private int noteOpNoThrow(int op, int uid, @Nullable String packageName, } } - return syncOp.getOpMode(); + final int mode = syncOp.getOpMode(); + + if (mode != MODE_ALLOWED && uid == Process.myUid()) { + if (AppPermissionUtils.shouldSpoofSelfAppOpCheck(op)) { + return MODE_ALLOWED; + } + } + + return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -9887,7 +9901,21 @@ public int noteProxyOpNoThrow(int op, @NonNull AttributionSource attributionSour } } - return syncOp.getOpMode(); + final int mode = syncOp.getOpMode(); + + if (mode != MODE_ALLOWED) { + int uid = attributionSource.getUid(); + int nextUid = attributionSource.getNextUid(); + boolean selfCheck = (uid == myUid) && (nextUid == myUid || nextUid == Process.INVALID_UID); + + if (selfCheck) { + if (AppPermissionUtils.shouldSpoofSelfAppOpCheck(op)) { + return MODE_ALLOWED; + } + } + } + + return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -10000,6 +10028,13 @@ private int checkOpNoThrow(int op, int uid, String packageName, @Nullable String mode = mService.checkOperationForDevice(op, uid, packageName, attributionTag, virtualDeviceId); } + + if (mode != MODE_ALLOWED && uid == Process.myUid()) { + if (AppPermissionUtils.shouldSpoofSelfAppOpCheck(op)) { + return MODE_ALLOWED; + } + } + return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/content/pm/AppPermissionUtils.java b/core/java/android/content/pm/AppPermissionUtils.java new file mode 100644 index 0000000000000..fdfc2e7a07b30 --- /dev/null +++ b/core/java/android/content/pm/AppPermissionUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 GrapheneOS + * + * 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.pm; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +/** @hide */ +@SystemApi +public class AppPermissionUtils { + + // If the list of spoofed permissions changes at runtime, make sure to invalidate the permission + // check cache, it's keyed on the PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE system property. + // Updates of GosPackageState invalidate this cache automatically. + // + // android.permission.PermissionManager#checkPermissionUncached + /** @hide */ + public static boolean shouldSpoofSelfCheck(String permName) { + return false; + } + + // android.app.AppOpsManager#checkOpNoThrow + // android.app.AppOpsManager#noteOpNoThrow + // android.app.AppOpsManager#noteProxyOpNoThrow + // android.app.AppOpsManager#unsafeCheckOpRawNoThrow + /** @hide */ + public static boolean shouldSpoofSelfAppOpCheck(int op) { + return false; + } + + public static boolean shouldSkipPermissionRequestDialog(@NonNull GosPackageState ps, @NonNull String perm) { + // Don't check whether the app actually declared this permission: + // app can request a permission that isn't declared in its AndroidManifest and if that + // permission is split into multiple permissions (based on app's targetSdk), and at least + // one of of those split permissions is present in manifest, then permission prompt would be + // shown anyway. + return getSpoofablePermissionDflag(ps, perm) != 0; + } + + // Controls spoofing of Activity#onRequestPermissionsResult() callback + public static boolean shouldSpoofPermissionRequestResult(@NonNull GosPackageState ps, @NonNull String perm) { + int dflag = getSpoofablePermissionDflag(ps, perm); + return dflag != 0 && ps.hasDerivedFlag(dflag); + } + + private static int getSpoofablePermissionDflag(GosPackageState ps, String perm) { + return 0; + } + + private AppPermissionUtils() {} +} diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 07b2b74ea9f4e..7804a4fbc3270 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -55,6 +55,7 @@ import android.content.AttributionSource; import android.content.Context; import android.content.PermissionChecker; +import android.content.pm.AppPermissionUtils; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; @@ -1759,12 +1760,22 @@ private static int checkPermissionUncached(@Nullable String permission, int pid, + permission); return PackageManager.PERMISSION_DENIED; } + int res; try { sShouldWarnMissingActivityManager = true; - return am.checkPermissionForDevice(permission, pid, uid, deviceId); + res = am.checkPermissionForDevice(permission, pid, uid, deviceId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + + if (res != PERMISSION_GRANTED) { + if (uid == android.os.Process.myUid()) { + if (AppPermissionUtils.shouldSpoofSelfCheck(permission)) { + res = PERMISSION_GRANTED; + } + } + } + return res; } private static int getPermissionRequestStateUncached(String packageName, String permission, @@ -2035,12 +2046,24 @@ public boolean equals(@Nullable Object rval) { /* @hide */ private static int checkPackageNamePermissionUncached( String permName, String pkgName, String persistentDeviceId, @UserIdInt int userId) { + int res; try { - return ActivityThread.getPermissionManager().checkPermission( + res = ActivityThread.getPermissionManager().checkPermission( pkgName, permName, persistentDeviceId, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + + if (res != PERMISSION_GRANTED) { + if (pkgName.equals(ActivityThread.currentPackageName()) + && userId == UserHandle.myUserId() + && AppPermissionUtils.shouldSpoofSelfCheck(permName)) + { + res = PERMISSION_GRANTED; + } + } + + return res; } /* @hide */ From 5b302e34f0a46eb988afce4e38edd0335ba8c412 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Thu, 9 Feb 2023 18:53:11 +0200 Subject: [PATCH 073/332] infrastructure for special runtime permissions Squashed with 8320ef57c9d47c6933c753b69c0279a960752014 by Andrew Gunnerson --- core/api/system-current.txt | 3 + .../java/android/app/ActivityThreadHooks.java | 3 + core/java/android/app/AppBindArgs.java | 2 + .../content/pm/AppPermissionUtils.java | 4 + .../android/content/pm/IPackageManager.aidl | 2 + .../pm/SpecialRuntimePermAppUtils.java | 27 ++++++ .../android/content/pm/SrtPermissions.java | 23 +++++ .../server/ext/PackageManagerHooks.java | 3 + .../server/pm/InstallPackageHelper.java | 25 ++++- .../server/pm/PackageManagerService.java | 7 ++ .../PermissionManagerServiceImpl.java | 62 +++++++++--- .../PermissionManagerServiceInternal.java | 19 +++- .../permission/SpecialRuntimePermUtils.java | 97 +++++++++++++++++++ 13 files changed, 259 insertions(+), 18 deletions(-) create mode 100644 core/java/android/content/pm/SpecialRuntimePermAppUtils.java create mode 100644 core/java/android/content/pm/SrtPermissions.java create mode 100644 services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 32cc8e3771def..0cef7cde38c2c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4522,6 +4522,9 @@ package android.content.pm { field @NonNull public static final android.os.Parcelable.Creator CREATOR; } + public class SpecialRuntimePermAppUtils { + } + public final class SuspendDialogInfo implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); diff --git a/core/java/android/app/ActivityThreadHooks.java b/core/java/android/app/ActivityThreadHooks.java index 17e07a6382d90..03649dc85965e 100644 --- a/core/java/android/app/ActivityThreadHooks.java +++ b/core/java/android/app/ActivityThreadHooks.java @@ -3,6 +3,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.GosPackageState; +import android.content.pm.SrtPermissions; import android.os.Bundle; import android.os.Process; import android.os.RemoteException; @@ -45,6 +46,8 @@ static Bundle onBind(Context appContext) { int[] flags = Objects.requireNonNull(args.getIntArray(AppBindArgs.KEY_FLAGS_ARRAY)); + SrtPermissions.setFlags(flags[AppBindArgs.FLAGS_IDX_SPECIAL_RUNTIME_PERMISSIONS]); + return args; } diff --git a/core/java/android/app/AppBindArgs.java b/core/java/android/app/AppBindArgs.java index 14a4900033631..4eb2c18a73ddc 100644 --- a/core/java/android/app/AppBindArgs.java +++ b/core/java/android/app/AppBindArgs.java @@ -5,5 +5,7 @@ public interface AppBindArgs { String KEY_GOS_PACKAGE_STATE = "gosPs"; String KEY_FLAGS_ARRAY = "flagsArr"; + int FLAGS_IDX_SPECIAL_RUNTIME_PERMISSIONS = 0; + int FLAGS_ARRAY_LEN = 10; } diff --git a/core/java/android/content/pm/AppPermissionUtils.java b/core/java/android/content/pm/AppPermissionUtils.java index fdfc2e7a07b30..b8a3610810e87 100644 --- a/core/java/android/content/pm/AppPermissionUtils.java +++ b/core/java/android/content/pm/AppPermissionUtils.java @@ -30,6 +30,10 @@ public class AppPermissionUtils { // android.permission.PermissionManager#checkPermissionUncached /** @hide */ public static boolean shouldSpoofSelfCheck(String permName) { + if (SrtPermissions.shouldSpoofSelfCheck(permName)) { + return true; + } + return false; } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 30422d52398e9..5b4f08ab1aa79 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -874,6 +874,8 @@ interface IPackageManager { @nullable Bundle getExtraAppBindArgs(String packageName); + void skipSpecialRuntimePermissionAutoGrantsForPackage(String packageName, int userId, in List permissions); + android.content.pm.GosPackageState getGosPackageState(String packageName, int userId); boolean setGosPackageState(String packageName, int userId, in android.content.pm.GosPackageState updatedPs, int editorFlags); diff --git a/core/java/android/content/pm/SpecialRuntimePermAppUtils.java b/core/java/android/content/pm/SpecialRuntimePermAppUtils.java new file mode 100644 index 0000000000000..1c947cf2441e2 --- /dev/null +++ b/core/java/android/content/pm/SpecialRuntimePermAppUtils.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 GrapheneOS + * + * 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.pm; + +import android.Manifest; +import android.annotation.SystemApi; + +/** @hide */ +@SystemApi +public class SpecialRuntimePermAppUtils { + + private SpecialRuntimePermAppUtils() {} +} diff --git a/core/java/android/content/pm/SrtPermissions.java b/core/java/android/content/pm/SrtPermissions.java new file mode 100644 index 0000000000000..4f80d26bf76c6 --- /dev/null +++ b/core/java/android/content/pm/SrtPermissions.java @@ -0,0 +1,23 @@ +package android.content.pm; + +import android.Manifest; + +/** @hide */ +public class SrtPermissions { // "special runtime permissions" + private static int flags; + + public static int getFlags() { + return flags; + } + + public static void setFlags(int value) { + flags = value; + } + + public static boolean shouldSpoofSelfCheck(String permName) { + switch (permName) { + default: + return false; + } + } +} diff --git a/services/core/java/com/android/server/ext/PackageManagerHooks.java b/services/core/java/com/android/server/ext/PackageManagerHooks.java index 7e28591ba5066..a9be105c93e63 100644 --- a/services/core/java/com/android/server/ext/PackageManagerHooks.java +++ b/services/core/java/com/android/server/ext/PackageManagerHooks.java @@ -20,6 +20,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.ext.PackageHooks; import com.android.server.pm.permission.Permission; +import com.android.server.pm.permission.SpecialRuntimePermUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.parsing.ParsingPackage; @@ -78,6 +79,8 @@ public static Bundle getExtraAppBindArgs(PackageManagerService pm, String packag requireNonNull(pmComputer.getApplicationInfo(packageName, 0L, userId)); int[] flagsArr = new int[AppBindArgs.FLAGS_ARRAY_LEN]; + flagsArr[AppBindArgs.FLAGS_IDX_SPECIAL_RUNTIME_PERMISSIONS] = + SpecialRuntimePermUtils.getFlags(pm, pkg, pkgState, userId); var b = new Bundle(); b.putParcelable(AppBindArgs.KEY_GOS_PACKAGE_STATE, gosPs); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 06e0e67339c6f..4efc05302944b 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -155,6 +155,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; @@ -759,6 +760,7 @@ public Pair installExistingPackageAsUser(@Nullable String permissionParamsBuilder.setAllowlistedRestrictedPermissions( new ArrayList<>(pkgSetting.getPkg().getRequestedPermissions())); } + permissionParamsBuilder.setNewlyInstalledInUserId(userId); mPm.mPermissionManager.onPackageInstalled(pkgSetting.getPkg(), Process.INVALID_UID /* previousAppId */, permissionParamsBuilder.build(), userId); @@ -2598,7 +2600,13 @@ private void updateSettingsInternalLI(AndroidPackage pkg, } } + final SparseBooleanArray archivedInUserIds = new SparseBooleanArray(); + if (userId != UserHandle.USER_ALL) { + if (PackageArchiver.isArchived(ps.getUserStateOrDefault(userId))) { + archivedInUserIds.put(userId, true); + } + // It's implied that when a user requests installation, they want the app to // be installed and enabled. The caller, however, can explicitly specify to // keep the existing enabled state. @@ -2609,6 +2617,10 @@ private void updateSettingsInternalLI(AndroidPackage pkg, // The caller explicitly specified INSTALL_ALL_USERS flag. // Thus, updating the settings to install the app for all users. for (int currentUserId : allUsers) { + if (PackageArchiver.isArchived(ps.getUserStateOrDefault(currentUserId))) { + archivedInUserIds.put(currentUserId, true); + } + // If the app is already installed for the currentUser, // keep it as installed as we might be updating the app at this place. // If not currently installed, check if the currentUser is restricted by @@ -2662,16 +2674,26 @@ private void updateSettingsInternalLI(AndroidPackage pkg, } } + final PermissionManagerServiceInternal.PackageInstalledParams.Builder + permissionParamsBuilder = + new PermissionManagerServiceInternal.PackageInstalledParams.Builder(); + // Set install reason for users that are having the package newly installed. if (userId == UserHandle.USER_ALL) { for (int currentUserId : allUsers) { if (!previousUserIds.contains(currentUserId) && ps.getInstalled(currentUserId)) { ps.setInstallReason(installReason, currentUserId); + if (!archivedInUserIds.get(currentUserId, false)) { + permissionParamsBuilder.setNewlyInstalledInUserId(currentUserId); + } } } } else if (!previousUserIds.contains(userId)) { ps.setInstallReason(installReason, userId); + if (!archivedInUserIds.get(userId, false)) { + permissionParamsBuilder.setNewlyInstalledInUserId(userId); + } } // TODO(b/169721400): generalize Incremental States and create a Callback object @@ -2692,9 +2714,6 @@ private void updateSettingsInternalLI(AndroidPackage pkg, mPm.mSettings.writeKernelMappingLPr(ps); - final PermissionManagerServiceInternal.PackageInstalledParams.Builder - permissionParamsBuilder = - new PermissionManagerServiceInternal.PackageInstalledParams.Builder(); final boolean grantRequestedPermissions = (installRequest.getInstallFlags() & PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS) != 0; if (grantRequestedPermissions) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 884f5a41998cb..c611a25feee2d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -233,6 +233,7 @@ import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; +import com.android.server.pm.permission.SpecialRuntimePermUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.PackageState; @@ -6734,6 +6735,12 @@ public Bundle getExtraAppBindArgs(String packageName) { return PackageManagerHooks.getExtraAppBindArgs(PackageManagerService.this, packageName); } + @Override + public void skipSpecialRuntimePermissionAutoGrantsForPackage(String packageName, int userId, List permissions) { + mContext.enforceCallingPermission(Manifest.permission.INSTALL_PACKAGES, null); + SpecialRuntimePermUtils.skipAutoGrantsForPackage(packageName, userId, permissions); + } + @Override public GosPackageState getGosPackageState(@NonNull String packageName, int userId) { int callingUid = Binder.getCallingUid(); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index b3326ad28ad74..6cb9b91f801f4 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -57,6 +57,7 @@ import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS; import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; +import static com.android.server.pm.permission.SpecialRuntimePermUtils.isSpecialRuntimePermission; import static java.util.concurrent.TimeUnit.SECONDS; @@ -150,6 +151,7 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.policy.PermissionPolicyInternal; @@ -1448,7 +1450,8 @@ private void grantRuntimePermissionInternal(String packageName, String permName, // their permissions as always granted runtime ones since we need // to keep the review required permission flag per user while an // install permission's state is shared across all users. - if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) { + if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime() && + !isSpecialRuntimePermission(permName)) { return; } @@ -1491,7 +1494,8 @@ private void grantRuntimePermissionInternal(String packageName, String permName, + " for package " + packageName); } - if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { + if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && + !isSpecialRuntimePermission(permName)) { Slog.w(TAG, "Cannot grant runtime permission to a legacy app"); return; } @@ -1635,7 +1639,8 @@ private void revokeRuntimePermissionInternal(String packageName, String permName // their permissions as always granted runtime ones since we need // to keep the review required permission flag per user while an // install permission's state is shared across all users. - if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) { + if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime() && + !isSpecialRuntimePermission(permName)) { return; } @@ -1849,7 +1854,8 @@ private void resetRuntimePermissionsInternal(@NonNull AndroidPackage pkg, // permission as requiring a review as this is the initial state. final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId); final int targetSdk = mPackageManagerInt.getUidTargetSdkVersion(uid); - final int flags = (targetSdk < Build.VERSION_CODES.M && isRuntimePermission) + final int flags = (targetSdk < Build.VERSION_CODES.M && isRuntimePermission + && !isSpecialRuntimePermission(permName)) ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKED_COMPAT : 0; @@ -1869,7 +1875,10 @@ private void resetRuntimePermissionsInternal(@NonNull AndroidPackage pkg, // If this permission was granted by default or role, make sure it is. if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0 - || (oldFlags & FLAG_PERMISSION_GRANTED_BY_ROLE) != 0) { + || (oldFlags & FLAG_PERMISSION_GRANTED_BY_ROLE) != 0 + || (isSpecialRuntimePermission(permName) + && checkPermission(packageName, permName, userId) == PERMISSION_GRANTED) + ) { // PermissionPolicyService will handle the app op for runtime permissions later. grantRuntimePermissionInternal(packageName, permName, false, Process.SYSTEM_UID, userId, delayingPermCallback); @@ -2643,6 +2652,8 @@ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, for (final int userId : userIds) { PackageUserStateInternal pkgUserState = ps.getUserStateOrDefault(userId); + final boolean isNotInstalledUserApp = !ps.isSystem() && !pkgUserState.isInstalled(); + final UserPermissionState userState = mState.getOrCreateUserState(userId); final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId()); @@ -2660,7 +2671,7 @@ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT); } - if (uidTargetSdkVersion < Build.VERSION_CODES.M) { + if (uidTargetSdkVersion < Build.VERSION_CODES.M && !isSpecialRuntimePermission(permissionName)) { uidState.updatePermissionFlags(permission, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, @@ -2838,7 +2849,7 @@ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, boolean restrictionApplied = (origState.getPermissionFlags( bp.getName()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; - if (appSupportsRuntimePermissions) { + if (appSupportsRuntimePermissions || isSpecialRuntimePermission(bp.getName())) { // If hard restricted we don't allow holding it if (permissionPolicyInitialized && hardRestricted) { if (!restrictionExempt) { @@ -2891,6 +2902,26 @@ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, } } } + + if (isSpecialRuntimePermission(permName)) { + if (origPermState == null && ps.isSystem()) { + // always grant special runtime permissions to system packages + if (uidState.grantPermission(bp)) { + wasChanged = true; + } + } + + if (isNotInstalledUserApp) { + // Previously, special runtime permissions were granted in users + // that didn't have the package installed, which breaks the code + // that allows to skip granting these permissions at install time. + // (if UidPermissionState is already present at install time, it's + // reused as is). + if (uidState.revokePermission(bp)) { + wasChanged = true; + } + } + } } else { if (origPermState == null) { // New permission @@ -2925,7 +2956,7 @@ && shouldGrantPermissionByProtectionFlags(pkg, ps, permission, if (restrictionApplied) { flags &= ~FLAG_PERMISSION_APPLY_RESTRICTION; // Dropping restriction on a legacy app implies a review - if (!appSupportsRuntimePermissions) { + if (!appSupportsRuntimePermissions && !isSpecialRuntimePermission(bp.getName())) { flags |= FLAG_PERMISSION_REVIEW_REQUIRED; } wasChanged = true; @@ -3645,7 +3676,7 @@ private boolean isPermissionsReviewRequiredInternal(@NonNull String packageName, } private void grantRequestedPermissionsInternal(@NonNull AndroidPackage pkg, - @Nullable ArrayMap permissionStates, int userId) { + @Nullable ArrayMap permissionStates, int userId, boolean newlyInstalled) { final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED | PackageManager.FLAG_PERMISSION_POLICY_FIXED; @@ -3660,9 +3691,13 @@ private void grantRequestedPermissionsInternal(@NonNull AndroidPackage pkg, final int myUid = Process.myUid(); for (String permission : pkg.getRequestedPermissions()) { + final boolean isPregrantedSpecialRuntimePermission = newlyInstalled && + SpecialRuntimePermUtils.shouldAutoGrant(mContext, pkg.getPackageName(), userId, permission); + Integer permissionState = permissionStates.get(permission); - if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) { + if (!isPregrantedSpecialRuntimePermission + && (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT)) { continue; } @@ -3676,14 +3711,15 @@ private void grantRequestedPermissionsInternal(@NonNull AndroidPackage pkg, shouldGrantRuntimePermission = (bp.isRuntime() || bp.isDevelopment()) && (!instantApp || bp.isInstant()) && (supportsRuntimePermissions || !bp.isRuntimeOnly()) + && permissionState != null && permissionState == PERMISSION_STATE_GRANTED; isAppOpPermission = bp.isAppOp(); } final int flags = getPermissionFlagsInternal(pkg.getPackageName(), permission, myUid, userId); - if (shouldGrantRuntimePermission) { - if (supportsRuntimePermissions) { + if (shouldGrantRuntimePermission || isPregrantedSpecialRuntimePermission) { + if (supportsRuntimePermissions || isPregrantedSpecialRuntimePermission) { // Installer cannot change immutable permissions. if ((flags & immutableFlags) == 0) { grantRuntimePermissionInternal(pkg.getPackageName(), permission, false, @@ -5054,7 +5090,7 @@ private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previou addAllowlistedRestrictedPermissionsInternal(pkg, params.getAllowlistedRestrictedPermissions(), FLAG_PERMISSION_WHITELIST_INSTALLER, userId); - grantRequestedPermissionsInternal(pkg, params.getPermissionStates(), userId); + grantRequestedPermissionsInternal(pkg, params.getPermissionStates(), userId, params.isNewlyInstalledInUserId(userId)); } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index ad765c8a0d54e..ba36ddf00fcf7 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -25,6 +25,7 @@ import android.content.pm.PermissionInfo; import android.permission.PermissionManagerInternal; import android.util.ArrayMap; +import android.util.SparseBooleanArray; import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.TriFunction; @@ -346,13 +347,17 @@ final class PackageInstalledParams { private final List mAllowlistedRestrictedPermissions; @NonNull private final int mAutoRevokePermissionsMode; + @NonNull + private final SparseBooleanArray mNewlyInstalledInUserIds; private PackageInstalledParams(@NonNull ArrayMap permissionStates, @NonNull List allowlistedRestrictedPermissions, - int autoRevokePermissionsMode) { + int autoRevokePermissionsMode, + @NonNull SparseBooleanArray newlyInstalledInUserIds) { mPermissionStates = permissionStates; mAllowlistedRestrictedPermissions = allowlistedRestrictedPermissions; mAutoRevokePermissionsMode = autoRevokePermissionsMode; + mNewlyInstalledInUserIds = newlyInstalledInUserIds; } /** @@ -384,6 +389,10 @@ public int getAutoRevokePermissionsMode() { return mAutoRevokePermissionsMode; } + public boolean isNewlyInstalledInUserId(int userId) { + return mNewlyInstalledInUserIds.get(userId, false); + } + /** * Builder class for {@link PackageInstalledParams}. */ @@ -394,6 +403,8 @@ public static final class Builder { private List mAllowlistedRestrictedPermissions = Collections.emptyList(); @NonNull private int mAutoRevokePermissionsMode = AppOpsManager.MODE_DEFAULT; + @NonNull + private final SparseBooleanArray mNewlyInstalledInUserIds = new SparseBooleanArray(); /** * Set the permissions states requested by the installer. @@ -441,6 +452,10 @@ public void setAutoRevokePermissionsMode(int autoRevokePermissionsMode) { mAutoRevokePermissionsMode = autoRevokePermissionsMode; } + public void setNewlyInstalledInUserId(int userId) { + mNewlyInstalledInUserIds.put(userId, true); + } + /** * Build a new instance of {@link PackageInstalledParams}. * @@ -450,7 +465,7 @@ public void setAutoRevokePermissionsMode(int autoRevokePermissionsMode) { public PackageInstalledParams build() { return new PackageInstalledParams( mPermissionStates == null ? new ArrayMap<>() : mPermissionStates, - mAllowlistedRestrictedPermissions, mAutoRevokePermissionsMode); + mAllowlistedRestrictedPermissions, mAutoRevokePermissionsMode, mNewlyInstalledInUserIds); } } } diff --git a/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java b/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java new file mode 100644 index 0000000000000..a0189ae08516d --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java @@ -0,0 +1,97 @@ +package com.android.server.pm.permission; + +import android.Manifest; +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.os.Build; +import android.os.Bundle; +import android.util.ArraySet; +import android.util.EmptyArray; +import android.util.LruCache; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.server.LocalServices; +import com.android.server.pm.PackageManagerService; +import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.pkg.PackageStateInternal; + +import java.util.List; + +public class SpecialRuntimePermUtils { + private static final String TAG = SpecialRuntimePermUtils.class.getSimpleName(); + + private static final ArraySet specialRuntimePermissions = new ArraySet<>(new String[] { + }); + + public static boolean isSpecialRuntimePermission(String permission) { + return specialRuntimePermissions.contains(permission); + } + + public static String[] getAll() { + return specialRuntimePermissions.toArray(EmptyArray.STRING); + } + + public static boolean shouldAutoGrant(Context ctx, String packageName, int userId, String perm) { + if (!isSpecialRuntimePermission(perm)) { + return false; + } + + return !isAutoGrantSkipped(packageName, userId, perm); + } + + public static int getFlags(PackageManagerService pm, AndroidPackage pkg, PackageState pkgState, int userId) { + int flags = 0; + + for (ParsedUsesPermission perm : pkg.getUsesPermissions()) { + String name = perm.getName(); + switch (name) { + default: + continue; + } + } + + return flags; + } + + // Maps userIds to map of package names to permissions that should not be auto granted + private static SparseArray>> skipAutoGrantsMap = new SparseArray<>(); + + public static void skipAutoGrantsForPackage(String packageName, int userId, List perms) { + PackageStateInternal psi = LocalServices.getService(PackageManagerInternal.class).getPackageStateInternal(packageName); + if (psi != null && psi.isSystem()) { + return; + } + + synchronized (skipAutoGrantsMap) { + LruCache> userMap = skipAutoGrantsMap.get(userId); + if (userMap == null) { + // 50 entries should be enough, only 1 is needed in vast majority of cases + userMap = new LruCache<>(50); + skipAutoGrantsMap.put(userId, userMap); + } + userMap.put(packageName, perms); + } + } + + private static boolean isAutoGrantSkipped(String packageName, int userId, String perm) { + List permList; + synchronized (skipAutoGrantsMap) { + LruCache> userMap = skipAutoGrantsMap.get(userId); + if (userMap == null) { + return false; + } + permList = userMap.get(packageName); + } + if (permList == null) { + return false; + } + return permList.contains(perm); + } + + private SpecialRuntimePermUtils() {} +} From 30a6e5073e50ca134920b212be4285ac70492acd Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Thu, 9 Feb 2023 19:02:24 +0200 Subject: [PATCH 074/332] add special runtime permission for other sensors --- core/api/current.txt | 2 ++ .../android/ext/settings/ExtSettings.java | 3 +++ core/java/android/provider/Settings.java | 4 +++- .../pm/pkg/parsing/ParsingPackage.java | 2 ++ .../pm/pkg/parsing/ParsingPackageUtils.java | 4 ++++ core/res/AndroidManifest.xml | 12 ++++++++++++ core/res/res/values/strings.xml | 12 ++++++++++++ .../permission/SpecialRuntimePermUtils.java | 19 +++++++++++++++++++ 8 files changed, 57 insertions(+), 1 deletion(-) diff --git a/core/api/current.txt b/core/api/current.txt index 07224db7dcd3b..3b2c17d945c7b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -240,6 +240,7 @@ package android { field public static final String NFC = "android.permission.NFC"; field public static final String NFC_PREFERRED_PAYMENT_INFO = "android.permission.NFC_PREFERRED_PAYMENT_INFO"; field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT"; + field public static final String OTHER_SENSORS = "android.permission.OTHER_SENSORS"; field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS"; field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY"; @@ -364,6 +365,7 @@ package android { field public static final String MICROPHONE = "android.permission-group.MICROPHONE"; field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES"; field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS"; + field public static final String OTHER_SENSORS = "android.permission-group.OTHER_SENSORS"; field public static final String PHONE = "android.permission-group.PHONE"; field public static final String READ_MEDIA_AURAL = "android.permission-group.READ_MEDIA_AURAL"; field public static final String READ_MEDIA_VISUAL = "android.permission-group.READ_MEDIA_VISUAL"; diff --git a/core/java/android/ext/settings/ExtSettings.java b/core/java/android/ext/settings/ExtSettings.java index 1f362f3fc260b..e18af7ee51aad 100644 --- a/core/java/android/ext/settings/ExtSettings.java +++ b/core/java/android/ext/settings/ExtSettings.java @@ -28,6 +28,9 @@ public class ExtSettings { public static final BoolSysProperty EXEC_SPAWNING = new BoolSysProperty( "persist.security.exec_spawn", true); + public static final BoolSetting AUTO_GRANT_OTHER_SENSORS_PERMISSION = new BoolSetting( + Setting.Scope.PER_USER, Settings.Secure.AUTO_GRANT_OTHER_SENSORS_PERMISSION, true); + // AppCompatConfig specifies which hardening features are compatible/incompatible with a // specific app. // This setting controls whether incompatible hardening features would be disabled by default diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 71105d035498b..12796bbadb82e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7070,7 +7070,9 @@ public static final class Secure extends NameValueTable { /** @see android.provider.Settings#getPublicSettingsForClass */ // ExtSettings BEGIN - + /** @hide */ + @Protected(readWrite = KnownSystemPackage.SETTINGS) + public static final String AUTO_GRANT_OTHER_SENSORS_PERMISSION = "auto_grant_OTHER_SENSORS_perm"; // ExtSettings END diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index aafcfe8afbd88..50d20dddea279 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -589,4 +589,6 @@ default PackageParsingHooks getPackageParsingHooks() { } void setPackageExt(@Nullable PackageExtIface ext); + + boolean isDeclaredHavingCode(); } diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index e5c1e32e781fe..f1e969953213d 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -1106,6 +1106,10 @@ private ParseResult validateBaseApkTags(ParseInput input, Parsin } } + if (pkg.isDeclaredHavingCode() && usesPerms.add(android.Manifest.permission.OTHER_SENSORS)) { + pkg.addImplicitPermission(android.Manifest.permission.OTHER_SENSORS); + } + convertCompatPermissions(pkg); convertSplitPermissions(pkg); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 9be58ea71d847..5e263bfaaaa11 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1923,6 +1923,18 @@ android:protectionLevel="dangerous|instant" /> + + + + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index cf16f8b27b9e5..486d10b7692a8 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1024,6 +1024,11 @@ access sensitive tracking data, such as eye gaze + + Sensors + + access sensor data about orientation, movement, etc. + Retrieve window content @@ -1448,6 +1453,13 @@ Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background. + + access sensors (like the compass) + + + Allows the app to access data from sensors + monitoring orientation, movement, vibration (including low frequency sound) and environmental data + Read calendar events and details diff --git a/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java b/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java index a0189ae08516d..9c73d51b30519 100644 --- a/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java +++ b/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java @@ -4,6 +4,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.pm.PackageManagerInternal; +import android.ext.settings.ExtSettings; import android.os.Build; import android.os.Bundle; import android.util.ArraySet; @@ -26,6 +27,7 @@ public class SpecialRuntimePermUtils { private static final String TAG = SpecialRuntimePermUtils.class.getSimpleName(); private static final ArraySet specialRuntimePermissions = new ArraySet<>(new String[] { + Manifest.permission.OTHER_SENSORS, }); public static boolean isSpecialRuntimePermission(String permission) { @@ -41,6 +43,23 @@ public static boolean shouldAutoGrant(Context ctx, String packageName, int userI return false; } + if (Manifest.permission.OTHER_SENSORS.equals(perm)) { + if (ActivityManager.getService() == null) { + // a failsafe: should never happen + Slog.d(TAG, "AMS is null"); + if (Build.isDebuggable()) { + throw new IllegalStateException(); + } + return false; + } + + var um = LocalServices.getService(UserManagerInternal.class); + // use parent profile settings for work profile + int userIdForSettings = um.getProfileParentId(userId); + + return ExtSettings.AUTO_GRANT_OTHER_SENSORS_PERMISSION.get(ctx, userIdForSettings); + } + return !isAutoGrantSkipped(packageName, userId, perm); } From ac01453d3a05a84bb37b4e051aacd2e686f63b2f Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Tue, 4 Oct 2022 18:02:35 +0300 Subject: [PATCH 075/332] notify the user when sensors access is denied by OTHER_SENSORS perm Notification is not shown if OTHER_SENSORS was explicitly denied by the user. --- .../SystemNotificationChannels.java | 5 + core/res/res/values/strings.xml | 10 ++ proto/src/system_messages.proto | 4 + ...gSpecialRuntimePermissionNotification.java | 164 ++++++++++++++++++ .../server/pm/PackageManagerNative.java | 9 + 5 files changed, 192 insertions(+) create mode 100644 services/core/java/com/android/server/ext/MissingSpecialRuntimePermissionNotification.java diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index c93e0d592c5b1..de4d1d6d8e421 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -279,7 +279,12 @@ private static NotificationChannel newAccountChannel(Context context) { private SystemNotificationChannels() {} + public static final String MISSING_PERMISSION = "MISSING_PERMISSION"; + private static void extraChannels(Context ctx, List dest) { + channel(ctx, MISSING_PERMISSION, + R.string.notification_channel_missing_permission, + NotificationManager.IMPORTANCE_HIGH, true, dest); } private static NotificationChannel channel(Context ctx, String id, int nameRes, int importance, boolean silent, List dest) { diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 486d10b7692a8..5f58ef6b8a7c2 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6895,4 +6895,14 @@ ul. Censored notifications from the lock screens of other users Notification from %1$s for %2$s Switch to %1$s + + Don’t show again + + Missing permission + + %1$s tried to access sensors + + Tap to ask for permission. + + diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index fbdafe9e4fec0..ba5f8b54dfb4e 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -322,6 +322,10 @@ message SystemMessage { // Package: android NOTE_PROTOTYPE_DETECTED = 400; + // See com.android.server.ext.MissingSpecialRuntimePermissionNotification + // Package: android + NOTE_MISSING_PERMISSION_OTHER_SENSORS = 500; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/core/java/com/android/server/ext/MissingSpecialRuntimePermissionNotification.java b/services/core/java/com/android/server/ext/MissingSpecialRuntimePermissionNotification.java new file mode 100644 index 0000000000000..32057dcc7e6e4 --- /dev/null +++ b/services/core/java/com/android/server/ext/MissingSpecialRuntimePermissionNotification.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2022 GrapheneOS + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.server.ext; + +import android.Manifest; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.os.Bundle; +import android.os.Process; +import android.os.SystemClock; +import android.os.UserHandle; +import android.permission.PermissionManager; +import android.util.ArrayMap; +import android.util.Slog; +import android.util.SparseLongArray; + +import com.android.internal.R; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.internal.notification.SystemNotificationChannels; +import com.android.server.LocalServices; + +import java.util.UUID; + +import static com.android.server.ext.SseUtils.addNotifAction; + +public class MissingSpecialRuntimePermissionNotification { + private static final String TAG = "SrPermissionNotif"; + + private static final ArrayMap lastShownTracker = new ArrayMap<>(); + + public static void maybeShow(Context ctx, String permissionName, int uid, String packageName) { + final long timestamp = SystemClock.uptimeMillis(); + + synchronized (lastShownTracker) { + SparseLongArray uids = lastShownTracker.get(permissionName); + if (uids == null) { + uids = new SparseLongArray(); + lastShownTracker.put(permissionName, uids); + } else { + long prevTs = uids.get(uid, 0); + + if (prevTs != 0 && (timestamp - prevTs) < 30_000L) { + // don't spam notifications for the same app and the same permission + return; + } + + uids.put(uid, timestamp); + } + } + + final UserHandle user = UserHandle.of(UserHandle.getUserId(uid)); + + var permManager = ctx.getSystemService(PermissionManager.class); + + if (permManager == null) { + // might happen during bootup + Slog.e(TAG, "PermissionManager is null"); + return; + } + + final int permFlags = permManager.getPermissionFlags(packageName, permissionName, user); + + if ((permFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0) { + return; + } + + var nb = new Notification.Builder(ctx, SystemNotificationChannels.MISSING_PERMISSION); + nb.setSmallIcon(R.drawable.stat_sys_warning); + + CharSequence appLabel = null; + { + ApplicationInfo appInfo = LocalServices.getService(PackageManagerInternal.class) + .getApplicationInfo(packageName, 0, Process.SYSTEM_UID, user.getIdentifier()); + if (appInfo != null) { + appLabel = appInfo.loadLabel(ctx.getPackageManager()); + } else if (uid < Process.FIRST_APPLICATION_UID) { + appLabel = packageName; + } + if (appLabel == null) { + Slog.d(TAG, "appLabel is null; uid: " + uid + ", pkg: " + packageName + + ", perm: " + permissionName); + return; + } + } + nb.setContentTitle(ctx.getString(R.string.missing_sensors_permission_title, appLabel)); + + nb.setContentText(ctx.getString(notifTextForPermission(permissionName))); + { + Intent intent = ctx.getPackageManager().buildRequestPermissionsIntent(new String[] { permissionName }); + intent.setAction(PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setIdentifier(UUID.randomUUID().toString()); + + var pi = PendingIntent.getActivityAsUser(ctx, 0, intent, + PendingIntent.FLAG_IMMUTABLE, null, user); + nb.setContentIntent(pi); + } + nb.setAutoCancel(true); + + var args = new Bundle(); + args.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + args.putString(Intent.EXTRA_PERMISSION_NAME, permissionName); + args.putParcelable(Intent.EXTRA_USER, user); + + PendingIntent dontShowAgainPi = IntentReceiver.getPendingIntent(NotifActionReceiver.class, + NotifActionReceiver::new, args, ctx); + + addNotifAction(ctx, dontShowAgainPi, R.string.notification_action_dont_show_again, nb); + + var notifManager = ctx.getSystemService(NotificationManager.class); + if (notifManager == null) { + // might happen during bootup + Slog.e(TAG, "NotificationManager is null"); + return; + } + + notifManager.notifyAsUser(null, notifIdForPermission(permissionName), nb.build(), user); + } + + static class NotifActionReceiver extends IntentReceiver { + @Override + public void onReceive(Context ctx, Bundle args) { + String packageName = args.getString(Intent.EXTRA_PACKAGE_NAME); + String permissionName = args.getString(Intent.EXTRA_PERMISSION_NAME); + var user = args.getParcelable(Intent.EXTRA_USER, UserHandle.class); + + final int flag = PackageManager.FLAG_PERMISSION_USER_SET; + + ctx.getSystemService(PermissionManager.class) + .updatePermissionFlags(packageName, permissionName, flag, flag, user); + + ctx.getSystemService(NotificationManager.class) + .cancelAsUser(null, notifIdForPermission(permissionName), user); + } + } + + private static int notifTextForPermission(String perm) { + switch (perm) { + case Manifest.permission.OTHER_SENSORS: + return R.string.missing_sensors_permission_message; + default: + throw new IllegalArgumentException(); + } + } + + private static int notifIdForPermission(String perm) { + switch (perm) { + case Manifest.permission.OTHER_SENSORS: + return SystemMessage.NOTE_MISSING_PERMISSION_OTHER_SENSORS; + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerNative.java b/services/core/java/com/android/server/pm/PackageManagerNative.java index 7d8573e35522f..fb1db2b07278c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerNative.java +++ b/services/core/java/com/android/server/pm/PackageManagerNative.java @@ -187,4 +187,13 @@ public StagedApexInfo[] getStagedApexInfos() { return mPm.mInstallerService.getStagingManager().getStagedApexInfos().toArray( new StagedApexInfo[0]); } + + public void onDeniedSpecialRuntimePermissionOp(String permissionName, int uid, String packageName) { + if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) { + throw new SecurityException(); + } + + com.android.server.ext.MissingSpecialRuntimePermissionNotification + .maybeShow(mPm.getContext(), permissionName, uid, packageName); + } } From bd64052069714d4e3f45fab18b9bd9813ef37c6f Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Sun, 17 Mar 2019 17:59:15 +0200 Subject: [PATCH 076/332] make INTERNET into a special runtime permission --- core/api/current.txt | 1 + core/res/AndroidManifest.xml | 10 +++++++++- core/res/res/values/strings.xml | 5 +++++ .../server/pm/permission/SpecialRuntimePermUtils.java | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/core/api/current.txt b/core/api/current.txt index 3b2c17d945c7b..3d4fe8c4cc98b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -364,6 +364,7 @@ package android { field public static final String LOCATION = "android.permission-group.LOCATION"; field public static final String MICROPHONE = "android.permission-group.MICROPHONE"; field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES"; + field public static final String NETWORK = "android.permission-group.NETWORK"; field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS"; field public static final String OTHER_SENSORS = "android.permission-group.OTHER_SENSORS"; field public static final String PHONE = "android.permission-group.PHONE"; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5e263bfaaaa11..4da2fcea09e8c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2189,13 +2189,21 @@ + + + + android:protectionLevel="dangerous|instant" /> access sensor data about orientation, movement, etc. + + Network + + access the network + Retrieve window content diff --git a/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java b/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java index 9c73d51b30519..d2b3a63f59eb7 100644 --- a/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java +++ b/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java @@ -27,6 +27,7 @@ public class SpecialRuntimePermUtils { private static final String TAG = SpecialRuntimePermUtils.class.getSimpleName(); private static final ArraySet specialRuntimePermissions = new ArraySet<>(new String[] { + Manifest.permission.INTERNET, Manifest.permission.OTHER_SENSORS, }); From d521481e187674b11d3527af5db4ad54cd65c856 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 31 Jul 2022 18:00:35 +0300 Subject: [PATCH 077/332] improve compatibility of INTERNET special runtime permission --- core/api/system-current.txt | 1 + core/java/android/app/DownloadManager.java | 23 +++++++++++++ .../pm/SpecialRuntimePermAppUtils.java | 12 ++++++- .../android/content/pm/SrtPermissions.java | 8 +++++ .../permission/SpecialRuntimePermUtils.java | 32 +++++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0cef7cde38c2c..30f9de7735ab6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4523,6 +4523,7 @@ package android.content.pm { } public class SpecialRuntimePermAppUtils { + method public static boolean isInternetCompatEnabled(); } public final class SuspendDialogInfo implements android.os.Parcelable { diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index f21c3e8d44d6e..396de2eaf1e46 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -34,6 +34,7 @@ import android.database.Cursor; import android.database.CursorWrapper; import android.database.DatabaseUtils; +import android.database.MatrixCursor; import android.net.ConnectivityManager; import android.net.NetworkPolicyManager; import android.net.Uri; @@ -53,6 +54,8 @@ import android.util.Pair; import android.webkit.MimeTypeMap; +import android.content.pm.SpecialRuntimePermAppUtils; + import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; @@ -1121,6 +1124,11 @@ public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray idToMi * future calls related to this download. Returns -1 if the operation fails. */ public long enqueue(Request request) { + if (SpecialRuntimePermAppUtils.isInternetCompatEnabled()) { + // invalid id (DownloadProvider uses SQLite and returns a row id) + return -1; + } + ContentValues values = request.toContentValues(mPackageName); Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); if (downloadUri == null) { @@ -1158,6 +1166,11 @@ public int markRowDeleted(long... ids) { * @return the number of downloads actually removed */ public int remove(long... ids) { + if (SpecialRuntimePermAppUtils.isInternetCompatEnabled()) { + // underlying provider is protected by the INTERNET permission + return 0; + } + return markRowDeleted(ids); } @@ -1173,6 +1186,11 @@ public Cursor query(Query query) { /** @hide */ public Cursor query(Query query, String[] projection) { + if (SpecialRuntimePermAppUtils.isInternetCompatEnabled()) { + // underlying provider is protected by the INTERNET permission + return new MatrixCursor(projection); + } + Cursor underlyingCursor = query.runQuery(mResolver, projection, mBaseUri); if (underlyingCursor == null) { return null; @@ -1551,6 +1569,11 @@ public long addCompletedDownload(String title, String description, throw new IllegalArgumentException(" invalid value for param: totalBytes"); } + if (SpecialRuntimePermAppUtils.isInternetCompatEnabled()) { + // underlying provider is protected by the INTERNET permission + return -1; + } + // if there is already an entry with the given path name in downloads.db, return its id Request request; if (uri != null) { diff --git a/core/java/android/content/pm/SpecialRuntimePermAppUtils.java b/core/java/android/content/pm/SpecialRuntimePermAppUtils.java index 1c947cf2441e2..85ca727f0deff 100644 --- a/core/java/android/content/pm/SpecialRuntimePermAppUtils.java +++ b/core/java/android/content/pm/SpecialRuntimePermAppUtils.java @@ -16,12 +16,22 @@ package android.content.pm; -import android.Manifest; import android.annotation.SystemApi; /** @hide */ @SystemApi public class SpecialRuntimePermAppUtils { + private static boolean isInternetCompatEnabled; + + /** @hide */ + public static void enableInternetCompat() { + isInternetCompatEnabled = true; + } + + public static boolean isInternetCompatEnabled() { + return isInternetCompatEnabled; + } + private SpecialRuntimePermAppUtils() {} } diff --git a/core/java/android/content/pm/SrtPermissions.java b/core/java/android/content/pm/SrtPermissions.java index 4f80d26bf76c6..73338d14b7dc8 100644 --- a/core/java/android/content/pm/SrtPermissions.java +++ b/core/java/android/content/pm/SrtPermissions.java @@ -4,6 +4,8 @@ /** @hide */ public class SrtPermissions { // "special runtime permissions" + public static final int FLAG_INTERNET_COMPAT_ENABLED = 1; + private static int flags; public static int getFlags() { @@ -12,10 +14,16 @@ public static int getFlags() { public static void setFlags(int value) { flags = value; + + if ((value & FLAG_INTERNET_COMPAT_ENABLED) != 0) { + SpecialRuntimePermAppUtils.enableInternetCompat(); + } } public static boolean shouldSpoofSelfCheck(String permName) { switch (permName) { + case Manifest.permission.INTERNET: + return SpecialRuntimePermAppUtils.isInternetCompatEnabled(); default: return false; } diff --git a/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java b/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java index d2b3a63f59eb7..4189091385202 100644 --- a/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java +++ b/services/core/java/com/android/server/pm/permission/SpecialRuntimePermUtils.java @@ -2,8 +2,11 @@ import android.Manifest; import android.app.ActivityManager; +import android.companion.virtual.VirtualDeviceManager; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.SrtPermissions; import android.ext.settings.ExtSettings; import android.os.Build; import android.os.Bundle; @@ -70,6 +73,11 @@ public static int getFlags(PackageManagerService pm, AndroidPackage pkg, Package for (ParsedUsesPermission perm : pkg.getUsesPermissions()) { String name = perm.getName(); switch (name) { + case Manifest.permission.INTERNET: + if (shouldEnableInternetCompat(pkg, pkgState, userId)) { + flags |= SrtPermissions.FLAG_INTERNET_COMPAT_ENABLED; + } + continue; default: continue; } @@ -78,6 +86,30 @@ public static int getFlags(PackageManagerService pm, AndroidPackage pkg, Package return flags; } + private static boolean shouldEnableInternetCompat(AndroidPackage pkg, PackageState pkgState, int userId) { + if (pkgState.isSystem() || pkgState.isUpdatedSystemApp()) { + // system packages should be aware of runtime INTERNET permission + return false; + } + + Bundle metadata = pkg.getMetaData(); + if (metadata != null) { + String key = Manifest.permission.INTERNET + ".mode"; + if ("runtime".equals(metadata.getString(key))) { + // AndroidManifest has + // + // declaration inside the element + return false; + } + } + + var permManager = LocalServices.getService(PermissionManagerServiceInternal.class); + // enable InternetCompat if package doesn't have the INTERNET permission + return permManager.checkPermission(pkg.getPackageName(), + Manifest.permission.INTERNET, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, userId) + != PackageManager.PERMISSION_GRANTED; + } + // Maps userIds to map of package names to permissions that should not be auto granted private static SparseArray>> skipAutoGrantsMap = new SparseArray<>(); From d2d792dbaebe41c06540fcc87023f43d95f6cfc5 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Fri, 19 May 2023 19:52:44 +0300 Subject: [PATCH 078/332] don't run jobs that need connectivity in apps that lack INTERNET perm Apps sometimes misbehave when INTERNET permission is revoked and a job that they scheduled with a connectivity constraint is executed. --- .../server/job/controllers/JobStatus.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 2d069f934d0d0..a17749146af71 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -36,6 +36,7 @@ import android.app.job.UserVisibleJobSummary; import android.content.ClipData; import android.content.ComponentName; +import android.content.Context; import android.net.Network; import android.net.NetworkRequest; import android.net.Uri; @@ -2518,6 +2519,23 @@ private boolean isConstraintsSatisfied(int satisfiedConstraints) { return true; } + if ((mRequiredConstraintsOfInterest & CONSTRAINT_CONNECTIVITY) != 0) { + if ((satisfiedConstraints & CONSTRAINT_CONNECTIVITY) != 0) { + var pmi = LocalServices.getService( + com.android.server.pm.permission.PermissionManagerServiceInternal.class); + + if (pmi.checkUidPermission(getSourceUid(), android.Manifest.permission.INTERNET, Context.DEVICE_ID_DEFAULT) != + android.content.pm.PackageManager.PERMISSION_GRANTED) { + if (DEBUG) { + Slog.d(TAG, "skipping job " + getJobId() + " for " + getSourcePackageName() + + " in user " + getSourceUserId() + ": it has CONSTRAINT_CONNECTIVITY, " + + "but its UID doesn't have the INTERNET permission"); + } + return false; + } + } + } + int sat = satisfiedConstraints; if (overrideState == OVERRIDE_SOFT) { // override: pretend all 'soft' requirements are satisfied From 88b0e2f301b40877ebeb88e1222c51a082af2818 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Fri, 7 Oct 2022 20:47:48 +0300 Subject: [PATCH 079/332] PackageInstallerUI: an option to skip auto-grant of INTERNET permission --- packages/PackageInstaller/AndroidManifest.xml | 1 + .../res/layout/install_content_view.xml | 37 ++++++-- .../PackageInstaller/res/values/strings.xml | 2 + .../PackageInstallerActivity.java | 90 ++++++++++++++++++- 4 files changed, 119 insertions(+), 11 deletions(-) diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 4da73593bdea6..780202849e2aa 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -16,6 +16,7 @@ + diff --git a/packages/PackageInstaller/res/layout/install_content_view.xml b/packages/PackageInstaller/res/layout/install_content_view.xml index affcca1ccaed1..c97525f11b726 100644 --- a/packages/PackageInstaller/res/layout/install_content_view.xml +++ b/packages/PackageInstaller/res/layout/install_content_view.xml @@ -70,14 +70,33 @@ - + + + + + + + + - \ No newline at end of file + diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml index 2197cf5ecc9e7..edddf271960d3 100644 --- a/packages/PackageInstaller/res/values/strings.xml +++ b/packages/PackageInstaller/res/values/strings.xml @@ -362,4 +362,6 @@ More options + + Allow Network permission diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index fc6f3ec81a248..f6dcbdcdc86ce 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -20,8 +20,10 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.Manifest; +import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.Dialog; import android.app.DialogFragment; @@ -40,10 +42,12 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -52,14 +56,18 @@ import android.text.TextUtils; import android.text.method.ScrollingMovementMethod; import android.util.Log; +import android.util.Pair; import android.view.View; import android.widget.Button; +import android.widget.CheckBox; +import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -142,9 +150,10 @@ public class PackageInstallerActivity extends Activity { private AlertDialog mDialog; + private CheckBox mGrantInternetPermission; + private void startInstallConfirm() { TextView viewToEnable; - if (mAppInfo != null) { viewToEnable = mDialog.requireViewById(R.id.install_confirm_question_update); @@ -164,7 +173,20 @@ private void startInstallConfirm() { } } else { // This is a new application with no permissions. - viewToEnable = mDialog.requireViewById(R.id.install_confirm_question); + LinearLayout layout = mDialog.requireViewById(R.id.install_confirm_question); + viewToEnable = layout.requireViewById(R.id.install_confirm_question_text); + + if (mPkgInfo != null) { + ApplicationInfo ai = mPkgInfo.applicationInfo; + boolean isSystemApp = ai != null && ai.isSystemApp(); + String[] perms = mPkgInfo.requestedPermissions; + if (!isSystemApp && perms != null && Arrays.asList(perms).contains(Manifest.permission.INTERNET)) { + mGrantInternetPermission = layout.requireViewById(R.id.install_allow_INTERNET_permission); + mGrantInternetPermission.setVisibility(View.VISIBLE); + } + } + + layout.setVisibility(View.VISIBLE); } viewToEnable.setVisibility(View.VISIBLE); @@ -499,6 +521,8 @@ private void bindUi() { builder.setPositiveButton(getString(R.string.install), (ignored, ignored2) -> { if (mOk.isEnabled()) { + handleSpecialRuntimePermissionAutoGrants(); + if (mSessionId != -1) { setActivityResult(RESULT_OK); finish(); @@ -928,4 +952,66 @@ public void onCancel(DialogInterface dialog) { getActivity().finish(); } } + + void handleSpecialRuntimePermissionAutoGrants() { + if (Build.VERSION.SDK_INT >= 35) { + handleSpecialRuntimePermissionAutoGrantsV2(); + return; + } + + var skipPermissionAutoGrants = new ArrayList(); + + if (mGrantInternetPermission != null) { + if (!mGrantInternetPermission.isChecked()) { + skipPermissionAutoGrants.add(Manifest.permission.INTERNET); + } + } + + var pm = AppGlobals.getPackageManager(); + var pkgName = mPkgInfo.packageName; + int userId = getUserId(); + try { + pm.skipSpecialRuntimePermissionAutoGrantsForPackage(pkgName, + userId, skipPermissionAutoGrants); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static void maybeAddPermissionState(String perm, @Nullable CheckBox checkBox, ArrayList> dst) { + if (checkBox != null) { + int state = checkBox.isChecked() ? + PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED : + PackageInstaller.SessionParams.PERMISSION_STATE_DENIED; + dst.add(Pair.create(perm, Integer.valueOf(state))); + } + } + + void handleSpecialRuntimePermissionAutoGrantsV2() { + int sessionId = mSessionId; + if (sessionId == -1) { + sessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, -1); + if (sessionId == -1) { + return; + } + } + + var list = new ArrayList>(); + maybeAddPermissionState(Manifest.permission.INTERNET, mGrantInternetPermission, list); + + if (list.isEmpty()) { + return; + } + + int num = list.size(); + String[] permissions = new String[num]; + int[] states = new int[num]; + for (int i = 0; i < num; ++i) { + Pair pair = list.get(i); + permissions[i] = pair.first; + states[i] = pair.second.intValue(); + } + + mInstaller.updatePermissionStates(sessionId, permissions, states); + } } From 38eaaaab89dd8b87731564049e94bd00922bf0b6 Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 12 Sep 2017 01:52:11 -0400 Subject: [PATCH 080/332] use permanent fingerprint lockout immediately --- .../sensors/fingerprint/hidl/LockoutFrameworkImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java index 0e05a7923db4c..073d9a3777f64 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java @@ -48,7 +48,7 @@ public class LockoutFrameworkImpl implements LockoutTracker { private static final String ACTION_LOCKOUT_RESET = "com.android.server.biometrics.sensors.fingerprint.ACTION_LOCKOUT_RESET"; private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5; - private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20; + private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 5; private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30 * 1000; private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user"; From cc9e01789fb04f39919c0bd3bf9a843069a43f4e Mon Sep 17 00:00:00 2001 From: Daniel Micay Date: Tue, 25 Jul 2017 11:22:33 -0400 Subject: [PATCH 081/332] add system property for disabling keyguard camera Change-Id: I0b65cac3c3d2fc495b339c34add742bd698b107c --- core/java/android/ext/settings/ExtSettings.java | 3 +++ .../java/com/android/server/GestureLauncherService.java | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/core/java/android/ext/settings/ExtSettings.java b/core/java/android/ext/settings/ExtSettings.java index e18af7ee51aad..cc3c7ec30e2d7 100644 --- a/core/java/android/ext/settings/ExtSettings.java +++ b/core/java/android/ext/settings/ExtSettings.java @@ -28,6 +28,9 @@ public class ExtSettings { public static final BoolSysProperty EXEC_SPAWNING = new BoolSysProperty( "persist.security.exec_spawn", true); + public static final BoolSetting ALLOW_KEYGUARD_CAMERA = new BoolSetting( + Setting.Scope.SYSTEM_PROPERTY, "persist.keyguard.camera", true); + public static final BoolSetting AUTO_GRANT_OTHER_SENSORS_PERMISSION = new BoolSetting( Setting.Scope.PER_USER, Settings.Secure.AUTO_GRANT_OTHER_SENSORS_PERMISSION, true); diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index 28258ae47a65e..aaa0949af927d 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.KeyguardManager; import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; @@ -32,6 +33,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; +import android.ext.settings.ExtSettings; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -844,6 +846,11 @@ private void sendPendingIntentWithBackgroundStartPrivileges(PendingIntent pendin boolean handleCameraGesture(boolean useWakelock, int source) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "GestureLauncher:handleCameraGesture"); try { + if (!ExtSettings.ALLOW_KEYGUARD_CAMERA.get(mContext)) { + if (mContext.getSystemService(KeyguardManager.class).isKeyguardLocked()) { + return false; + } + } boolean userSetupComplete = isUserSetupComplete(); if (!userSetupComplete) { if (DBG) { From 25353ab37a4ce2736df7b446cf042341c89ffe09 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sat, 13 Jan 2024 21:19:00 +0200 Subject: [PATCH 082/332] add auto-reboot feature Requires the corresponding changes to system/core and system/sepolicy. --- .../android/ext/settings/ExtSettings.java | 5 +++ core/java/android/provider/Settings.java | 4 ++ .../server/policy/keyguard/AutoReboot.java | 39 +++++++++++++++++++ .../policy/keyguard/KeyguardStateMonitor.java | 2 + 4 files changed, 50 insertions(+) create mode 100644 services/core/java/com/android/server/policy/keyguard/AutoReboot.java diff --git a/core/java/android/ext/settings/ExtSettings.java b/core/java/android/ext/settings/ExtSettings.java index cc3c7ec30e2d7..20b1ff9594fac 100644 --- a/core/java/android/ext/settings/ExtSettings.java +++ b/core/java/android/ext/settings/ExtSettings.java @@ -34,6 +34,11 @@ public class ExtSettings { public static final BoolSetting AUTO_GRANT_OTHER_SENSORS_PERMISSION = new BoolSetting( Setting.Scope.PER_USER, Settings.Secure.AUTO_GRANT_OTHER_SENSORS_PERMISSION, true); + public static final IntSetting AUTO_REBOOT_TIMEOUT = new IntSetting( + Setting.Scope.GLOBAL, Settings.Global.AUTO_REBOOT_TIMEOUT, + // default value: 18 hours + (int) TimeUnit.HOURS.toMillis(18)); + // AppCompatConfig specifies which hardening features are compatible/incompatible with a // specific app. // This setting controls whether incompatible hardening features would be disabled by default diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 12796bbadb82e..1f5f865cee632 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13428,6 +13428,10 @@ public static final class Global extends NameValueTable { public static final String ALLOW_DISABLING_HARDENING_VIA_APP_COMPAT_CONFIG = "allow_automatic_pkg_hardening_config"; // historical name + /** @hide */ + @Protected(readWrite = KnownSystemPackage.SETTINGS) + public static final String AUTO_REBOOT_TIMEOUT = "settings_reboot_after_timeout"; + // ExtSettings END // NOTE: If you add new settings here, be sure to add them to diff --git a/services/core/java/com/android/server/policy/keyguard/AutoReboot.java b/services/core/java/com/android/server/policy/keyguard/AutoReboot.java new file mode 100644 index 0000000000000..603e458639e62 --- /dev/null +++ b/services/core/java/com/android/server/policy/keyguard/AutoReboot.java @@ -0,0 +1,39 @@ +package com.android.server.policy.keyguard; + +import android.content.Context; +import android.ext.settings.ExtSettings; +import android.os.SystemProperties; +import android.util.Slog; + +/** @hide */ +class AutoReboot { + private static final String TAG = AutoReboot.class.getSimpleName(); + + // writes to this system property are special-cased in init + private static final String SYS_PROP = "sys.auto_reboot_ctl"; + + // This callback is invoked: + // - when keyguard becomes active (i.e. when device gets locked, including at boot-time) + // - when keyguard is dismissed by unlocking the device + // - when keyguard is dismissed by switching to a user that doesn't have a secure lockscreen, + // but not when switching to a user that does have a secure lockscreen + // - showing=false callback is invoked at boot-time when there's no lock screen, i.e. when the + // device boots straight into the home screen or initial setup wizard + // + // Note that "swipe-to-unlock" lockscreen is considered to be a keyguard. + static void onKeyguardShowingStateChanged(Context ctx, boolean showing, int userId) { + Slog.d(TAG, "onKeyguardShowingStateChanged, showing: " + showing + ", userId: " + userId); + + if (!showing) { + SystemProperties.set(SYS_PROP, "on_device_unlocked"); + return; + } + + final int timeoutMillis = ExtSettings.AUTO_REBOOT_TIMEOUT.get(ctx); + final int timeoutSeconds = timeoutMillis / 1000; + if (timeoutSeconds > 0) { + SystemProperties.set(SYS_PROP, Integer.toString(timeoutSeconds)); + } + Slog.d(TAG, "timeoutSeconds: " + timeoutSeconds); + } +} diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index c0aa8aeff7111..a925077c831ea 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -89,6 +89,8 @@ public void onShowingStateChanged(boolean showing, int userId) { mIsShowing = showing; mCallback.onShowingChanged(); + + AutoReboot.onKeyguardShowingStateChanged(mContext, showing, userId); } @Override // Binder interface From 09eda646f4d5d451cd0d09edc27b23914aa430d2 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 31 Jul 2022 15:22:06 +0300 Subject: [PATCH 083/332] infrastructure for the Storage Scopes feature --- core/api/system-current.txt | 18 ++ .../java/android/app/ActivityThreadHooks.java | 3 + core/java/android/app/Instrumentation.java | 2 + core/java/android/app/StorageScope.java | 159 +++++++++++++ core/java/android/app/WallpaperManager.java | 12 + .../content/pm/AppPermissionUtils.java | 28 ++- .../internal/app/StorageScopesAppHooks.java | 211 ++++++++++++++++++ .../ExternalStorageProvider.java | 18 ++ 8 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 core/java/android/app/StorageScope.java create mode 100644 core/java/com/android/internal/app/StorageScopesAppHooks.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 30f9de7735ab6..0d393acb24be6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1194,6 +1194,24 @@ package android.app { method public boolean isStatusBarExpansionDisabled(); } + public final class StorageScope { + ctor public StorageScope(@NonNull String, int); + method @NonNull public static android.content.Intent createConfigActivityIntent(@NonNull String); + method @NonNull public static android.app.StorageScope[] deserializeArray(@NonNull android.content.pm.GosPackageState); + method public boolean isDirectory(); + method public boolean isFile(); + method public boolean isWritable(); + method public static int maxArrayLength(); + method @Nullable public static byte[] serializeArray(@NonNull android.app.StorageScope[]); + field public static final String EXTERNAL_STORAGE_PROVIDER_METHOD_CONVERT_DOC_ID_TO_PATH = "StorageScopes_convertDocIdToPath"; + field public static final int FLAG_ALLOW_WRITES = 1; // 0x1 + field public static final int FLAG_IS_DIR = 2; // 0x2 + field public static final String MEDIA_PROVIDER_METHOD_INVALIDATE_MEDIA_PROVIDER_CACHE = "StorageScopes_invalidateCache"; + field public static final String MEDIA_PROVIDER_METHOD_MEDIA_ID_TO_FILE_PATH = "StorageScopes_mediaIdToFilePath"; + field public final int flags; + field @NonNull public final String path; + } + public final class SystemServiceRegistry { method public static void registerContextAwareService(@NonNull String, @NonNull Class, @NonNull android.app.SystemServiceRegistry.ContextAwareServiceProducerWithBinder); method public static void registerContextAwareService(@NonNull String, @NonNull Class, @NonNull android.app.SystemServiceRegistry.ContextAwareServiceProducerWithoutBinder); diff --git a/core/java/android/app/ActivityThreadHooks.java b/core/java/android/app/ActivityThreadHooks.java index 03649dc85965e..594556e3f57a2 100644 --- a/core/java/android/app/ActivityThreadHooks.java +++ b/core/java/android/app/ActivityThreadHooks.java @@ -9,6 +9,8 @@ import android.os.RemoteException; import android.util.Log; +import com.android.internal.app.StorageScopesAppHooks; + import java.util.Objects; class ActivityThreadHooks { @@ -62,6 +64,7 @@ static void onBind2(Context appContext, Bundle appBindArgs) { // called from both main and worker threads static void onGosPackageStateChanged(Context ctx, GosPackageState state, boolean fromBind) { + StorageScopesAppHooks.maybeEnable(state); } static Service instantiateService(String className) { diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 19fecb9bf7c28..47f0a95a46a34 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -64,6 +64,7 @@ import android.view.Window; import android.view.WindowManagerGlobal; +import com.android.internal.app.StorageScopesAppHooks; import com.android.internal.content.ReferrerIntent; import java.io.File; @@ -1992,6 +1993,7 @@ public ActivityResult execStartActivity( try { intent.migrateExtraStreamToClipData(who); intent.prepareToLeaveProcess(who); + StorageScopesAppHooks.maybeModifyActivityIntent(who, intent); int result = ActivityTaskManager.getService().startActivity(whoThread, who.getOpPackageName(), who.getAttributionTag(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, diff --git a/core/java/android/app/StorageScope.java b/core/java/android/app/StorageScope.java new file mode 100644 index 0000000000000..c9f1af1249541 --- /dev/null +++ b/core/java/android/app/StorageScope.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2022 GrapheneOS + * + * 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.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.content.Intent; +import android.content.pm.GosPackageState; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; + +/** + * @hide + */ +@SystemApi +public final class StorageScope { + private static final String TAG = "StorageScope"; + + @NonNull + public final String path; + public final int flags; // note that flags are cast to short during serialization + + public static final int FLAG_ALLOW_WRITES = 1; + public static final int FLAG_IS_DIR = 1 << 1; + + public StorageScope(@NonNull String path, int flags) { + this.path = path; + this.flags = flags; + } + + @NonNull + public static Intent createConfigActivityIntent(@NonNull String targetPkg) { + Intent i = new Intent(Intent.ACTION_MAIN); + i.setClassName("com.android.permissioncontroller", + "com.android.permissioncontroller.sscopes.StorageScopesActivity"); + i.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPkg); + return i; + } + + public static int maxArrayLength() { + // Should be less than Byte.MAX_VALUE (it is cast to byte during serialization). + // Note that the MediaProvider filtering based on StorageScopes is O(n), + // where n is the number of the StorageScopes + return 20; + } + + public boolean isWritable() { + return (flags & FLAG_ALLOW_WRITES) != 0; + } + + public boolean isDirectory() { + return (flags & FLAG_IS_DIR) != 0; + } + + public boolean isFile() { + return (flags & FLAG_IS_DIR) == 0; + } + + private static final int VERSION = 0; + + @Nullable + public static byte[] serializeArray(@NonNull @SuppressLint("ArrayReturn") StorageScope[] array) { + if (array.length == 0) { + return null; // special case to minimize the size of persistent state + } + + ByteArrayOutputStream bos = new ByteArrayOutputStream(1000); + DataOutputStream s = new DataOutputStream(bos); + try { + s.writeByte(VERSION); + + final int cnt = array.length; + if (cnt > maxArrayLength()) { + throw new IllegalStateException(); + } + s.writeByte(cnt); + + for (int i = 0; i < cnt; ++i) { + StorageScope scope = array[i]; + s.writeUTF(scope.path); + s.writeShort(scope.flags); + } + return bos.toByteArray(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @NonNull @SuppressLint("ArrayReturn") + public static StorageScope[] deserializeArray(@NonNull GosPackageState gosPackageState) { + byte[] ser = gosPackageState.storageScopes; + + if (ser == null) { + return new StorageScope[0]; + } + + DataInputStream s = new DataInputStream(new ByteArrayInputStream(ser)); + try { + final int version = s.readByte(); + if (version != StorageScope.VERSION) { + Log.e(TAG, "unexpected version " + version); + return new StorageScope[0]; + } + + int cnt = s.readByte(); + StorageScope[] arr = new StorageScope[cnt]; + for (int i = 0; i < cnt; ++i) { + String path = s.readUTF(); + short pathFlags = (short) s.readUnsignedShort(); + + arr[i] = new StorageScope(path, pathFlags); + } + + return arr; + } catch (Exception e) { + Log.e(TAG, "deserialization failed", e); + return new StorageScope[0]; + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof StorageScope)) { + return false; + } + StorageScope o = (StorageScope) obj; + return path.equals(o.path) && flags == o.flags; + } + + @Override + public int hashCode() { + return 31 * flags + path.hashCode(); + } + + public static final String MEDIA_PROVIDER_METHOD_INVALIDATE_MEDIA_PROVIDER_CACHE = "StorageScopes_invalidateCache"; + public static final String MEDIA_PROVIDER_METHOD_MEDIA_ID_TO_FILE_PATH = "StorageScopes_mediaIdToFilePath"; + + public static final String EXTERNAL_STORAGE_PROVIDER_METHOD_CONVERT_DOC_ID_TO_PATH = "StorageScopes_convertDocIdToPath"; +} diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index e8d2e2871ef2f..53a802484f432 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -99,6 +99,7 @@ import com.android.internal.R; import com.android.internal.annotations.Keep; +import com.android.internal.app.StorageScopesAppHooks; import libcore.io.IoUtils; @@ -778,6 +779,11 @@ public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault, return getDefaultWallpaper(context, FLAG_SYSTEM); } + if (StorageScopesAppHooks.isEnabled()) { + Log.d("StorageScopes", "returning default wallpaper"); + return getDefaultWallpaper(context, FLAG_SYSTEM); + } + if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { Log.w(TAG, "No permission to access wallpaper, suppressing" + " exception to avoid crashing legacy app."); @@ -1956,6 +1962,12 @@ private ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int + " wallpaper file to avoid crashing legacy app."); return getDefaultSystemWallpaperFile(); } + + if (StorageScopesAppHooks.isEnabled()) { + Log.d("StorageScopes", "returning default wallpaper file"); + return getDefaultSystemWallpaperFile(); + } + if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { Log.w(TAG, "No permission to access wallpaper, suppressing" + " exception to avoid crashing legacy app."); diff --git a/core/java/android/content/pm/AppPermissionUtils.java b/core/java/android/content/pm/AppPermissionUtils.java index b8a3610810e87..d7d7c6e9fded6 100644 --- a/core/java/android/content/pm/AppPermissionUtils.java +++ b/core/java/android/content/pm/AppPermissionUtils.java @@ -19,6 +19,8 @@ import android.annotation.NonNull; import android.annotation.SystemApi; +import com.android.internal.app.StorageScopesAppHooks; + /** @hide */ @SystemApi public class AppPermissionUtils { @@ -30,6 +32,10 @@ public class AppPermissionUtils { // android.permission.PermissionManager#checkPermissionUncached /** @hide */ public static boolean shouldSpoofSelfCheck(String permName) { + if (StorageScopesAppHooks.shouldSpoofSelfPermissionCheck(permName)) { + return true; + } + if (SrtPermissions.shouldSpoofSelfCheck(permName)) { return true; } @@ -43,6 +49,10 @@ public static boolean shouldSpoofSelfCheck(String permName) { // android.app.AppOpsManager#unsafeCheckOpRawNoThrow /** @hide */ public static boolean shouldSpoofSelfAppOpCheck(int op) { + if (StorageScopesAppHooks.shouldSpoofSelfAppOpCheck(op)) { + return true; + } + return false; } @@ -52,16 +62,28 @@ public static boolean shouldSkipPermissionRequestDialog(@NonNull GosPackageState // permission is split into multiple permissions (based on app's targetSdk), and at least // one of of those split permissions is present in manifest, then permission prompt would be // shown anyway. - return getSpoofablePermissionDflag(ps, perm) != 0; + return getSpoofablePermissionDflag(ps, perm, true) != 0; } // Controls spoofing of Activity#onRequestPermissionsResult() callback public static boolean shouldSpoofPermissionRequestResult(@NonNull GosPackageState ps, @NonNull String perm) { - int dflag = getSpoofablePermissionDflag(ps, perm); + int dflag = getSpoofablePermissionDflag(ps, perm, false); return dflag != 0 && ps.hasDerivedFlag(dflag); } - private static int getSpoofablePermissionDflag(GosPackageState ps, String perm) { + private static int getSpoofablePermissionDflag(GosPackageState ps, String perm, boolean forRequestDialog) { + if (ps.hasFlag(GosPackageStateFlag.STORAGE_SCOPES_ENABLED)) { + int permDflag = StorageScopesAppHooks.getSpoofablePermissionDflag(perm); + if (permDflag != 0) { + if (!forRequestDialog) { + if (StorageScopesAppHooks.shouldSkipPermissionCheckSpoof(ps.derivedFlags, permDflag)) { + return 0; + } + } + return permDflag; + } + } + return 0; } diff --git a/core/java/com/android/internal/app/StorageScopesAppHooks.java b/core/java/com/android/internal/app/StorageScopesAppHooks.java new file mode 100644 index 0000000000000..717f249f0cf33 --- /dev/null +++ b/core/java/com/android/internal/app/StorageScopesAppHooks.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2022 GrapheneOS + * + * 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.app; + +import android.Manifest; +import android.annotation.AnyThread; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.GosPackageState; +import android.content.pm.GosPackageStateFlag; +import android.ext.DerivedPackageFlag; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.provider.Settings; + +public class StorageScopesAppHooks { + private static final String TAG = "StorageScopesAppHooks"; + + private static volatile boolean isEnabled; + private static int gosPsDerivedFlags; + + @AnyThread + public static void maybeEnable(GosPackageState ps) { + if (isEnabled) { + return; + } + + if (ps.hasFlag(GosPackageStateFlag.STORAGE_SCOPES_ENABLED)) { + gosPsDerivedFlags = ps.derivedFlags; + isEnabled = true; + } + } + + public static boolean isEnabled() { + return isEnabled; + } + + public static boolean shouldSkipPermissionCheckSpoof(int gosPsDflags, int permDerivedFlag) { + if ((gosPsDflags & DerivedPackageFlag.HAS_READ_MEDIA_VISUAL_USER_SELECTED_DECLARATION) != 0) { + switch (permDerivedFlag) { + case DerivedPackageFlag.HAS_READ_MEDIA_AUDIO_DECLARATION: + case DerivedPackageFlag.HAS_READ_MEDIA_VIDEO_DECLARATION: + // see https://developer.android.com/about/versions/14/changes/partial-photo-video-access + return true; + } + } + + return false; + } + + // call only if isEnabled == true + private static boolean shouldSpoofSelfPermissionCheckInner(int permDerivedFlag) { + if (permDerivedFlag == 0) { + return false; + } + + if (shouldSkipPermissionCheckSpoof(gosPsDerivedFlags, permDerivedFlag)) { + return false; + } + + return (gosPsDerivedFlags & permDerivedFlag) != 0; + } + + public static boolean shouldSpoofSelfPermissionCheck(String permName) { + if (!isEnabled) { + return false; + } + + return shouldSpoofSelfPermissionCheckInner(getSpoofablePermissionDflag(permName)); + } + + public static boolean shouldSpoofSelfAppOpCheck(int op) { + if (!isEnabled) { + return false; + } + + return shouldSpoofSelfPermissionCheckInner(getSpoofableAppOpPermissionDflag(op)); + } + + // Instrumentation#execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle) + public static void maybeModifyActivityIntent(Context ctx, Intent i) { + String action = i.getAction(); + if (action == null) { + return; + } + + int op; + switch (action) { + case Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION: + op = AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE; + break; + case Settings.ACTION_REQUEST_MANAGE_MEDIA: + op = AppOpsManager.OP_MANAGE_MEDIA; + break; + default: + return; + } + + Uri uri = i.getData(); + if (uri == null || !"package".equals(uri.getScheme())) { + return; + } + + String pkgName = uri.getSchemeSpecificPart(); + + if (pkgName == null) { + return; + } + + if (!pkgName.equals(ctx.getPackageName())) { + return; + } + + boolean shouldModify = false; + + if (shouldSpoofSelfAppOpCheck(op)) { + // in case a buggy app launches intent again despite pseudo-having the permission + shouldModify = true; + } else { + if (op == AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE) { + shouldModify = !Environment.isExternalStorageManager(); + } else if (op == AppOpsManager.OP_MANAGE_MEDIA) { + shouldModify = !MediaStore.canManageMedia(ctx); + } + } + + if (shouldModify) { + i.setAction(action + "_PROMPT"); + } + } + + public static int getSpoofablePermissionDflag(String permName) { + switch (permName) { + case Manifest.permission.READ_EXTERNAL_STORAGE: + return DerivedPackageFlag.HAS_READ_EXTERNAL_STORAGE_DECLARATION; + + case Manifest.permission.WRITE_EXTERNAL_STORAGE: + return DerivedPackageFlag.HAS_WRITE_EXTERNAL_STORAGE_DECLARATION; + + case Manifest.permission.ACCESS_MEDIA_LOCATION: + return DerivedPackageFlag.HAS_ACCESS_MEDIA_LOCATION_DECLARATION; + + case Manifest.permission.READ_MEDIA_AUDIO: + return DerivedPackageFlag.HAS_READ_MEDIA_AUDIO_DECLARATION; + + case Manifest.permission.READ_MEDIA_IMAGES: + return DerivedPackageFlag.HAS_READ_MEDIA_IMAGES_DECLARATION; + + case Manifest.permission.READ_MEDIA_VIDEO: + return DerivedPackageFlag.HAS_READ_MEDIA_VIDEO_DECLARATION; + + case Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED: + return DerivedPackageFlag.HAS_READ_MEDIA_VISUAL_USER_SELECTED_DECLARATION; + + default: + return 0; + } + } + + private static int getSpoofableAppOpPermissionDflag(int op) { + switch (op) { + case AppOpsManager.OP_READ_EXTERNAL_STORAGE: + return DerivedPackageFlag.HAS_READ_EXTERNAL_STORAGE_DECLARATION; + + case AppOpsManager.OP_WRITE_EXTERNAL_STORAGE: + return DerivedPackageFlag.HAS_WRITE_EXTERNAL_STORAGE_DECLARATION; + + case AppOpsManager.OP_READ_MEDIA_AUDIO: + return DerivedPackageFlag.HAS_READ_MEDIA_AUDIO_DECLARATION; + + case AppOpsManager.OP_READ_MEDIA_IMAGES: + return DerivedPackageFlag.HAS_READ_MEDIA_IMAGES_DECLARATION; + + case AppOpsManager.OP_READ_MEDIA_VIDEO: + return DerivedPackageFlag.HAS_READ_MEDIA_VIDEO_DECLARATION; + + case AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE: + return DerivedPackageFlag.HAS_MANAGE_EXTERNAL_STORAGE_DECLARATION; + + case AppOpsManager.OP_MANAGE_MEDIA: + return DerivedPackageFlag.HAS_MANAGE_MEDIA_DECLARATION; + + case AppOpsManager.OP_ACCESS_MEDIA_LOCATION: + return DerivedPackageFlag.HAS_ACCESS_MEDIA_LOCATION_DECLARATION; + + case AppOpsManager.OP_READ_MEDIA_VISUAL_USER_SELECTED: + return DerivedPackageFlag.HAS_READ_MEDIA_VISUAL_USER_SELECTED_DECLARATION; + + default: + return 0; + } + } + + private StorageScopesAppHooks() {} +} diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 28b891ebc3c93..6d683904df315 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -828,6 +828,24 @@ public Bundle call(String method, String arg, Bundle extras) { throw new IllegalStateException(e); } } + case android.app.StorageScope.EXTERNAL_STORAGE_PROVIDER_METHOD_CONVERT_DOC_ID_TO_PATH: { + // only PermissionController is expected to call this method + getContext().enforceCallingPermission( + android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null); + + String docId = arg; + String path; + try { + path = getFileForDocId(docId, true).getAbsolutePath(); + } catch (Exception e) { + Log.d(TAG, method + " failed", e); + return null; + } + + final Bundle out = new Bundle(); + out.putString(DocumentsContract.EXTRA_RESULT, path); + return out; + } default: Log.w(TAG, "unknown method passed to call(): " + method); } From 2c83694787691746272a99f2ea50a8de6d99c9da Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Thu, 5 Oct 2023 22:52:38 +0300 Subject: [PATCH 084/332] sscopes: temp: disable incomplete handling of READ_MEDIA_VISUAL_USER_SELECTED Treat it same way other storage perms are treated for now. --- core/java/com/android/internal/app/StorageScopesAppHooks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/com/android/internal/app/StorageScopesAppHooks.java b/core/java/com/android/internal/app/StorageScopesAppHooks.java index 717f249f0cf33..6864603c3d792 100644 --- a/core/java/com/android/internal/app/StorageScopesAppHooks.java +++ b/core/java/com/android/internal/app/StorageScopesAppHooks.java @@ -57,7 +57,7 @@ public static boolean shouldSkipPermissionCheckSpoof(int gosPsDflags, int permDe case DerivedPackageFlag.HAS_READ_MEDIA_AUDIO_DECLARATION: case DerivedPackageFlag.HAS_READ_MEDIA_VIDEO_DECLARATION: // see https://developer.android.com/about/versions/14/changes/partial-photo-video-access - return true; + return false; } } From c0e140465137f38e60b38241256799d0bc94a723 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 31 Jul 2022 15:08:46 +0300 Subject: [PATCH 085/332] control access to Android/obb directory with a GosPackageState flag --- .../android/server/StorageManagerService.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 3adbd229d2007..970a0e5440df2 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -67,6 +67,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; +import android.content.pm.GosPackageStateFlag; import android.content.pm.IPackageManager; import android.content.pm.IPackageMoveObserver; import android.content.pm.PackageManager; @@ -4680,9 +4682,25 @@ private int getMountModeInternal(int uid, String packageName) { break; } } - if (hasInstall || hasInstallOp) { + if (hasInstall) { return StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER; } + + if (hasInstallOp) { + /* + Originally, previous check was `if (hasInstall || hasInstallOp)`. + + Use a special flag to control access to just Android/obb directory instead. + Toggle for this flag is in ExternalSourcesDetails.java in Settings app + (same screen that grants the REQUEST_INSTALL_PACKAGES permission) + */ + + GosPackageState ps = mPmInternal.getGosPackageState(packageName, UserHandle.getUserId(uid)); + if (ps.hasFlag(GosPackageStateFlag.ALLOW_ACCESS_TO_OBB_DIRECTORY)) { + return StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER; + } + } + return StorageManager.MOUNT_MODE_EXTERNAL_DEFAULT; } catch (RemoteException e) { // Should not happen From 8cc3daa41ac4b50c3b5cb1039e294d4f9b8599f5 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sun, 1 Oct 2023 15:24:28 +0300 Subject: [PATCH 086/332] ErrorDialogController: do not auto-dismiss crash dialogs This is a workaround for a bug that auto-dismisses crash dialog for native crash almost immediately after it is shown. Crash dialogs are shown only for foreground apps by default, there's no need to auto dismiss them. --- .../core/java/com/android/server/am/ErrorDialogController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/am/ErrorDialogController.java b/services/core/java/com/android/server/am/ErrorDialogController.java index 82f35adbb1344..03f437106c967 100644 --- a/services/core/java/com/android/server/am/ErrorDialogController.java +++ b/services/core/java/com/android/server/am/ErrorDialogController.java @@ -98,7 +98,7 @@ boolean hasDebugWaitingDialog() { @GuardedBy("mProcLock") void clearAllErrorDialogs() { - clearCrashDialogs(); + clearCrashDialogs(false); clearAnrDialogs(); clearViolationDialogs(); clearWaitingDialog(); From 5011b9f97ef5fa9179d10ee5945d26acb2623410 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Fri, 1 Nov 2024 10:08:22 +0200 Subject: [PATCH 087/332] fix ANR details link not working in profiles and in secondary users Crash report dialog is not affected, it uses startActivityAsUser() already. --- .../java/com/android/server/am/AppNotRespondingDialog.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java index d3e91da3d330e..69120e5356582 100644 --- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java +++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.UserHandle; import android.text.BidiFormatter; import android.util.Slog; import android.view.LayoutInflater; @@ -179,7 +180,7 @@ public void handleMessage(Message msg) { if (appErrorIntent != null) { try { - getContext().startActivity(appErrorIntent); + getContext().startActivityAsUser(appErrorIntent, UserHandle.of(mProc.userId)); } catch (ActivityNotFoundException e) { Slog.w(TAG, "bug report receiver dissappeared", e); } From 96f3a0401c356403bcf119618fc04070af69cb77 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Mon, 7 Oct 2024 18:59:24 +0300 Subject: [PATCH 088/332] include path of ANR stack traces file in ApplicationErrorReport ANR stack traces file contains stack traces of all app's threads and of all threads of relevant or possibly relevant system processes, such as system_server. --- core/java/android/app/ActivityManager.java | 6 ++++++ core/java/android/app/ApplicationErrorReport.java | 7 +++++++ services/core/java/com/android/server/am/AppErrors.java | 1 + .../com/android/server/am/ProcessErrorStateRecord.java | 9 +++++++-- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 9cc7b8ff47c29..c79cf7f832815 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3650,6 +3650,10 @@ public static class ProcessErrorStateInfo implements Parcelable { */ public byte[] crashData = null; + /** @hide */ + @Nullable + public String tracesFilePath; + public ProcessErrorStateInfo() { } @@ -3668,6 +3672,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(shortMsg); dest.writeString(longMsg); dest.writeString(stackTrace); + dest.writeString(tracesFilePath); } public void readFromParcel(Parcel source) { @@ -3679,6 +3684,7 @@ public void readFromParcel(Parcel source) { shortMsg = source.readString(); longMsg = source.readString(); stackTrace = source.readString(); + tracesFilePath = source.readString(); } public static final @android.annotation.NonNull Creator CREATOR = diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index 9cea5e8ef4cff..896ce039f054e 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -543,6 +544,10 @@ public static class AnrInfo { */ public String info; + /** @hide */ + @Nullable + public String tracesFilePath; + /** * Create an uninitialized instance of AnrInfo. */ @@ -556,6 +561,7 @@ public AnrInfo(Parcel in) { activity = in.readString(); cause = in.readString(); info = in.readString(); + tracesFilePath = in.readString(); } /** @@ -565,6 +571,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(activity); dest.writeString(cause); dest.writeString(info); + dest.writeString(tracesFilePath); } /** diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 2fbf05eb00615..5bd2c8202f606 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -879,6 +879,7 @@ private ApplicationErrorReport createAppErrorReportLOSP(ProcessRecord r, report.anrInfo.activity = anrReport.tag; report.anrInfo.cause = anrReport.shortMsg; report.anrInfo.info = anrReport.longMsg; + report.anrInfo.tracesFilePath = anrReport.tracesFilePath; } return report; diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 17fcbf47206fd..8bd5e082a5db4 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -677,7 +677,7 @@ null, new Float(loadingProgress), incrementalMetrics, errorId, synchronized (mProcLock) { // Set the app's notResponding state, and look up the errorReportReceiver makeAppNotRespondingLSP(activityShortComponentName, - annotation != null ? "ANR " + annotation : "ANR", info.toString()); + annotation != null ? "ANR " + annotation : "ANR", info.toString(), tracesFile); mDialogController.setAnrController(anrController); } @@ -696,7 +696,7 @@ null, new Float(loadingProgress), incrementalMetrics, errorId, } @GuardedBy({"mService", "mProcLock"}) - private void makeAppNotRespondingLSP(String activity, String shortMsg, String longMsg) { + private void makeAppNotRespondingLSP(String activity, String shortMsg, String longMsg, @Nullable File tracesFile) { setNotResponding(true); // mAppErrors can be null if the AMS is constructed with injector only. This will only // happen in tests. @@ -704,6 +704,11 @@ private void makeAppNotRespondingLSP(String activity, String shortMsg, String lo mNotRespondingReport = mService.mAppErrors.generateProcessError(mApp, ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING, activity, shortMsg, longMsg, null); + // Filename of tracesFile contains ANR timestamp with millisecond precision, it's highly + // unlikely to be reused + if (tracesFile != null) { + mNotRespondingReport.tracesFilePath = tracesFile.getAbsolutePath(); + } } startAppProblemLSP(); mApp.getWindowProcessController().stopFreezingActivities(); From e01fdec90130cf149514cebcbe54c8093be47b34 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Tue, 8 Oct 2024 16:30:22 +0300 Subject: [PATCH 089/332] don't block reading ANR stack traces file via Unix file permissions Access to these files is controlled by their SELinux policy. They are labeled as anr_data_file. Enforcing additional read restrictions for ANR stack traces files through Unix permissions prevented LogViewer app from accessing them, since it doesn't run as the highly privileged UID 1000 (android.uid.system) which owns these files. --- .../core/java/com/android/server/am/StackTracesDumpHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java index 2021ba4f3b5da..a9a5e15489ff4 100644 --- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java +++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java @@ -456,7 +456,7 @@ private static synchronized File createAnrDumpFile(File tracesDir) throws IOExce final File anrFile = new File(tracesDir, ANR_FILE_PREFIX + formattedDate); if (anrFile.createNewFile()) { - FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw------- + FileUtils.setPermissions(anrFile.getAbsolutePath(), 0644, -1, -1); // -rw-r--r-- return anrFile; } else { throw new IOException("Unable to create ANR dump file: createNewFile failed"); From 3bf2c44899ebb9caff8f9a2d35e883915cd145a9 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Mon, 8 Aug 2022 19:03:37 +0300 Subject: [PATCH 090/332] add option to view application error details in LogViewer app Adds a "Show details" item to crash and ANR (app not responding) dialogs which opens the LogViewer app. --- .../android/app/ApplicationErrorReport.java | 33 ++++++++++++++++++- core/res/res/layout/app_anr_dialog.xml | 4 +-- core/res/res/layout/app_error_dialog.xml | 4 +-- core/res/res/values/strings.xml | 3 ++ .../java/com/android/server/am/AppErrors.java | 1 + 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index 896ce039f054e..39bf240eb9160 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -26,6 +26,8 @@ import android.os.Binder; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; +import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; import android.util.Printer; @@ -99,6 +101,9 @@ public class ApplicationErrorReport implements Parcelable { */ public String packageName; + /** @hide */ + public ApplicationInfo applicationInfo; + /** * Package name of the application which installed the application this * report pertains to. @@ -163,13 +168,19 @@ public static ComponentName getErrorReportReceiver(Context context, String packageName, int appFlags) { // check if error reporting is enabled in secure settings int enabled = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.SEND_ACTION_APP_ERROR, 0); + Settings.Global.SEND_ACTION_APP_ERROR, 1); if (enabled == 0) { return null; } PackageManager pm = context.getPackageManager(); + ComponentName logViewerApp = getErrorReportReceiver(pm, packageName, + android.ext.LogViewerApp.getPackageName()); + if (logViewerApp != null) { + return logViewerApp; + } + // look for receiver in the installer package String candidate = null; ComponentName result = null; @@ -234,6 +245,11 @@ static ComponentName getErrorReportReceiver(PackageManager pm, String errorPacka public void writeToParcel(Parcel dest, int flags) { dest.writeInt(type); dest.writeString(packageName); + ApplicationInfo appInfo = applicationInfo; + dest.writeBoolean(appInfo != null); + if (appInfo != null) { + appInfo.writeToParcel(dest, 0); + } dest.writeString(installerPackageName); dest.writeString(processName); dest.writeLong(time); @@ -261,6 +277,9 @@ public void writeToParcel(Parcel dest, int flags) { public void readFromParcel(Parcel in) { type = in.readInt(); packageName = in.readString(); + if (in.readBoolean()) { + applicationInfo = ApplicationInfo.CREATOR.createFromParcel(in); + } installerPackageName = in.readString(); processName = in.readString(); time = in.readLong(); @@ -346,6 +365,11 @@ public static class CrashInfo { */ public String crashTag; + /** @hide */ + public long processUptimeMs; + /** @hide */ + public long processStartupLatencyMs; + /** * Create an uninitialized instance of CrashInfo. */ @@ -399,6 +423,9 @@ public CrashInfo(Throwable tr) { } exceptionMessage = sanitizeString(exceptionMessage); + + processUptimeMs = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime(); + processStartupLatencyMs = Process.getStartElapsedRealtime() - Process.getStartRequestedElapsedRealtime(); } /** {@hide} */ @@ -440,6 +467,8 @@ public CrashInfo(Parcel in) { throwLineNumber = in.readInt(); stackTrace = in.readString(); crashTag = in.readString(); + processUptimeMs = in.readLong(); + processStartupLatencyMs = in.readLong(); } /** @@ -456,6 +485,8 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(throwLineNumber); dest.writeString(stackTrace); dest.writeString(crashTag); + dest.writeLong(processUptimeMs); + dest.writeLong(processStartupLatencyMs); int total = dest.dataPosition()-start; if (Binder.CHECK_PARCEL_SIZE && total > 20*1024) { Slog.d("Error", "ERR: exHandler=" + exceptionHandlerClassName); diff --git a/core/res/res/layout/app_anr_dialog.xml b/core/res/res/layout/app_anr_dialog.xml index 5ad0f4c0f6cc7..ad3a2d2991de8 100644 --- a/core/res/res/layout/app_anr_dialog.xml +++ b/core/res/res/layout/app_anr_dialog.xml @@ -41,8 +41,8 @@ android:id="@+id/aerr_report" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/aerr_report" - android:drawableStart="@drawable/ic_feedback" + android:text="@string/aerr_show_details" + android:drawableStart="@drawable/ic_info_outline_24" style="@style/aerr_list_item" /> diff --git a/core/res/res/layout/app_error_dialog.xml b/core/res/res/layout/app_error_dialog.xml index c3b149a1e2959..a47b820183777 100644 --- a/core/res/res/layout/app_error_dialog.xml +++ b/core/res/res/layout/app_error_dialog.xml @@ -52,8 +52,8 @@ android:id="@+id/aerr_report" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/aerr_report" - android:drawableStart="@drawable/ic_feedback" + android:text="@string/aerr_show_details" + android:drawableStart="@drawable/ic_info_outline_24" style="@style/aerr_list_item" />