最近在使用google 平板 Tangorpro来研究安卓Framework,因此刷成了可以自己编译的AOSP版本.

但是AOSP 开源代码没有自带Google Play应用商店, 通过查询,有通过 OpenGApp的方式来集成.

参考:  https://github.com/opengapps/aosp_build

我的需求只需要集成三件套(Google服务框架、Google play商店和Google Play服务), 能从Google应用市场下载安装应用即可.

有没有更简单的集成方法呢?

第一个问题就是要下载一个跟本地设备匹配的APK版本, 这个可以从Tangorpro的原生ROM中提取出来.

可以从https://developers.google.com/android/drivers?hl=zh-cn 下载本设备对应的镜像文件.

把镜像里的三件套APK提取出来即可.

Image 路径 文件 包名
product.img /product/priv-app/Phonesky Phonesky.apk com.android.vending
product.img /product/priv-app/PrebuiltGmsCoreSc PrebuiltGmsCoreSc.apk com.google.android.gms
system_ext.img /system_ext/priv-app/GoogleServicesFramework GoogleServicesFramework.apk com.google.android.gsf

有了这个三个apk, 如何集成到ROM里呢?

可以参考vendor/google_devices/tangorpro/proprietary下面UwbVendorService.apk的配置方法,

将三个APK拷贝到这个目录:

修改此目录下面的Android.mk,末尾增加

include $(CLEAR_VARS)
LOCAL_MODULE := Phonesky
LOCAL_PACKAGE_NAME := com.android.vending
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_PRIVILEGED_MODULE := true
LOCAL_MODULE_OWNER := qorvo
LOCAL_MODULE_CLASS := APPS
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
include $(BUILD_PREBUILT)



include $(CLEAR_VARS)
LOCAL_MODULE := GoogleServicesFramework
LOCAL_PACKAGE_NAME := com.google.android.gsf
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_PRIVILEGED_MODULE := true
LOCAL_MODULE_OWNER := qorvo
LOCAL_MODULE_CLASS := APPS
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
include $(BUILD_PREBUILT)



include $(CLEAR_VARS)
LOCAL_MODULE := PrebuiltGmsCoreSc
LOCAL_PACKAGE_NAME := com.google.android.gms
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_PRIVILEGED_MODULE := true
LOCAL_MODULE_OWNER := qorvo
LOCAL_MODULE_CLASS := APPS
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
include $(BUILD_PREBUILT)

再把上级目录的device-partial.mk 修改一下,

PRODUCT_PACKAGES := \
    UwbVendorService \
    Phonesky \
    GoogleServicesFramework \
    PrebuiltGmsCoreSc

注意,重新make时需要增加一个设置export DISABLE_ARTIFACT_PATH_REQUIREMENTS=true

否则编译会报错:

vendor/google_devices/tangorpro/device-partial.mk was modified, regenerating...
vendor/google_devices/tangorpro/device-partial.mk was modified, regenerating...
[ 99% 144/145] finishing legacy Make module parsing ...
FAILED: 
build/make/core/artifact_path_requirements.mk:31: warning:  device/google/tangorpro/aosp_tangorpro.mk produces files inside build/make/target/product/generic_system.mks artifact path requirement. 
Offending entries:
system/priv-app/Phonesky/Phonesky.apk
In file included from build/make/core/main.mk:1407:
build/make/core/artifact_path_requirements.mk:31: error: Build failed.
13:03:53 ckati failed with: exit status 1

重新make ,烧录.重启.

设置科学上网之后,打开Google Play会提示需要认证设备. Play保护机制认证 可以通过Google的临时授权来加入,参考:

https://www.google.com/android/uncertified/

将此设备的ID加入临时设备注册即可. 等待5分钟左右即可生效.

如果一直提示未获取Play保护机制认证, 可以清空Google Play和Goole服务的数据,再重试.

点击登录,报错,日志如下:

