├── gappstub ├── gappsstub.apk └── Android.mk ├── speechservicestub ├── speech-services-stub.apk └── Android.mk ├── README.md └── frameworks-base.patch /gappstub/gappsstub.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sn-00-x/android-auto-android13/HEAD/gappstub/gappsstub.apk -------------------------------------------------------------------------------- /speechservicestub/speech-services-stub.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sn-00-x/android-auto-android13/HEAD/speechservicestub/speech-services-stub.apk -------------------------------------------------------------------------------- /gappstub/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | LOCAL_MODULE_TAGS := optional 5 | LOCAL_MODULE := gappsstub 6 | LOCAL_SRC_FILES := gappsstub.apk 7 | LOCAL_MODULE_CLASS := APPS 8 | LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX) 9 | LOCAL_CERTIFICATE := PRESIGNED 10 | include $(BUILD_PREBUILT) 11 | 12 | include $(CLEAR_VARS) 13 | LOCAL_MODULE_TAGS := optional 14 | LOCAL_MODULE := gappsstub-unsigned 15 | LOCAL_SRC_FILES := gappsstub-unsigned.apk 16 | LOCAL_MODULE_CLASS := APPS 17 | LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX) 18 | LOCAL_CERTIFICATE := release 19 | include $(BUILD_PREBUILT) 20 | -------------------------------------------------------------------------------- /speechservicestub/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | LOCAL_MODULE_TAGS := optional 5 | LOCAL_MODULE := speechservicestub 6 | LOCAL_SRC_FILES := speech-services-stub.apk 7 | LOCAL_MODULE_CLASS := APPS 8 | LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX) 9 | LOCAL_CERTIFICATE := PRESIGNED 10 | include $(BUILD_PREBUILT) 11 | 12 | include $(CLEAR_VARS) 13 | LOCAL_MODULE_TAGS := optional 14 | LOCAL_MODULE := speechservicestub-unsigned 15 | LOCAL_SRC_FILES := speech-services-unsigned-stub.apk 16 | LOCAL_MODULE_CLASS := APPS 17 | LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX) 18 | LOCAL_CERTIFICATE := release 19 | include $(BUILD_PREBUILT) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Android Auto as user app with media apps support on GrapheneOS / Android 13 2 | 3 | This repository provides everything that is needed to add Android Auto support (with media apps support) to GrapheneOS (Android 13). However, the patch itself does not rely on GrapheneOS, so it should be possible to make this work on a different OS with slight modifications. **If you are looking for an Android 14 patch, see [here](https://github.com/sn-00-x/android-auto).** 4 | 5 | The provided [patch](https://raw.githubusercontent.com/sn-00-x/android-auto-android13/main/frameworks-base.patch) includes compatibility changes and makes it possible to e.g. watch Netflix on your car screen with the help of Screen2Auto while restricting it's permissions when not connected to a head unit. 6 | Furthermore the repo includes optional custom built Google Search App and Google Speech Services stubs. 7 | 8 | To make this run, you need to build your own rom. If you are instead looking for a simpler method to run Android Auto (that involves rooting your device), have a look at [aa4mg](https://github.com/sn-00-x/aa4mg) 9 | 10 | ## Build 11 | 12 | - Set up your build environment according to the official [GrapheneOS build instructions](https://grapheneos.org/build), sync sources (the patch is based on [this release](https://grapheneos.org/releases#2023080800)) and proprietary files. 13 | - clone this repo into vendor/android-auto: 14 | ``` 15 | cd vendor 16 | git clone https://github.com/sn-00-x/android-auto-android13.git 17 | ``` 18 | - Apply the patch in root directory: 19 | ``` 20 | cd .. 21 | patch -p1 < vendor/android-auto/frameworks-base.patch 22 | ``` 23 | - To use the Google Search App and Google Speech Services stubs, add the following line `device/google/$device/device-$device.mk`: 24 | ``` 25 | PRODUCT_PACKAGES += gappsstub speechservicestub 26 | ``` 27 | - If you want to use Screen2Auto, you need to set it's package name and the sha256 hash of your signing cert in `frameworks/base/core/java/android/app/compat/sn00x/AndroidAutoHelper.java` ( PACKAGE_SCREEN2AUTO and SIGNATURES_SCREEN2AUTO in line 54ff). See [Install Screen2Auto](#install-screen2auto) section below. 28 | - To be able to play content protected by DRM, such as Netflix, the device must be missing liboemcrypto.so. Therefor search and comment out the following section in `vendor/google_devices/$device/proprietary/Android.bp` 29 | ``` 30 | /* 31 | cc_prebuilt_library_shared { 32 | name: "liboemcrypto", 33 | owner: "google_devices", 34 | target: { android_arm64: { srcs: [ "vendor/lib64/liboemcrypto.so" ] } }, 35 | compile_multilib: "64", 36 | check_elf_files: false, 37 | prefer: true, 38 | strip: { none: true }, 39 | soc_specific: true, 40 | } 41 | */ 42 | ``` 43 | Note that without liboemcrypto.so Widevine DRM will fall back to using L3 and Netflix will only stream in SD, but at least can be mirrored to the head unit. 44 | - Continue the build process described in [GrapheneOS build instructions](https://grapheneos.org/build) 45 | 46 | ## Get Android Auto and Google Maps 47 | 48 | - Be sure to get Android Auto and Maps with correct signatures. When downloading directly from PlayStore (without having preinstalled stubs on your system), the apps may be provided with other signatures. However, those may checked and rejected when connecting to a car. 49 | Android Auto must be signed with eeb557fc154afc0d8eec621bdc7ea950 / 9ca91f9e704d630ef67a23f52bf1577a92b9ca5d. Get it e.g. [here](https://www.apkmirror.com/apk/google-inc/android-auto/android-auto-10-2-6332-release/android-auto-10-2-633224-release-android-apk-download/) 50 | Google Maps must be signed with 38918a453d07199354f8b19af05ec6562ced5788 / f0fd6c5b410f25cb25c3b53346c8972fae30f8ee7411df910480ad6b2d60db83. Get it e.g. [here](https://www.apkmirror.com/apk/google-inc/maps/maps-11-93-0307-release/google-maps-11-93-0307-android-apk-download/) 51 | - Go to phone settings and grant "nearby devices" permission to Android Auto 52 | - Connect to car, follow instructions 53 | - In case your device says there is no app to handle the connection, reboot and try again. 54 | - When an error regarding restricted settings is shown, go to phone settings -> Apps -> Android Auto -> click the three dots in the upper right corner -> Allow restricted settings 55 | - Continue setting up android auto 56 | - In case you run in any problems, replug device 57 | 58 | ## Install Screen2Auto 59 | 60 | Screen2Auto 3.7 can be obtained from https://inceptive.ru/projects/s2a/download 61 | 62 | However Google has blacklisted Screen2Auto (after all it circumvents security restrictions of Android Auto). 63 | So you need to find a way to rename the package and sign it with your own cert. I will neither provide a renamed package nor provide any information on how to do this. I don't want Google to blacklist the version I use or change the way they blacklist apps. 64 | 65 | Assuming you have a renamed version of Screen2Auto, install it and only grant "System settings recording" when asked for permissions. Screen2Auto needs this permission to modify system settings to be able to e.g. rotate the screen. Leave the other permissions untouched. The patch in this repo handles granting "display over other apps" permission to Screen2Auto only while connected to a head unit. 66 | 67 | Make the following changes in Screen2Auto: 68 | - Touch button settings -> Set Double tab to "Launcher" 69 | - Other settings -> Enable "Alternative touch" (but click cancel when asked for enabling Accessibility Service) 70 | 71 | If touch does not work for you correctly, try to select another profile under Other settings -> Alternative touch 72 | 73 | ## Sources 74 | 75 | sources for custom built stubs with build instructions for each of the stubs: 76 | 77 | https://github.com/SolidHal/SpeechServices-Package-Spoof 78 | 79 | https://github.com/SolidHal/Gapp-Package-Spoof 80 | 81 | ## Thanks 82 | big thanks to everyone involved in the thread here https://github.com/microg/GmsCore/issues/897 83 | 84 | https://github.com/VarunS2002/Xposed-Disable-FLAG_SECURE 85 | 86 | https://github.com/Magisk-Modules-Repo/liboemcryptodisabler 87 | -------------------------------------------------------------------------------- /frameworks-base.patch: -------------------------------------------------------------------------------- 1 | diff -wurN a/frameworks/base/core/java/android/app/compat/sn00x/AndroidAutoHelper.java b/frameworks/base/core/java/android/app/compat/sn00x/AndroidAutoHelper.java 2 | --- a/frameworks/base/core/java/android/app/compat/sn00x/AndroidAutoHelper.java 1970-01-01 01:00:00.000000000 +0100 3 | +++ b/frameworks/base/core/java/android/app/compat/sn00x/AndroidAutoHelper.java 2023-08-30 14:39:42.402352779 +0200 4 | @@ -0,0 +1,387 @@ 5 | +/* 6 | + * Copyright (C) 2021 The Android Open Source Project 7 | + * 8 | + * Licensed under the Apache License, Version 2.0 (the "License"); 9 | + * you may not use this file except in compliance with the License. 10 | + * You may obtain a copy of the License at 11 | + * 12 | + * http://www.apache.org/licenses/LICENSE-2.0 13 | + * 14 | + * Unless required by applicable law or agreed to in writing, software 15 | + * distributed under the License is distributed on an "AS IS" BASIS, 16 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | + * See the License for the specific language governing permissions and 18 | + * limitations under the License. 19 | + */ 20 | + 21 | +package android.app.compat.sn00x; 22 | + 23 | +import android.app.ActivityThread; 24 | +import android.content.Context; 25 | +import android.content.pm.ApplicationInfo; 26 | +import android.content.pm.IPackageManager; 27 | +import android.content.pm.PackageInfo; 28 | +import android.content.pm.PackageManager; 29 | +import android.content.pm.Signature; 30 | +import android.content.pm.SigningDetails; 31 | +import android.content.pm.SigningInfo; 32 | +import android.os.Build; 33 | +import android.os.RemoteException; 34 | +import android.provider.Settings; 35 | +import android.util.ArraySet; 36 | +import android.util.Log; 37 | +import android.util.PackageUtils; 38 | + 39 | +import java.security.NoSuchAlgorithmException; 40 | +import java.util.ArrayList; 41 | +import java.util.Arrays; 42 | +import java.util.Collections; 43 | +import java.util.List; 44 | +import java.util.Set; 45 | + 46 | +/** 47 | + * This class provides helpers for Android Auto, screen2auto and media app compatibility. 48 | + * 49 | + * @hide 50 | + */ 51 | +public final class AndroidAutoHelper { 52 | + private static final String PACKAGE_ANDROIDAUTO = "com.google.android.projection.gearhead"; 53 | + private static final Set SIGNATURES_ANDROIDAUTO = new ArraySet<>( 54 | + Arrays.asList( 55 | + "FDB00C43DBDE8B51CB312AA81D3B5FA17713ADB94B28F598D77F8EB89DACEEDF" // CN=gearhead, OU=Android, O=Google Inc., L=Mountain View, ST=California, C=US 56 | + ) 57 | + ); 58 | + private static final String PACKAGE_SCREEN2AUTO = "new.package.name.s2a"; // replace with new package name of Screen2Auto 59 | + private static final Set SIGNATURES_SCREEN2AUTO = new ArraySet<>( 60 | + Arrays.asList( 61 | + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" // replace with sha256 hash of signing certificate 62 | + ) 63 | + ); 64 | + private static final List PACKAGES_MEDIAAPPS = Arrays.asList( 65 | + "com.netflix.mediaclient", 66 | + "com.amazon.avod.thirdpartyclient", 67 | + "com.disney.disneyplus", 68 | + "com.spotify.music" 69 | + ); 70 | + 71 | + private static boolean androidAutoActive = false; 72 | + private static boolean screenCaptureActive = false; 73 | + 74 | + // Define additionally allowed permissions for AndroidAuto 75 | + private static final ArrayList PERMISSIONS_ANDROIDAUTO = new ArrayList( 76 | + Arrays.asList( 77 | + "android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY", 78 | + "android.permission.CREATE_VIRTUAL_DEVICE", 79 | + "android.permission.INTERNAL_SYSTEM_WINDOW", 80 | + "android.permission.MANAGE_COMPANION_DEVICES", 81 | + "android.permission.MANAGE_USB", 82 | + "android.permission.MODIFY_AUDIO_ROUTING", 83 | + "android.permission.READ_PRIVILEGED_PHONE_STATE", 84 | + "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION", 85 | + "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION", 86 | + "android.permission.BLUETOOTH_PRIVILEGED", 87 | + "android.permission.LOCAL_MAC_ADDRESS", 88 | + "android.permission.REQUEST_COMPANION_SELF_MANAGED", 89 | + "android.permission.READ_PHONE_STATE" 90 | + ) 91 | + ); 92 | + 93 | + /* Define additionally allowed permissions for Screen2Auto - these will only be allowed when car is connected(!) */ 94 | + private static final ArrayList PERMISSIONS_SCREEN2AUTO = new ArrayList( 95 | + Arrays.asList( 96 | + "android.permission.CAPTURE_VIDEO_OUTPUT", // avoid cast confirmation dialog 97 | + "android.permission.SYSTEM_APPLICATION_OVERLAY", // display over other apps 98 | + "android.permission.START_ACTIVITIES_FROM_BACKGROUND" 99 | + ) 100 | + ); 101 | + // grant additional permissions while screen2auto is capturing the screen 102 | + private static final ArrayList PERMISSIONS_SCREEN2AUTO_DURING_SCREENCAPTURE = new ArrayList( 103 | + Arrays.asList( 104 | + "android.permission.BIND_ACCESSIBILITY_SERVICE" 105 | + ) 106 | + ); 107 | + 108 | + // Spoof permission checks for Screen2Auto 109 | + private static final ArrayList SPOOF_PERMISSIONS_SCREEN2AUTO = new ArrayList( 110 | + Arrays.asList( 111 | + "android.permission.SYSTEM_APPLICATION_OVERLAY" // display over other apps 112 | + ) 113 | + ); 114 | + 115 | + private static boolean isAndroidAuto = false; 116 | + private static boolean isScreen2Auto = false; 117 | + private static boolean isMediaApp = false; 118 | + private static Context context; 119 | + 120 | + // Static only 121 | + private AndroidAutoHelper() { } 122 | + 123 | + /** @hide */ 124 | + public static Context appContext() { 125 | + return context; 126 | + } 127 | + 128 | + private static boolean uidBelongsToOneOfPackages(int uid, List packages) { 129 | + if (uid == 0) { 130 | + return false; 131 | + } 132 | + try { 133 | + return packages.stream().anyMatch( 134 | + e -> Arrays.asList(context.getPackageManager().getPackagesForUid(uid)).contains(e) 135 | + ); 136 | + } catch (Exception ignored) {} 137 | + return false; 138 | + } 139 | + 140 | + private static boolean uidBelongsToPackage(int uid, String packageName) { 141 | + return uidBelongsToOneOfPackages(uid, Collections.singletonList(packageName)); 142 | + } 143 | + 144 | + /** @hide */ 145 | + public static boolean isAndroidAutoContext() { 146 | + return isAndroidAuto; 147 | + } 148 | + 149 | + private static boolean validateCertDigests(Signature[] signatures, Set validSignatureDigests) throws NoSuchAlgorithmException { 150 | + for (Signature signature : signatures) { 151 | + String signatureDigest = PackageUtils.computeSha256Digest(signature.toByteArray()); 152 | + 153 | + if (validSignatureDigests.contains(signatureDigest)) { 154 | + return true; 155 | + } 156 | + } 157 | + return false; 158 | + } 159 | + 160 | + private static boolean packageMatchesSignatureDigests(String packageName, Set validSignatureDigests) { 161 | + IPackageManager pm = ActivityThread.getPackageManager(); 162 | + try { 163 | + PackageInfo pkg = pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, 0); 164 | + SigningInfo si = pkg.signingInfo; 165 | + Signature[] signatures = si.getApkContentsSigners(); 166 | + 167 | + boolean validCert = validateCertDigests(signatures, validSignatureDigests); 168 | + 169 | + if (!validCert && si.hasPastSigningCertificates()) { 170 | + Signature[] pastSignatures = si.getSigningCertificateHistory(); 171 | + validCert = validateCertDigests(pastSignatures, validSignatureDigests); 172 | + } 173 | + 174 | + return validCert; 175 | + } catch (RemoteException e) { 176 | + e.rethrowFromSystemServer(); 177 | + } catch (NoSuchAlgorithmException ignore) {} // won't happen 178 | + 179 | + return false; 180 | + } 181 | + 182 | + /** 183 | + * Checks if packageName is AndroidAuto and package has matching signature 184 | + * 185 | + * @hide 186 | + */ 187 | + public static boolean isAndroidAuto(String packageName) { 188 | + if (PACKAGE_ANDROIDAUTO.equals(packageName) 189 | + && packageMatchesSignatureDigests(packageName, SIGNATURES_ANDROIDAUTO)) { 190 | + return true; 191 | + } 192 | + return false; 193 | + } 194 | + 195 | + /** 196 | + * Checks if uid belongs to AndroidAuto, and package has matching signature 197 | + * 198 | + * @hide 199 | + */ 200 | + public static boolean isAndroidAuto(int uid) { 201 | + return uidBelongsToPackage(uid, PACKAGE_ANDROIDAUTO) && isAndroidAuto(PACKAGE_ANDROIDAUTO); 202 | + } 203 | + 204 | + /** 205 | + * Checks if packageName and uid belong to AndroidAuto, and package has matching signature 206 | + * 207 | + * @hide 208 | + */ 209 | + public static boolean isAndroidAuto(String packageName, int uid) { 210 | + if (PACKAGE_ANDROIDAUTO.equals(packageName) && isAndroidAuto(uid)) { 211 | + return true; 212 | + } 213 | + return false; 214 | + } 215 | + 216 | + /** 217 | + * Checks if packageName is AndroidAuto and signingDetails match 218 | + * 219 | + * @hide 220 | + */ 221 | + public static boolean isAndroidAuto(String packageName, SigningDetails signingDetails) { 222 | + if (PACKAGE_ANDROIDAUTO.equals(packageName) 223 | + && signingDetails.hasAncestorOrSelfWithDigest(SIGNATURES_ANDROIDAUTO)) { 224 | + return true; 225 | + } 226 | + return false; 227 | + } 228 | + 229 | + /** 230 | + * Checks if packageName is Screen2Auto and package has matching signature 231 | + * 232 | + * @hide 233 | + */ 234 | + public static boolean isScreen2Auto(String packageName) { 235 | + if (PACKAGE_SCREEN2AUTO.equals(packageName) 236 | + && packageMatchesSignatureDigests(packageName, SIGNATURES_SCREEN2AUTO)) { 237 | + return true; 238 | + } 239 | + return false; 240 | + } 241 | + 242 | + /** @hide */ 243 | + public static boolean isScreen2AutoContext() { return isScreen2Auto; } 244 | + 245 | + /** 246 | + * Checks if uid belongs to Screen2Auto, and package has matching signature 247 | + * 248 | + * @hide 249 | + */ 250 | + public static boolean isScreen2Auto(int uid) { 251 | + return uidBelongsToPackage(uid, PACKAGE_SCREEN2AUTO) && isScreen2Auto(PACKAGE_SCREEN2AUTO); 252 | + } 253 | + 254 | + /** 255 | + * Checks if packageName is Screen2Auto and signingDetails match 256 | + * 257 | + * @hide 258 | + */ 259 | + public static boolean isScreen2Auto(String packageName, SigningDetails signingDetails) { 260 | + if (PACKAGE_SCREEN2AUTO.equals(packageName) 261 | + && signingDetails.hasAncestorOrSelfWithDigest(SIGNATURES_SCREEN2AUTO)) { 262 | + return true; 263 | + } 264 | + return false; 265 | + } 266 | + 267 | + /** 268 | + * Checks if additional permission should be granted 269 | + * 270 | + * @hide 271 | + */ 272 | + public static boolean hasAdditionalPermission(String packageName, String permissionName, SigningDetails signingDetails) { 273 | + 274 | + if (PACKAGE_ANDROIDAUTO.equals(packageName) && isAndroidAuto(packageName, signingDetails) 275 | + && PERMISSIONS_ANDROIDAUTO.contains(permissionName)) { 276 | + return true; 277 | + } 278 | + 279 | + if (PACKAGE_SCREEN2AUTO.equals(packageName) && androidAutoActive && isScreen2Auto(packageName, signingDetails)) { 280 | + if (PERMISSIONS_SCREEN2AUTO.contains(permissionName)) { 281 | + return true; 282 | + } 283 | + if (screenCaptureActive && PERMISSIONS_SCREEN2AUTO_DURING_SCREENCAPTURE.contains(permissionName)) { 284 | + return true; 285 | + } 286 | + } 287 | + 288 | + return false; 289 | + } 290 | + 291 | + /** @hide */ 292 | + public static boolean isMediaAppContext() { 293 | + return isMediaApp; 294 | + } 295 | + 296 | + /** @hide */ 297 | + public static boolean isMediaApp(int uid) { 298 | + return uidBelongsToOneOfPackages(uid, PACKAGES_MEDIAAPPS); 299 | + } 300 | + 301 | + /** @hide */ 302 | + public static void applicationStart(Context context) { 303 | + AndroidAutoHelper.context = context; 304 | + ApplicationInfo appInfo = context.getApplicationInfo(); 305 | + 306 | + if (appInfo == null || !appInfo.enabled) { 307 | + return; 308 | + } 309 | + 310 | + String pkgName = appInfo.packageName; 311 | + 312 | + isAndroidAuto = isAndroidAuto(pkgName); // also checks signature 313 | + isScreen2Auto = isScreen2Auto(pkgName); // also checks signature 314 | + isMediaApp = PACKAGES_MEDIAAPPS.contains(pkgName); 315 | + } 316 | + 317 | + private static void handleDisplayChanged(String name, int ownerUid, boolean added) 318 | + { 319 | + if (name.equals("Dashboard") && isAndroidAuto(ownerUid)) { 320 | + androidAutoActive = added; 321 | + } 322 | + if (name.equals("ScreenCapture") && isScreen2Auto(ownerUid)) { 323 | + setScreenCaptureActive(added); 324 | + } 325 | + } 326 | + 327 | + /** @hide */ 328 | + public static void handleDisplayAdded(String name, int ownerUid) { 329 | + handleDisplayChanged(name, ownerUid, true); 330 | + } 331 | + 332 | + /** @hide */ 333 | + public static void handleDisplayRemoved(String name, int ownerUid) { 334 | + handleDisplayChanged(name, ownerUid, false); 335 | + } 336 | + 337 | + /** 338 | + * adds/removes Screen2Auto's accessibility service when "ScreenCapture" display device is added/removed. 339 | + * */ 340 | + private static void setScreenCaptureActive(boolean screenCaptureActiveStatus) { 341 | + screenCaptureActive = screenCaptureActiveStatus; 342 | + 343 | + String accessibilityService = PACKAGE_SCREEN2AUTO + "/ru.inceptive.screentwoauto.services.SplitScreenService"; 344 | + 345 | + int accessibilityEnabled = 0; 346 | + 347 | + try { 348 | + accessibilityEnabled = Settings.Secure.getInt( 349 | + context.getContentResolver(), 350 | + android.provider.Settings.Secure.ACCESSIBILITY_ENABLED); 351 | + 352 | + if (accessibilityEnabled == 0) { 353 | + Settings.Secure.putInt(context.getContentResolver(), 354 | + android.provider.Settings.Secure.ACCESSIBILITY_ENABLED, 1); 355 | + } 356 | + 357 | + String services = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 358 | + if (screenCaptureActiveStatus && ((services == null) || !services.contains(accessibilityService))) { 359 | + if (services == null) { 360 | + services = ""; 361 | + } 362 | + if (services.length() > 0) { 363 | + services += ":"; 364 | + } 365 | + services += accessibilityService; 366 | + } else if (!screenCaptureActiveStatus && (services != null) && services.contains(accessibilityService)) { 367 | + services = services.replace(accessibilityService, ""); 368 | + } 369 | + 370 | + services = services.replace("::", ":"); 371 | + if (services.length() <= 1) { 372 | + services = null; 373 | + } 374 | + 375 | + Settings.Secure.putString(context.getContentResolver(), 376 | + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, services); 377 | + } catch (Exception e) { 378 | + Log.e("AAH", "S2A accessibility service exception", e); 379 | + } 380 | + } 381 | + 382 | + /** @hide */ 383 | + public static boolean isScreenCaptureActive() { 384 | + return screenCaptureActive; 385 | + } 386 | + 387 | + /** @hide */ 388 | + public static boolean shouldSpoofSelfPermissionCheck(String perm) { 389 | + return isScreen2Auto && SPOOF_PERMISSIONS_SCREEN2AUTO.contains(perm); 390 | + } 391 | +} 392 | diff -wurN a/frameworks/base/core/java/android/app/ContextImpl.java b/frameworks/base/core/java/android/app/ContextImpl.java 393 | --- a/frameworks/base/core/java/android/app/ContextImpl.java 2023-08-30 14:57:19.378620736 +0200 394 | +++ b/frameworks/base/core/java/android/app/ContextImpl.java 2023-08-30 10:14:29.559387205 +0200 395 | @@ -27,6 +27,7 @@ 396 | import android.annotation.Nullable; 397 | import android.annotation.UiContext; 398 | import android.app.compat.gms.GmsCompat; 399 | +import android.app.compat.sn00x.AndroidAutoHelper; 400 | import android.compat.annotation.UnsupportedAppUsage; 401 | import android.content.AttributionSource; 402 | import android.content.AutofillOptions; 403 | @@ -2264,6 +2265,10 @@ 404 | } 405 | } 406 | 407 | + if (AndroidAutoHelper.shouldSpoofSelfPermissionCheck(permission)) { 408 | + return PERMISSION_GRANTED; 409 | + } 410 | + 411 | return checkPermission(permission, Process.myPid(), Process.myUid()); 412 | } 413 | 414 | diff -wurN a/frameworks/base/core/java/android/app/Instrumentation.java b/frameworks/base/core/java/android/app/Instrumentation.java 415 | --- a/frameworks/base/core/java/android/app/Instrumentation.java 2023-08-30 14:57:19.378620736 +0200 416 | +++ b/frameworks/base/core/java/android/app/Instrumentation.java 2023-08-28 15:25:13.929933006 +0200 417 | @@ -20,6 +20,7 @@ 418 | import android.annotation.NonNull; 419 | import android.annotation.Nullable; 420 | import android.app.compat.gms.GmsCompat; 421 | +import android.app.compat.sn00x.AndroidAutoHelper; 422 | import android.compat.annotation.UnsupportedAppUsage; 423 | import android.content.ActivityNotFoundException; 424 | import android.content.ComponentName; 425 | @@ -58,6 +59,7 @@ 426 | 427 | import com.android.internal.app.StorageScopesAppHooks; 428 | import com.android.internal.content.ReferrerIntent; 429 | + 430 | import com.android.internal.gmscompat.GmsHooks; 431 | 432 | import java.io.File; 433 | @@ -1243,6 +1245,7 @@ 434 | throws InstantiationException, IllegalAccessException, 435 | ClassNotFoundException { 436 | GmsCompat.maybeEnable(context); 437 | + AndroidAutoHelper.applicationStart(context); 438 | Application app = getFactory(context.getPackageName()) 439 | .instantiateApplication(cl, className); 440 | app.attach(context); 441 | @@ -1262,6 +1265,7 @@ 442 | throws InstantiationException, IllegalAccessException, 443 | ClassNotFoundException { 444 | GmsCompat.maybeEnable(context); 445 | + AndroidAutoHelper.applicationStart(context); 446 | Application app = (Application)clazz.newInstance(); 447 | app.attach(context); 448 | return app; 449 | diff -wurN a/frameworks/base/core/java/android/view/SurfaceView.java b/frameworks/base/core/java/android/view/SurfaceView.java 450 | --- a/frameworks/base/core/java/android/view/SurfaceView.java 2023-08-30 14:57:19.378620736 +0200 451 | +++ b/frameworks/base/core/java/android/view/SurfaceView.java 2023-08-28 15:25:13.929933006 +0200 452 | @@ -22,6 +22,7 @@ 453 | 454 | import android.annotation.NonNull; 455 | import android.annotation.Nullable; 456 | +import android.app.compat.sn00x.AndroidAutoHelper; 457 | import android.compat.annotation.UnsupportedAppUsage; 458 | import android.content.Context; 459 | import android.content.res.CompatibilityInfo.Translator; 460 | @@ -688,7 +689,7 @@ 461 | * @param isSecure True if the surface view is secure. 462 | */ 463 | public void setSecure(boolean isSecure) { 464 | - if (isSecure) { 465 | + if (isSecure && !AndroidAutoHelper.isMediaAppContext()) { 466 | mSurfaceFlags |= SurfaceControl.SECURE; 467 | } else { 468 | mSurfaceFlags &= ~SurfaceControl.SECURE; 469 | diff -wurN a/frameworks/base/core/java/android/view/Window.java b/frameworks/base/core/java/android/view/Window.java 470 | --- a/frameworks/base/core/java/android/view/Window.java 2023-08-30 14:57:19.378620736 +0200 471 | +++ b/frameworks/base/core/java/android/view/Window.java 2023-08-28 15:25:13.929933006 +0200 472 | @@ -34,6 +34,7 @@ 473 | import android.annotation.TestApi; 474 | import android.annotation.UiContext; 475 | import android.app.WindowConfiguration; 476 | +import android.app.compat.sn00x.AndroidAutoHelper; 477 | import android.compat.annotation.UnsupportedAppUsage; 478 | import android.content.Context; 479 | import android.content.pm.ActivityInfo; 480 | @@ -1280,6 +1281,9 @@ 481 | */ 482 | public void setFlags(int flags, int mask) { 483 | final WindowManager.LayoutParams attrs = getAttributes(); 484 | + if (AndroidAutoHelper.isMediaAppContext()) { 485 | + flags &= ~WindowManager.LayoutParams.FLAG_SECURE; 486 | + } 487 | attrs.flags = (attrs.flags&~mask) | (flags&mask); 488 | mForcedWindowFlags |= mask; 489 | dispatchWindowAttributesChanged(attrs); 490 | diff -wurN a/frameworks/base/core/java/android/view/WindowManagerGlobal.java b/frameworks/base/core/java/android/view/WindowManagerGlobal.java 491 | --- a/frameworks/base/core/java/android/view/WindowManagerGlobal.java 2023-08-30 14:57:19.378620736 +0200 492 | +++ b/frameworks/base/core/java/android/view/WindowManagerGlobal.java 2023-08-28 15:25:13.929933006 +0200 493 | @@ -20,6 +20,7 @@ 494 | import android.annotation.NonNull; 495 | import android.annotation.Nullable; 496 | import android.app.ActivityManager; 497 | +import android.app.compat.sn00x.AndroidAutoHelper; 498 | import android.compat.annotation.UnsupportedAppUsage; 499 | import android.content.ComponentCallbacks2; 500 | import android.content.Context; 501 | @@ -318,6 +319,10 @@ 502 | throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); 503 | } 504 | 505 | + if (AndroidAutoHelper.isMediaAppContext()) { 506 | + ((WindowManager.LayoutParams) params).flags &= ~WindowManager.LayoutParams.FLAG_SECURE; 507 | + } 508 | + 509 | final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; 510 | if (parentWindow != null) { 511 | parentWindow.adjustLayoutParamsForSubWindow(wparams); 512 | @@ -422,6 +427,10 @@ 513 | throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); 514 | } 515 | 516 | + if (AndroidAutoHelper.isMediaAppContext()) { 517 | + ((WindowManager.LayoutParams) params).flags &= ~WindowManager.LayoutParams.FLAG_SECURE; 518 | + } 519 | + 520 | final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; 521 | 522 | view.setLayoutParams(wparams); 523 | diff -wurN a/frameworks/base/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/frameworks/base/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java 524 | --- a/frameworks/base/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java 2023-08-30 14:57:19.378620736 +0200 525 | +++ b/frameworks/base/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java 2023-08-28 15:25:13.929933006 +0200 526 | @@ -19,6 +19,7 @@ 527 | import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; 528 | import static android.app.PendingIntent.FLAG_IMMUTABLE; 529 | import static android.app.PendingIntent.FLAG_ONE_SHOT; 530 | +import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; 531 | import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME; 532 | import static android.content.ComponentName.createRelative; 533 | 534 | @@ -34,6 +35,7 @@ 535 | import android.annotation.SuppressLint; 536 | import android.annotation.UserIdInt; 537 | import android.app.PendingIntent; 538 | +import android.app.compat.sn00x.AndroidAutoHelper; 539 | import android.companion.AssociationInfo; 540 | import android.companion.AssociationRequest; 541 | import android.companion.IAssociationRequestCallback; 542 | @@ -175,6 +177,17 @@ 543 | createAssociationAndNotifyApplication(request, packageName, userId, 544 | /*macAddress*/ null, callback); 545 | return; 546 | + } 547 | + 548 | + // create association for AndroidAuto right away 549 | + // alternatively packageName (including cert digest) could be added to config_systemAutomotiveProjection 550 | + // to grant the role android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION. However, though it would make willAddRoleHolder() pass, 551 | + // more invasive permissions (e.g. microphone) would be granted. 552 | + if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(request.getDeviceProfile()) 553 | + && AndroidAutoHelper.isAndroidAuto(packageName, packageUid)) { 554 | + createAssociationAndNotifyApplication(request, packageName, userId, 555 | + /*macAddress*/ null, callback); 556 | + return; 557 | } 558 | 559 | // 2b. Build a PendingIntent for launching the confirmation UI, and send it back to the app: 560 | diff -wurN a/frameworks/base/services/companion/java/com/android/server/companion/PermissionsUtils.java b/frameworks/base/services/companion/java/com/android/server/companion/PermissionsUtils.java 561 | --- a/frameworks/base/services/companion/java/com/android/server/companion/PermissionsUtils.java 2023-08-30 14:57:19.378620736 +0200 562 | +++ b/frameworks/base/services/companion/java/com/android/server/companion/PermissionsUtils.java 2023-08-28 15:25:13.929933006 +0200 563 | @@ -36,6 +36,7 @@ 564 | import android.annotation.NonNull; 565 | import android.annotation.Nullable; 566 | import android.annotation.UserIdInt; 567 | +import android.app.compat.sn00x.AndroidAutoHelper; 568 | import android.companion.AssociationInfo; 569 | import android.companion.AssociationRequest; 570 | import android.companion.CompanionDeviceManager; 571 | @@ -166,6 +167,9 @@ 572 | @Nullable String actionDescription) { 573 | if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return; 574 | 575 | + // Allow AndroidAuto to manage it's associations 576 | + if (userId == 0 && AndroidAutoHelper.isAndroidAuto(packageName, getCallingUid())) return; 577 | + 578 | throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have " 579 | + "permissions to " 580 | + (actionDescription != null ? actionDescription : "manage associations") 581 | diff -wurN a/frameworks/base/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/frameworks/base/services/core/java/com/android/server/display/DisplayDeviceRepository.java 582 | --- a/frameworks/base/services/core/java/com/android/server/display/DisplayDeviceRepository.java 2023-08-30 14:57:19.378620736 +0200 583 | +++ b/frameworks/base/services/core/java/com/android/server/display/DisplayDeviceRepository.java 2023-08-29 16:10:13.101421332 +0200 584 | @@ -17,6 +17,7 @@ 585 | package com.android.server.display; 586 | 587 | import android.annotation.NonNull; 588 | +import android.app.compat.sn00x.AndroidAutoHelper; 589 | import android.os.Trace; 590 | import android.util.Slog; 591 | import android.view.Display; 592 | @@ -156,6 +157,7 @@ 593 | 594 | mDisplayDevices.add(device); 595 | sendEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); 596 | + AndroidAutoHelper.handleDisplayAdded(info.name, info.ownerUid); 597 | } 598 | } 599 | 600 | @@ -205,6 +207,7 @@ 601 | Slog.i(TAG, "Display device removed: " + info); 602 | device.mDebugLastLoggedDeviceInfo = info; 603 | sendEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED); 604 | + AndroidAutoHelper.handleDisplayRemoved(info.name, info.ownerUid); 605 | } 606 | } 607 | 608 | diff -wurN a/frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java b/frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java 609 | --- a/frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java 2023-08-30 14:57:19.378620736 +0200 610 | +++ b/frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java 2023-08-28 15:25:13.929933006 +0200 611 | @@ -65,6 +65,7 @@ 612 | import android.annotation.Nullable; 613 | import android.annotation.UserIdInt; 614 | import android.app.ActivityManager; 615 | +import android.app.compat.sn00x.AndroidAutoHelper; 616 | import android.content.ComponentName; 617 | import android.content.Context; 618 | import android.content.Intent; 619 | @@ -169,6 +170,8 @@ 620 | import java.util.Set; 621 | import java.util.UUID; 622 | 623 | +import static com.android.internal.gmscompat.GmsInfo.PACKAGE_PLAY_STORE; 624 | + 625 | /** 626 | * This class contains the implementation of the Computer functions. It 627 | * is entirely self-contained - it has no implicit access to 628 | @@ -5103,6 +5106,10 @@ 629 | final int callingUid = Binder.getCallingUid(); 630 | final int userId = UserHandle.getUserId(callingUid); 631 | 632 | + if (AndroidAutoHelper.isAndroidAuto(callingUid)) { 633 | + return new InstallSourceInfo(PACKAGE_PLAY_STORE, null, PACKAGE_PLAY_STORE, PACKAGE_PLAY_STORE); 634 | + } 635 | + 636 | String installerPackageName; 637 | String initiatingPackageName; 638 | String originatingPackageName; 639 | diff -wurN a/frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java 640 | --- a/frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java 2023-08-30 14:57:19.390620702 +0200 641 | +++ b/frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java 2023-08-28 15:25:13.933933009 +0200 642 | @@ -16,6 +16,7 @@ 643 | 644 | package com.android.server.pm; 645 | 646 | +import static android.content.Intent.ACTION_MAIN; 647 | import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE; 648 | import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; 649 | import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; 650 | @@ -37,6 +38,7 @@ 651 | import android.annotation.Nullable; 652 | import android.annotation.UserIdInt; 653 | import android.app.ActivityManager; 654 | +import android.app.compat.sn00x.AndroidAutoHelper; 655 | import android.compat.annotation.ChangeId; 656 | import android.compat.annotation.Disabled; 657 | import android.content.Context; 658 | @@ -1097,6 +1099,11 @@ 659 | Intent intent, String resolvedType, int filterCallingUid) { 660 | if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return; 661 | 662 | + // Allow screen2auto to start apps targeting Android 13 663 | + if (ACTION_MAIN.equals(intent.getAction()) && AndroidAutoHelper.isScreen2Auto(filterCallingUid)) { 664 | + return; 665 | + } 666 | + 667 | final Printer logPrinter = DEBUG_INTENT_MATCHING 668 | ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM) 669 | : null; 670 | diff -wurN a/frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java 671 | --- a/frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java 2023-08-30 14:57:19.390620702 +0200 672 | +++ b/frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java 2023-08-30 14:52:40.723680369 +0200 673 | @@ -67,6 +67,7 @@ 674 | import android.app.ActivityManager; 675 | import android.app.IActivityManager; 676 | import android.app.admin.DevicePolicyManagerInternal; 677 | +import android.app.compat.sn00x.AndroidAutoHelper; 678 | import android.compat.annotation.ChangeId; 679 | import android.compat.annotation.EnabledAfter; 680 | import android.content.Context; 681 | @@ -178,7 +179,6 @@ 682 | private static final String SKIP_KILL_APP_REASON_NOTIFICATION_TEST = "skip permission revoke " 683 | + "app kill for notification test"; 684 | 685 | - 686 | private static final long BACKUP_TIMEOUT_MILLIS = SECONDS.toMillis(60); 687 | 688 | /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ 689 | @@ -965,6 +965,10 @@ 690 | return PackageManager.PERMISSION_DENIED; 691 | } 692 | 693 | + if (AndroidAutoHelper.hasAdditionalPermission(pkg.getPackageName(), permissionName, pkg.getSigningDetails())) { 694 | + return PackageManager.PERMISSION_GRANTED; 695 | + } 696 | + 697 | if (checkSinglePermissionInternalLocked(uidState, permissionName, isInstantApp)) { 698 | return PackageManager.PERMISSION_GRANTED; 699 | } 700 | diff -wurN a/frameworks/base/services/core/java/com/android/server/wm/WindowState.java b/frameworks/base/services/core/java/com/android/server/wm/WindowState.java 701 | --- a/frameworks/base/services/core/java/com/android/server/wm/WindowState.java 2023-08-30 14:57:19.390620702 +0200 702 | +++ b/frameworks/base/services/core/java/com/android/server/wm/WindowState.java 2023-08-28 15:25:13.933933009 +0200 703 | @@ -193,6 +193,7 @@ 704 | import android.app.ActivityTaskManager; 705 | import android.app.AppOpsManager; 706 | import android.app.admin.DevicePolicyCache; 707 | +import android.app.compat.sn00x.AndroidAutoHelper; 708 | import android.content.Context; 709 | import android.content.res.Configuration; 710 | import android.graphics.Matrix; 711 | @@ -2020,7 +2021,7 @@ 712 | } 713 | 714 | boolean isSecureLocked() { 715 | - if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) { 716 | + if (!AndroidAutoHelper.isMediaApp(mShowUserId) && (mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) { 717 | return true; 718 | } 719 | return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId); 720 | --------------------------------------------------------------------------------