diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index f0c2f3ba03cf7..6abd504fe6c0c 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -125,6 +125,8 @@ package android.content {
public class Intent implements Cloneable android.os.Parcelable {
field public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED";
+ field public static final String ACTION_USER_STARTED = "android.intent.action.USER_STARTED";
+ field public static final String ACTION_USER_STOPPED = "android.intent.action.USER_STOPPED";
field public static final String EXTRA_SETTING_NAME = "setting_name";
field public static final String EXTRA_SETTING_NEW_VALUE = "new_value";
field public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value";
@@ -146,6 +148,7 @@ package android.content.pm {
}
public abstract class PackageManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract android.content.pm.PackageInfo getPackageInfoAsUser(@NonNull String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public String getSdkSandboxPackageName();
method @RequiresPermission(android.Manifest.permission.MAKE_UID_VISIBLE) public void makeUidVisible(int, int);
field public static final String EXTRA_VERIFICATION_ROOT_HASH = "android.content.pm.extra.VERIFICATION_ROOT_HASH";
@@ -157,6 +160,7 @@ package android.content.pm {
package android.ext {
public class ConnectivityUtil {
+ method public static boolean isAppStrictLeakBlockingEnabled(@NonNull android.content.Context, int, @NonNull android.content.pm.ApplicationInfo, @NonNull android.content.pm.GosPackageState);
method public static boolean isRegularAppWithLockdownVpnEnabled(@NonNull android.content.Context, int);
method public static boolean isSystem(@NonNull android.content.Context, int);
}
@@ -629,6 +633,7 @@ package android.os {
public final class UserHandle implements android.os.Parcelable {
method public static int getUid(int, int);
method public static int getUserId(int);
+ method public static boolean isCore(int);
}
public class UserManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c0df87a641bb3..f19c1446b23ce 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5867,6 +5867,17 @@ package android.ext {
field public static final int UNKNOWN = 0; // 0x0
}
+ public class SettingsIntents {
+ method @NonNull public static android.content.Intent createAppIntent(@NonNull android.content.Context, @NonNull String, @NonNull String, boolean);
+ field public static final String APP_HARDENED_MALLOC = "android.settings.OPEN_APP_HARDENED_MALLOC_SETTINGS";
+ field public static final String APP_MANAGE_PLAY_INTEGRITY_API = "android.settings.OPEN_APP_MANAGE_PLAY_INTEGRITY_API_SETTINGS";
+ field public static final String APP_MEMORY_DYN_CODE_LOADING = "android.settings.OPEN_APP_MEMORY_DYN_CODE_LOADING_SETTINGS";
+ field public static final String APP_MEMTAG = "android.settings.OPEN_APP_MEMTAG_SETTINGS";
+ field public static final String APP_NATIVE_DEBUGGING = "android.settings.OPEN_APP_NATIVE_DEBUGGING_SETTINGS";
+ field public static final String APP_STORAGE_DYN_CODE_LOADING = "android.settings.OPEN_APP_STORAGE_DYN_CODE_LOADING_SETTINGS";
+ field public static final String APP_STRICT_LEAK_BLOCKING = "android.settings.OPEN_APP_STRICT_LEAK_BLOCKING";
+ }
+
}
package android.ext.cscopes {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5ca94259fe348..60f323ec1f824 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4237,6 +4237,7 @@ public static Intent createChooser(Intent target, CharSequence title, IntentSend
* the current state of the user.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String ACTION_USER_STARTED =
"android.intent.action.USER_STARTED";
@@ -4302,6 +4303,7 @@ public static Intent createChooser(Intent target, CharSequence title, IntentSend
* @hide
*/
@TestApi
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String ACTION_USER_STOPPED =
"android.intent.action.USER_STOPPED";
diff --git a/core/java/android/content/pm/GosPackageState.java b/core/java/android/content/pm/GosPackageState.java
index 58b0a00af6961..9c0e6bbf65453 100644
--- a/core/java/android/content/pm/GosPackageState.java
+++ b/core/java/android/content/pm/GosPackageState.java
@@ -17,6 +17,8 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Objects;
@@ -402,11 +404,18 @@ public Editor setNotifyUidAfterApply(boolean v) {
public boolean apply() {
try {
return ActivityThread.getPackageManager().setGosPackageState(packageName, userId,
- new GosPackageState(flagStorage1, packageFlagStorage, storageScopes, contactScopes),
- editorFlags);
+ toState(), editorFlags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+
+ /** @hide */
+ @VisibleForTesting
+ @NonNull
+ public GosPackageState toState() {
+ return new GosPackageState(flagStorage1, packageFlagStorage, storageScopes,
+ contactScopes);
+ }
}
}
diff --git a/core/java/android/content/pm/GosPackageStateFlag.java b/core/java/android/content/pm/GosPackageStateFlag.java
index 79c05d16711b3..e4d12cfed954b 100644
--- a/core/java/android/content/pm/GosPackageStateFlag.java
+++ b/core/java/android/content/pm/GosPackageStateFlag.java
@@ -6,7 +6,12 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/** @hide */
+/**
+ * GosPackageStateFlag defines the valid flags for GosPackageState. When adding a new flag, its
+ * permissions must also be set in GosPackageStatePermissions.
+ *
+ * @hide
+ */
@SystemApi
public interface GosPackageStateFlag {
/* SysApi */ int STORAGE_SCOPES_ENABLED = 0;
@@ -38,6 +43,9 @@ public interface GosPackageStateFlag {
/** @hide */ int BLOCK_PLAY_INTEGRITY_API = 28;
/** @hide */ int USE_EXEC_SPAWNING_NON_DEFAULT = 29;
/** @hide */ int USE_EXEC_SPAWNING = 30;
+ /** @hide */ int STRICT_LEAK_BLOCKING_NON_DEFAULT = 31;
+ /** @hide */ int STRICT_LEAK_BLOCKING = 32;
+ /** @hide */ int STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF = 33;
/** @hide */
@IntDef(value = {
@@ -68,6 +76,9 @@ public interface GosPackageStateFlag {
BLOCK_PLAY_INTEGRITY_API,
USE_EXEC_SPAWNING_NON_DEFAULT,
USE_EXEC_SPAWNING,
+ STRICT_LEAK_BLOCKING_NON_DEFAULT,
+ STRICT_LEAK_BLOCKING,
+ STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF,
})
@Retention(RetentionPolicy.SOURCE)
@interface Enum {}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4360a8a45d117..bcf29cd5e54c1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -6157,6 +6157,8 @@ public PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage,
@SuppressWarnings("HiddenAbstractMethod")
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
@UnsupportedAppUsage
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
public abstract PackageInfo getPackageInfoAsUser(@NonNull String packageName,
int flags, @UserIdInt int userId) throws NameNotFoundException;
diff --git a/core/java/android/ext/ConnectivityUtil.java b/core/java/android/ext/ConnectivityUtil.java
index 75777dcb00988..34b80264e9fbf 100644
--- a/core/java/android/ext/ConnectivityUtil.java
+++ b/core/java/android/ext/ConnectivityUtil.java
@@ -4,7 +4,9 @@
import android.annotation.SystemApi;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.GosPackageState;
import android.content.pm.PackageManager;
+import android.ext.settings.app.AswStrictLeakBlocking;
import android.os.UserHandle;
import android.provider.Settings;
@@ -43,4 +45,9 @@ public static boolean isRegularAppWithLockdownVpnEnabled(@NonNull Context contex
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0, UserHandle.getUserId(uid));
return lockdownVpnEnabled == 1 && !isSystem(context, uid);
}
+
+ public static boolean isAppStrictLeakBlockingEnabled(@NonNull Context context, int uid,
+ @NonNull ApplicationInfo ai, @NonNull GosPackageState ps) {
+ return AswStrictLeakBlocking.I.get(context, uid, ai, ps);
+ }
}
diff --git a/core/java/android/ext/SettingsIntents.java b/core/java/android/ext/SettingsIntents.java
index 956b1144b0e4d..01c34b1392f53 100644
--- a/core/java/android/ext/SettingsIntents.java
+++ b/core/java/android/ext/SettingsIntents.java
@@ -1,10 +1,13 @@
package android.ext;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
/** @hide */
+@SystemApi
public class SettingsIntents {
public static final String APP_NATIVE_DEBUGGING = "android.settings.OPEN_APP_NATIVE_DEBUGGING_SETTINGS";
@@ -13,12 +16,19 @@ public class SettingsIntents {
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 final String APP_MANAGE_PLAY_INTEGRITY_API = "android.settings.OPEN_APP_MANAGE_PLAY_INTEGRITY_API_SETTINGS";
+ public static final String APP_STRICT_LEAK_BLOCKING = "android.settings.OPEN_APP_STRICT_LEAK_BLOCKING";
- public static Intent getAppIntent(Context ctx, String action, String pkgName) {
+ // This constructor keeps the linter happy.
+ private SettingsIntents() {}
+
+ public static @NonNull Intent createAppIntent(@NonNull Context ctx, @NonNull String action,
+ @NonNull String pkgName, boolean newTask) {
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);
+ if (newTask) {
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
return i;
}
}
diff --git a/core/java/android/ext/settings/app/AswStrictLeakBlocking.java b/core/java/android/ext/settings/app/AswStrictLeakBlocking.java
new file mode 100644
index 0000000000000..bfa20e1f30dfb
--- /dev/null
+++ b/core/java/android/ext/settings/app/AswStrictLeakBlocking.java
@@ -0,0 +1,41 @@
+package android.ext.settings.app;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.GosPackageState;
+import android.content.pm.GosPackageStateFlag;
+
+/** @hide */
+public class AswStrictLeakBlocking extends AppSwitch {
+ public static final AswStrictLeakBlocking I = new AswStrictLeakBlocking();
+
+ private AswStrictLeakBlocking() {
+ gosPsFlag = GosPackageStateFlag.STRICT_LEAK_BLOCKING;
+ gosPsFlagNonDefault = GosPackageStateFlag.STRICT_LEAK_BLOCKING_NON_DEFAULT;
+ gosPsFlagSuppressNotif = GosPackageStateFlag.STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF;
+ }
+
+ @Override
+ public Boolean getImmutableValue(Context ctx, int userId, ApplicationInfo appInfo,
+ GosPackageState ps, StateInfo si) {
+ if (appInfo.isSystemApp()) {
+ si.immutabilityReason = IR_IS_SYSTEM_APP;
+ // This is overly permissive because not all system apps have permissions that allow
+ // them to bypass lockdown VPNs (netd PERMISSION_SYSTEM). With that said, it's plausible
+ // that a system app without PERMISSION_SYSTEM could have a valid reason to use
+ // functionality gated behind strict leak blocking. GrapheneOS ships proprietary core
+ // components and system apps that could fall under that category.
+ // TODO: Determine which core components and system apps actually need this to be false
+ // (this will be difficult to maintain).
+ return false;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected boolean getDefaultValueInner(Context ctx, int userId, ApplicationInfo appInfo,
+ GosPackageState ps, StateInfo si) {
+ return true;
+ }
+}
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 08f8315985e04..b29be440eb175 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -285,6 +285,7 @@ public static boolean isApp(int uid) {
* Whether a UID belongs to a system core component or not.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static boolean isCore(int uid) {
if (uid >= 0) {
final int appId = getAppId(uid);
diff --git a/core/res/res/values/string_ext.xml b/core/res/res/values/string_ext.xml
index 12fbc7f26e3b6..c6379ca01e8b7 100644
--- a/core/res/res/values/string_ext.xml
+++ b/core/res/res/values/string_ext.xml
@@ -29,6 +29,9 @@
%1$s tried to use native code debugging
+ %1$s tried to bypass strict leak blocking
+
+
System journal
diff --git a/services/api/current.txt b/services/api/current.txt
index 00cff22d3f179..d67d471250119 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -45,6 +45,7 @@ package com.android.server.am {
method public boolean canStartForegroundService(int, int, @NonNull String);
method @FlaggedApi("com.android.server.am.get_package_names_for_pid_api") @NonNull public String[] getPackageNamesForPid(int, int);
method public void killSdkSandboxClientAppProcess(@NonNull android.os.IBinder);
+ method public void showAppStrictLeakBlockingNotification(int, int);
method @FlaggedApi("com.android.server.am.fgs_delegate_system_api") public boolean startForegroundServiceDelegate(@NonNull com.android.server.am.ForegroundServiceDelegationParams, @Nullable android.content.ServiceConnection);
method @Nullable public android.content.ComponentName startSdkSandboxService(@NonNull android.content.Intent, int, @NonNull String, @NonNull String) throws android.os.RemoteException;
method @FlaggedApi("com.android.server.am.fgs_delegate_system_api") public void stopForegroundServiceDelegate(@NonNull com.android.server.am.ForegroundServiceDelegationParams);
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index c44746e04c5b5..e72edf882fd33 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -224,4 +224,6 @@ boolean startForegroundServiceDelegate(
@FlaggedApi(Flags.FLAG_FGS_DELEGATE_SYSTEM_API)
void stopForegroundServiceDelegate(
@NonNull ForegroundServiceDelegationParams params);
+
+ void showAppStrictLeakBlockingNotification(int uid, int pid);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b2da89210e1e9..7694482b1bc69 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -525,6 +525,7 @@
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.crashrecovery.CrashRecoveryHelper;
import com.android.server.criticalevents.CriticalEventLog;
+import com.android.server.ext.AppStrictLeakBlockingUtil;
import com.android.server.ext.DynCodeLoadingUtils;
import com.android.server.ext.PackageManagerHooks;
import com.android.server.firewall.IntentFirewall;
@@ -19410,6 +19411,11 @@ public void dispatchGosPackageStateCallbacks(int uid) {
public ProcessRecordSnapshot getProcessRecordByPid(int pid) {
return mProcessList.getProcessRecordByPid(pid);
}
+
+ @Override
+ public void showAppStrictLeakBlockingNotification(int uid, int pid) {
+ AppStrictLeakBlockingUtil.showNotification(mContext, uid, pid);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/ext/AppStrictLeakBlockingUtil.java b/services/core/java/com/android/server/ext/AppStrictLeakBlockingUtil.java
new file mode 100644
index 0000000000000..7051861165527
--- /dev/null
+++ b/services/core/java/com/android/server/ext/AppStrictLeakBlockingUtil.java
@@ -0,0 +1,48 @@
+package com.android.server.ext;
+
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.pm.GosPackageStateFlag;
+import android.content.pm.PackageManagerInternal;
+import android.ext.SettingsIntents;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.pm.Computer;
+
+public class AppStrictLeakBlockingUtil {
+ private static final String TAG = AppStrictLeakBlockingUtil.class.getSimpleName();
+
+ public static void showNotification(Context ctx, int uid, int pid) {
+ String firstPackageName = null;
+
+ var ami = LocalServices.getService(ActivityManagerInternal.class);
+ ActivityManagerInternal.ProcessRecordSnapshot prs = ami.getProcessRecordByPid(pid);
+ if (prs != null && prs.appInfo != null) {
+ firstPackageName = prs.appInfo.packageName;
+ } else {
+ var pmi = LocalServices.getService(PackageManagerInternal.class);
+ Computer snapshot = (Computer)pmi.snapshot();
+ String[] packages = snapshot.getPackagesForUid(uid);
+ if (packages != null && packages.length == 1) {
+ firstPackageName = packages[0];
+ }
+ }
+
+ if (firstPackageName == null) {
+ Slog.d(TAG, "firstPackageName is null for uid " + uid);
+ return;
+ }
+
+ var n = AppSwitchNotification.maybeCreate(ctx, firstPackageName, uid,
+ SettingsIntents.APP_STRICT_LEAK_BLOCKING);
+ if (n == null) {
+ return;
+ }
+
+ n.titleRes = R.string.notif_app_strict_leak_blocking_title;
+ n.gosPsFlagSuppressNotif = GosPackageStateFlag.STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF;
+ n.maybeShow();
+ }
+}
diff --git a/services/core/java/com/android/server/ext/AppSwitchNotification.java b/services/core/java/com/android/server/ext/AppSwitchNotification.java
index 66f93579779e3..3123d0f05dcf1 100644
--- a/services/core/java/com/android/server/ext/AppSwitchNotification.java
+++ b/services/core/java/com/android/server/ext/AppSwitchNotification.java
@@ -175,7 +175,7 @@ public void maybeShow() {
nb.setContentTitle(ctx.getString(titleRes, appLabel));
}
{
- var intent = SettingsIntents.getAppIntent(ctx, settingsIntentAction, pkgName);
+ var intent = SettingsIntents.createAppIntent(ctx, settingsIntentAction, pkgName, true);
var pi = PendingIntent.getActivityAsUser(ctx, 0, intent,
PendingIntent.FLAG_IMMUTABLE, null, userHandle);
nb.setContentIntent(pi);
diff --git a/services/core/java/com/android/server/pm/GosPackageStatePermissions.java b/services/core/java/com/android/server/pm/GosPackageStatePermissions.java
index 8dc9b77d2f85d..5eb70ac301e6b 100644
--- a/services/core/java/com/android/server/pm/GosPackageStatePermissions.java
+++ b/services/core/java/com/android/server/pm/GosPackageStatePermissions.java
@@ -22,6 +22,9 @@
import java.util.Objects;
import static android.content.pm.GosPackageStateFlag.ALLOW_ACCESS_TO_OBB_DIRECTORY;
+import static android.content.pm.GosPackageStateFlag.STRICT_LEAK_BLOCKING;
+import static android.content.pm.GosPackageStateFlag.STRICT_LEAK_BLOCKING_NON_DEFAULT;
+import static android.content.pm.GosPackageStateFlag.STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF;
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;
@@ -145,6 +148,9 @@ static void init(PackageManagerService pm) {
USE_EXEC_SPAWNING_NON_DEFAULT,
USE_EXEC_SPAWNING,
ENABLE_EXPLOIT_PROTECTION_COMPAT_MODE,
+ STRICT_LEAK_BLOCKING_NON_DEFAULT,
+ STRICT_LEAK_BLOCKING,
+ STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF,
};
builder()
.readWriteFlags(settingsReadWriteFlags)