E  FATAL EXCEPTION: main
   Process: com.android.vending, PID: 9176
   java.lang.RuntimeException: Unable to start service com.google.android.finsky.setup.VpaService@14bce4b with Intent { dat=vpaservice://startvpafordeferredsetupnotification/... cmp=com.android.vending/com.google.android.finsky.setup.VpaService }: java.lang.SecurityException: Starting FGS with type systemExempted callerApp=ProcessRecord{119f011 9176:com.android.vending/u0a128} targetSDK=34 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED] any of the permissions allOf=false [android.permission.SCHEDULE_EXACT_ALARM, android.permission.USE_EXACT_ALARM, android:activate_vpn] 
   	at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:5100)
   	at android.app.ActivityThread.-$$Nest$mhandleServiceArgs(Unknown Source:0)
   	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2432)
   	at android.os.Handler.dispatchMessage(Handler.java:107)
   	at android.os.Looper.loopOnce(Looper.java:232)
   	at android.os.Looper.loop(Looper.java:317)
   	at android.app.ActivityThread.main(ActivityThread.java:8592)
   	at java.lang.reflect.Method.invoke(Native Method)
   	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
   	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
   Caused by: java.lang.SecurityException: Starting FGS with type systemExempted callerApp=ProcessRecord{119f011 9176:com.android.vending/u0a128} targetSDK=34 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED] any of the permissions allOf=false [android.permission.SCHEDULE_EXACT_ALARM, android.permission.USE_EXACT_ALARM, android:activate_vpn] 
   	at android.os.Parcel.createExceptionOrNull(Parcel.java:3183)
   	at android.os.Parcel.createException(Parcel.java:3167)
   	at android.os.Parcel.readException(Parcel.java:3150)
   	at android.os.Parcel.readException(Parcel.java:3092)
   	at android.app.IActivityManager$Stub$Proxy.setServiceForeground(IActivityManager.java:6960)
   	at android.app.Service.startForeground(Service.java:776)
   	at com.google.android.finsky.setup.VpaService.onStartCommand(PG:112)
   	at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:5082)
   	at android.app.ActivityThread.-$$Nest$mhandleServiceArgs(Unknown Source:0) 
   	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2432) 
   	at android.os.Handler.dispatchMessage(Handler.java:107) 
   	at android.os.Looper.loopOnce(Looper.java:232) 
   	at android.os.Looper.loop(Looper.java:317) 
   	at android.app.ActivityThread.main(ActivityThread.java:8592) 
   	at java.lang.reflect.Method.invoke(Native Method) 
   	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580) 
   	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878) 
   Caused by: android.os.RemoteException: Remote stack trace:
   	at com.android.server.am.ActiveServices.validateForegroundServiceType(ActiveServices.java:2842)
   	at com.android.server.am.ActiveServices.setServiceForegroundInnerLocked(ActiveServices.java:2530)
   	at com.android.server.am.ActiveServices.setServiceForegroundLocked(ActiveServices.java:1806)
   	at com.android.server.am.ActivityManagerService.setServiceForeground(ActivityManagerService.java:13793)
   	at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:3483)

查看ActiveServices.java 的函数validateForegroundServiceType

private Pair<Integer, RuntimeException> validateForegroundServiceType(ServiceRecord r,
            @ForegroundServiceType int type,
            @ForegroundServiceType int defaultToType,
            @ForegroundServiceType int startType) {
        final ForegroundServiceTypePolicy policy = ForegroundServiceTypePolicy.getDefaultPolicy();
        final ForegroundServiceTypePolicyInfo policyInfo =
                policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
        final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
                mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
                r.isFgsAllowedWiu_forStart(), policyInfo);
        RuntimeException exception = null;
        switch (code) {
            case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
                final String msg = "Starting FGS with type "
                        + ServiceInfo.foregroundServiceTypeToLabel(type)
                        + " code=" + code
                        + " callerApp=" + r.app
                        + " targetSDK=" + r.app.info.targetSdkVersion;
                Slog.wtfQuiet(TAG, msg);
                Slog.w(TAG, msg);
            } break;
            case FGS_TYPE_POLICY_CHECK_DISABLED: {
                if (startType == FOREGROUND_SERVICE_TYPE_MANIFEST
                        && type == FOREGROUND_SERVICE_TYPE_NONE) {
                    exception = new MissingForegroundServiceTypeException(
                            "Starting FGS without a type "
                            + " callerApp=" + r.app
                            + " targetSDK=" + r.app.info.targetSdkVersion);
                } else {
                    exception = new InvalidForegroundServiceTypeException(
                            "Starting FGS with type "
                            + ServiceInfo.foregroundServiceTypeToLabel(type)
                            + " callerApp=" + r.app
                            + " targetSDK=" + r.app.info.targetSdkVersion
                            + " has been prohibited");
                }
            } break;
            case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_PERMISSIVE: {
                final String msg = "Starting FGS with type "
                        + ServiceInfo.foregroundServiceTypeToLabel(type)
                        + " code=" + code
                        + " callerApp=" + r.app
                        + " targetSDK=" + r.app.info.targetSdkVersion
                        + " requiredPermissions=" + policyInfo.toPermissionString()
                        + (policyInfo.hasForegroundOnlyPermission()
                        ? " and the app must be in the eligible state/exemptions"
                        + " to access the foreground only permission" : "");
                Slog.wtfQuiet(TAG, msg);
                Slog.w(TAG, msg);
            } break;
            case FGS_TYPE_POLICY_CHECK_PERMISSION_DENIED_ENFORCED: {
                exception = new SecurityException("Starting FGS with type "
                        + ServiceInfo.foregroundServiceTypeToLabel(type)
                        + " callerApp=" + r.app
                        + " targetSDK=" + r.app.info.targetSdkVersion
                        + " requires permissions: "
                        + policyInfo.toPermissionString()
                        + (policyInfo.hasForegroundOnlyPermission()
                        ? " and the app must be in the eligible state/exemptions"
                        + " to access the foreground only permission" : ""));
                exception.printStackTrace();
            } break;
            case FGS_TYPE_POLICY_CHECK_OK:
            default:
                break;
        }
        return Pair.create(code, exception);
    }

是由于policy.checkForegroundServiceTypePolicy 检测未过.

ForegroundServiceTypePolicy.checkForegroundServiceTypePolicy方法又会调用

SystemExemptedFgsTypePermission.checkPermission方法,代码如下:

private class SystemExemptedFgsTypePermission extends ForegroundServiceTypePermission {
        SystemExemptedFgsTypePermission() {
            super("System exempted");
        }

        @Override
        public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
                @NonNull String packageName, boolean allowWhileInUse) {
            final AppRestrictionController appRestrictionController = mAm.mAppRestrictionController;
            @ReasonCode int reason = appRestrictionController
                    .getPotentialSystemExemptionReason(callerUid);
            if (reason == REASON_DENIED) {
                reason = appRestrictionController
                        .getPotentialSystemExemptionReason(callerUid, packageName);
                if (reason == REASON_DENIED) {
                    reason = appRestrictionController
                            .getPotentialUserAllowedExemptionReason(callerUid, packageName);
                }
            }
            if (reason == REASON_DENIED) {
                if (ArrayUtils.contains(mAm.getPackageManagerInternal().getKnownPackageNames(
                        KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM), packageName)) {
                    reason = REASON_PACKAGE_INSTALLER;
                }
            }

            switch (reason) {
                case REASON_SYSTEM_UID:
                case REASON_SYSTEM_ALLOW_LISTED:
                case REASON_DEVICE_DEMO_MODE:
                case REASON_DISALLOW_APPS_CONTROL:
                case REASON_DEVICE_OWNER:
                case REASON_PROFILE_OWNER:
                case REASON_PROC_STATE_PERSISTENT:
                case REASON_PROC_STATE_PERSISTENT_UI:
                case REASON_SYSTEM_MODULE:
                case REASON_CARRIER_PRIVILEGED_APP:
                case REASON_DPO_PROTECTED_APP:
                case REASON_ACTIVE_DEVICE_ADMIN:
                case REASON_ROLE_EMERGENCY:
                case REASON_ALLOWLISTED_PACKAGE:
                case REASON_PACKAGE_INSTALLER:
                case REASON_SYSTEM_EXEMPT_APP_OP:
                    return PERMISSION_GRANTED;
                default:
                    return PERMISSION_DENIED;
            }
        }
    }

AppRestrictionController.getPotentialSystemExemptionReason方法代码如下:

/**
     * @param uid The uid to check.
     * @return The potential exemption reason of the given uid. The caller must decide
     * whether or not it should be exempted.
     */
    @ReasonCode
    int getPotentialSystemExemptionReason(int uid) {
        if (UserHandle.isCore(uid)) {
            return REASON_SYSTEM_UID;
        }
        if (isOnSystemDeviceIdleAllowlist(uid)) {
            return REASON_SYSTEM_ALLOW_LISTED;
        }
        if (UserManager.isDeviceInDemoMode(mContext)) {
            return REASON_DEVICE_DEMO_MODE;
        }
        final int userId = UserHandle.getUserId(uid);
        if (mInjector.getUserManagerInternal()
                .hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)) {
            return REASON_DISALLOW_APPS_CONTROL;
        }
        final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
        if (am.isDeviceOwner(uid)) {
            return REASON_DEVICE_OWNER;
        }
        if (am.isProfileOwner(uid)) {
            return REASON_PROFILE_OWNER;
        }
        final int uidProcState = am.getUidProcessState(uid);
        if (uidProcState <= PROCESS_STATE_PERSISTENT) {
            return REASON_PROC_STATE_PERSISTENT;
        } else if (uidProcState <= PROCESS_STATE_PERSISTENT_UI) {
            return REASON_PROC_STATE_PERSISTENT_UI;
        }
        return REASON_DENIED;
    }

分析SystemExemptedFgsTypePermission .checkPermission, 发现是判断当前用户id是不是可以系统豁免没有通过.

是不是可以修改包名,伪装成REASON_PACKAGE_INSTALLER呢?

修改代码如下,直接将com.android.vending判断为REASON_PACKAGE_INSTALLER

if (ArrayUtils.contains(mAm.getPackageManagerInternal().getKnownPackageNames(
                        KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM), packageName)||
                        
"com.android.vending".equals(packageName)
) {
      reason = REASON_PACKAGE_INSTALLER;
  }

重新编译烧录之后,可以登录Google Play,安装下载也是正常的.