Skip to content

Instantly share code, notes, and snippets.

@sn-00-x
Last active October 21, 2023 13:38
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sn-00-x/9bd0b0143139c7efdae5507ad845ed86 to your computer and use it in GitHub Desktop.
Save sn-00-x/9bd0b0143139c7efdae5507ad845ed86 to your computer and use it in GitHub Desktop.
AndroidAuto, Screen2Auto with Netflix + other media apps on GrapheneOS A13 without root
build:
- include https://github.com/sn-00-x/android-auto-stub into build
- apply frameworks-base.patch to frameworks/base
- apply proprietary.patch to vendor/google_devices/[DEVICE NAME]/proprietary
- clone Screen2Auto and use a custom name (S2A is blocked by AndroidAuto and changing that would require root)
- set the name of the cloned app in core/java/android/app/compat/sn00x/AndroidAutoHelper.java (PACKAGE_SCREEN2AUTO)
runtime:
- install screen2auto (without granting network permisions), start it and only grant "display over other applications" and "system settings recording" (modify system settings, needed to set orientation)
-> at that point s2a should already be startable in AA and be able to launch your apps
- optional: enable screen2auto in accessibility settings, start Screen2Auto, click "Other settings", select "Alternative touch", select "Profile 2", close the app, reboot
-> now apps should be controllable with the head unit's touch display.
explaination of the patch:
- adds a helper that provides functions to detect if the current app or calling uid is either Android Auto, Screen2Auto or a media app (Netflix, Amazon Prime, Disney+)
- disable GrapheneOS's forced fs-verity check (only for Android Auto and only if it's signature matches!) for priv-app updates, so that the AA stub is updateable and survives a reboot
- Bypass A13's intent related behaviour change ( https://developer.android.com/about/versions/13/behavior-changes-all#intents ) (only for ACTION_MAIN originating from Screen2Auto will be allowed!), so that S2A can launch apps targeting A13
- Remove FLAG_SECURE for media app's windows
- Remove liboemcrypto.so - Widevine DRM will fall back to using L3 and Netflix will only stream in SD, but can be mirrored to the head unit
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 741fec57a676..563c5bd88558 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -20,6 +20,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.gms.GmsCompat;
+import android.app.compat.sn00x.AndroidAutoHelper;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -1243,6 +1244,7 @@ public class Instrumentation {
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
GmsCompat.maybeEnable(context);
+ AndroidAutoHelper.applicationStart(context);
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
app.attach(context);
@@ -1262,6 +1264,7 @@ public class Instrumentation {
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
GmsCompat.maybeEnable(context);
+ AndroidAutoHelper.applicationStart(context);
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
diff --git a/core/java/android/app/compat/sn00x/AndroidAutoHelper.java b/core/java/android/app/compat/sn00x/AndroidAutoHelper.java
new file mode 100644
index 000000000000..d757792aa329
--- /dev/null
+++ b/core/java/android/app/compat/sn00x/AndroidAutoHelper.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package android.app.compat.sn00x;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.SigningDetails;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class provides helpers for Android Auto, screen2auto and media app compatibility.
+ *
+ * @hide
+ */
+public final class AndroidAutoHelper {
+ // Package names for GMS apps
+ private static final String PACKAGE_ANDROIDAUTO = "com.google.android.projection.gearhead";
+ private static final String SIGNATURE_ANDROIDAUTO = "FDB00C43DBDE8B51CB312AA81D3B5FA17713ADB94B28F598D77F8EB89DACEEDF";
+ private static final String PACKAGE_SCREEN2AUTO = "name.of.cloned.app";
+ private static final List<String> PACKAGES_MEDIAAPPS = Arrays.asList(
+ "com.netflix.mediaclient",
+ "com.amazon.avod.thirdpartyclient",
+ "com.disney.disneyplus"
+ );
+
+ /** Define additionally allowed permissions for Screen2Auto */
+ private static final ArrayList<String> PERMISSIONS_SCREEN2AUTO = new ArrayList<String>(
+ Arrays.asList(
+ "android.permission.CAPTURE_VIDEO_OUTPUT" // avoid cast confirmation dialog
+ )
+ );
+
+ private static boolean isAndroidAuto = false;
+ private static boolean isScreen2Auto = false;
+ private static boolean isMediaApp = false;
+ private static Context appContext;
+
+ // Static only
+ private AndroidAutoHelper() { }
+
+ /** @hide */
+ public static Context appContext() {
+ return appContext;
+ }
+
+ private static boolean uidBelongsToOneOfPackages(int uid, List<String> packages) {
+ if (uid == 0) {
+ return false;
+ }
+ try {
+ return packages.stream().anyMatch(
+ e -> Arrays.asList(appContext.getPackageManager().getPackagesForUid(uid)).contains(e)
+ );
+ } catch (Exception ignored) {}
+ return false;
+ }
+
+ private static boolean uidBelongsToPackage(int uid, String packageName) {
+ return uidBelongsToOneOfPackages(uid, Collections.singletonList(packageName));
+ }
+
+ /** @hide */
+ public static boolean isAndroidAuto() {
+ return isAndroidAuto;
+ }
+
+ /** @hide */
+ public static boolean isAndroidAuto(int uid) {
+ return uidBelongsToPackage(uid, PACKAGE_ANDROIDAUTO);
+ }
+
+ public static boolean isAndroidAuto(String packageName, SigningDetails signingDetails) {
+ if (PACKAGE_ANDROIDAUTO.equals(packageName)) {
+ if (signingDetails.hasAncestorOrSelfWithDigest(
+ new ArraySet<>(Collections.singletonList(SIGNATURE_ANDROIDAUTO))
+ )) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @hide */
+ public static boolean isScreen2Auto() { return isScreen2Auto; }
+
+ /** @hide */
+ public static boolean isScreen2Auto(int uid) {
+ return uidBelongsToPackage(uid, PACKAGE_SCREEN2AUTO);
+ }
+
+ public static boolean hasAdditionalPermission(String packageName, String permissionName) {
+ return PACKAGE_SCREEN2AUTO.equals(packageName) && PERMISSIONS_SCREEN2AUTO.contains(permissionName);
+ }
+
+ /** @hide */
+ public static boolean isMediaApp() {
+ return isMediaApp;
+ }
+
+ /** @hide */
+ public static boolean isMediaApp(int uid) {
+ return uidBelongsToOneOfPackages(uid, PACKAGES_MEDIAAPPS);
+ }
+
+ /** @hide */
+ public static void applicationStart(Context appCtx) {
+ appContext = appCtx;
+ ApplicationInfo appInfo = appCtx.getApplicationInfo();
+
+ if (appInfo == null || !appInfo.enabled) {
+ return;
+ }
+
+ String pkgName = appInfo.packageName;
+
+ isAndroidAuto = PACKAGE_ANDROIDAUTO.equals(pkgName);
+ isScreen2Auto = PACKAGE_SCREEN2AUTO.equals(pkgName);
+ isMediaApp = PACKAGES_MEDIAAPPS.contains(pkgName);
+ }
+}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0927bc7b9427..cb1993dfbc68 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -22,6 +22,7 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAY
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.compat.sn00x.AndroidAutoHelper;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.CompatibilityInfo.Translator;
@@ -688,7 +689,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
* @param isSecure True if the surface view is secure.
*/
public void setSecure(boolean isSecure) {
- if (isSecure) {
+ if (isSecure && !AndroidAutoHelper.isMediaApp()) {
mSurfaceFlags |= SurfaceControl.SECURE;
} else {
mSurfaceFlags &= ~SurfaceControl.SECURE;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 02027e4a3969..902d9d492cea 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -34,6 +34,7 @@ import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.app.WindowConfiguration;
+import android.app.compat.sn00x.AndroidAutoHelper;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -1275,6 +1276,9 @@ public abstract class Window {
*/
public void setFlags(int flags, int mask) {
final WindowManager.LayoutParams attrs = getAttributes();
+ if (AndroidAutoHelper.isMediaApp()) {
+ flags &= ~WindowManager.LayoutParams.FLAG_SECURE;
+ }
attrs.flags = (attrs.flags&~mask) | (flags&mask);
mForcedWindowFlags |= mask;
dispatchWindowAttributesChanged(attrs);
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index d37756551db3..0a9525c840d0 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -20,6 +20,7 @@ import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.compat.sn00x.AndroidAutoHelper;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
import android.content.Context;
@@ -318,6 +319,10 @@ public final class WindowManagerGlobal {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
+ if (AndroidAutoHelper.isMediaApp()) {
+ ((WindowManager.LayoutParams) params).flags &= ~WindowManager.LayoutParams.FLAG_SECURE;
+ }
+
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
@@ -422,6 +427,10 @@ public final class WindowManagerGlobal {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
+ if (AndroidAutoHelper.isMediaApp()) {
+ ((WindowManager.LayoutParams) params).flags &= ~WindowManager.LayoutParams.FLAG_SECURE;
+ }
+
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9cffd4be507b..3aba12e3ab0a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2169,7 +2169,7 @@
<!-- The name of the package that will hold the system gallery role. -->
<string name="config_systemGallery" translatable="false">com.android.gallery3d</string>
<!-- The names of the packages that will hold the automotive projection role. -->
- <string name="config_systemAutomotiveProjection" translatable="false"></string>
+ <string name="config_systemAutomotiveProjection" translatable="false">com.google.android.projection.gearhead</string>
<!-- The name of the package that will hold the system cluster service role. -->
<string name="config_systemAutomotiveCluster" translatable="false"></string>
<!-- The name of the package that will hold the system shell role. -->
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 58448bfefdaf..ced9d1369b6c 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -65,6 +65,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.compat.sn00x.AndroidAutoHelper;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -169,6 +170,8 @@ import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import static com.android.internal.gmscompat.GmsInfo.PACKAGE_PLAY_STORE;
+
/**
* This class contains the implementation of the Computer functions. It
* is entirely self-contained - it has no implicit access to
@@ -5103,6 +5106,10 @@ public class ComputerEngine implements Computer {
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
+ if (AndroidAutoHelper.isAndroidAuto(callingUid)) {
+ return new InstallSourceInfo(PACKAGE_PLAY_STORE, null, PACKAGE_PLAY_STORE, PACKAGE_PLAY_STORE);
+ }
+
String installerPackageName;
String initiatingPackageName;
String originatingPackageName;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 505e88cbb361..a94f563a3052 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -96,6 +96,7 @@ import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
import android.app.BroadcastOptions;
import android.app.backup.IBackupManager;
+import android.app.compat.sn00x.AndroidAutoHelper;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -1535,6 +1536,10 @@ final class InstallPackageHelper {
}
}
+ if (AndroidAutoHelper.isAndroidAuto(parsedPackage.getPackageName(), parsedPackage.getSigningDetails())) {
+ abortInstall = false;
+ }
+
if (abortInstall) {
throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR, message);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 7eb00ff5c137..383c94e0f3b8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.Intent.ACTION_MAIN;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
@@ -37,6 +38,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.compat.sn00x.AndroidAutoHelper;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.Context;
@@ -1097,6 +1099,11 @@ public class PackageManagerServiceUtils {
Intent intent, String resolvedType, int filterCallingUid) {
if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+ // Allow screen2auto to start apps targeting Android 13
+ if (ACTION_MAIN.equals(intent.getAction()) && AndroidAutoHelper.isScreen2Auto(filterCallingUid)) {
+ return;
+ }
+
final Printer logPrinter = DEBUG_INTENT_MATCHING
? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
: null;
diff --git a/services/core/java/com/android/server/pm/PackageVerityExt.java b/services/core/java/com/android/server/pm/PackageVerityExt.java
index d5865ace9077..519c2bd23681 100644
--- a/services/core/java/com/android/server/pm/PackageVerityExt.java
+++ b/services/core/java/com/android/server/pm/PackageVerityExt.java
@@ -22,6 +22,8 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import android.app.compat.sn00x.AndroidAutoHelper;
+
// Performs additional checks on system package updates
public class PackageVerityExt {
private static final String TAG = PackageVerityExt.class.getSimpleName();
@@ -102,14 +104,18 @@ public class PackageVerityExt {
}
}
- if (checkFsVerity) {
- checkFsVerity(systemPkgUpdate);
- }
-
final SigningDetails updatePkgSigningDetails = parseSigningDetails(systemPkgUpdate,
// verify APK against its signature
false);
+ if (AndroidAutoHelper.isAndroidAuto(systemPkgUpdate.getPackageName(), (new SigningDetails(updatePkgSigningDetails)))) {
+ checkFsVerity = false;
+ }
+
+ if (checkFsVerity) {
+ checkFsVerity(systemPkgUpdate);
+ }
+
final SigningDetails systemPkgSigningDetails = parseSigningDetails(systemPkg,
// skip signature verification, system image APKs are protected by verified boot
true);
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 e92d49327cc8..23a9b0bd4dab 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -67,6 +67,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.compat.sn00x.AndroidAutoHelper;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.Context;
@@ -137,7 +138,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.AndroidPackageApi;
import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.PackageUserStateUtils;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedPermissionGroup;
@@ -177,7 +177,6 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
private static final String SKIP_KILL_APP_REASON_NOTIFICATION_TEST = "skip permission revoke "
+ "app kill for notification test";
-
private static final long BACKUP_TIMEOUT_MILLIS = SECONDS.toMillis(60);
/** Cap the size of permission trees that 3rd party apps can define; in characters of text */
@@ -964,6 +963,10 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
return PackageManager.PERMISSION_DENIED;
}
+ if (AndroidAutoHelper.hasAdditionalPermission(pkg.getPackageName(), permissionName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
if (checkSinglePermissionInternalLocked(uidState, permissionName, isInstantApp)) {
return PackageManager.PERMISSION_GRANTED;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 04699619bfc8..3b74bb96bf12 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -193,6 +193,7 @@ import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
+import android.app.compat.sn00x.AndroidAutoHelper;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Matrix;
@@ -2024,7 +2025,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
boolean isSecureLocked() {
- if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ if (!AndroidAutoHelper.isMediaApp(mShowUserId) && (mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
return true;
}
return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
diff -wurN a/Android.bp b/Android.bp
--- a/Android.bp 2023-05-19 17:04:07.234201683 +0200
+++ b/Android.bp 2023-05-19 17:06:12.664331130 +0200
@@ -2368,6 +2368,7 @@
soc_specific: true,
}
+/*
cc_prebuilt_library_shared {
name: "liboemcrypto",
owner: "google_devices",
@@ -2378,6 +2379,7 @@
strip: { none: true },
soc_specific: true,
}
+*/
cc_prebuilt_library_shared {
name: "libproxy_trusty",
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment