Android AutoMotive系统预置GMS

GMS介绍

GMS全称为GoogleMobile Service,即谷歌移动服务GMS是Google开发并推动Android的动力,是谷歌程序运行的基础。

GMS提供有GooglePlaySearchGoogle语音GmailContact SyncCalendar SyncTalkGoogle MapsStreet ViewYouTubeAndroid Market等服务,GMS为安卓上的谷歌公司系列应用提供支持

GMS资源获取

由于Google官方未开放相关的服务,应用的资源下载,这时我们可以选择再Open GApps网站上进行下载, Open GApps项目是一个开源项目,每晚(欧洲时间)更新一次Google相关的APP,包含适配各种架构,安卓版本的Google相关的服务,应用的资源文件。

版本的选择

当我们进入Open GApps后可看到如下的选项界面,比较清晰,根据需求进行选择下载:

针对不同的Variant包含的内容,网上已有介绍,这里也粘贴一下:

类型 描述
aroma super版所包含的GApps相同,但是在Recovery中引入了图形化界面。
super 包含了所有GApps,像韩语日语中文拼音中文注音输入法等。(请注意:如果你是用的是基于原生的ROM ,本版本会替换相机,通讯录等等所有有关应用)。
stock 类似于Google Pixel出厂内置的GApps,相比super版少了其他语种的输入法以及Google地球等。
full stock 版所包含的内容相同,但此版本不会替换手机原本的应用.
mini 包含基础的 Google 服务框架,以及一些影响力较大的GApps,相比full版去掉了Docs等应用。
micro 包含基础的Google服务框架和 Gmail 等常见GApps
nano 包含基础的Google服务框架,但不会有其他不必要的GApps
pico 包含最迷你的Google服务框架,但由于框架并非完整,部分GApps可能无法运行
tvstock 此软件包适用于Android TV设备。 它包括Nexus Player标配的所有GApps
tvmini 适用于Android TV设备,只包含一些影响力较大的GApps

这里我建议下一个pico版本和super,导入pico相关的内容后可以正常运行,根据需要去super版本内拿其余的apk

资源包的解析

下载后解压可看到如下目录形式的文件结构:


其中

  • Core是核心应用和资源的目录,也就是我们需要预置的部分;
  • GApps是其他的原生apk,根据需求判断是否需要添加;
  • Optional是一些可选的库,可一并导入进系统;
  • tar-armunzip-armzip-arm是一些解压缩的工具;
  • 其余的文件还包含一些安装脚本,可自动将相关的内容安装至我们的手机中,由于我们是预置进系统内,所以不需要关注这部分的东西;

由于提取相关的apk和资源手动弄起来比较慢,这里提供一个我自己写的脚本,放到解压后的目录的根目录,然后执行脚本就可以将所有我们需要的东西整理输出到一个out目录,我们再根据目录的结构预置进我们的系统即可;

basepath=$(cd `dirname $0`; pwd)

mkdir -p out/priv-app
mkdir -p out/common
mkdir -p out/app

cd $basepath/Core && find . -name '*.lz' | xargs -n1 lzip -d && ls *.tar | xargs -n1 tar -xvf && cd ..
cd $basepath/GApps && find . -name '*.lz' | xargs -n1 lzip -d && ls *.tar | xargs -n1 tar -xvf && cd ..
cd $basepath/Optional && find . -name '*.lz' | xargs -n1 lzip -d && ls *.tar | xargs -n1 tar -xvf && cd ..

find -name priv-app | xargs -i cp -rf {} out/
find -name common | xargs -i cp -rf {} out/
find -name app | xargs -i cp -rf {} out/

cp g.prop out/common/etc/

执行后对应的out目录的内部结构如下:

Gapps_dir.png

这里介绍其中几个比较重要的应用

  1. Google服务框架: GoogleServicesFramework;
  2. Google核心服务:PrebuiltGmsCore
  3. GooglePlay:Phonesky

注意:我们预置相关的应用,编写相应Android.mk时,保留应用原有的签名,不要使用我们的系统签名去覆盖它

GMS预置

GMS资源预置

priv-app的预置

预置Android.mk的编写,由于应用较多,所以这里以一个应用为例,其他的依样画葫芦即可:

##############GoogleServicesFramework##################
include $(CLEAR_VARS)
#
## Module name should match apk name to be installed
LOCAL_MODULE := GoogleServicesFramework
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := GoogleServicesFramework.apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_DEX_PREOPT := true
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_PREBUILT)

app的预置

同样的以一个应用为例

##############YouTube##################
include $(CLEAR_VARS)
## Module name should match apk name to be installed
LOCAL_MODULE := YouTube
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := YouTube.apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_DEX_PREOPT := false
LOCAL_OVERRIDES_PACKAGES += \
                YouTubeLeanback
include $(BUILD_PREBUILT)

common目录的预置

common目录内部的文件只需要拷贝进system目录下即可,根据需要写在对应的mk

    # google config
    PRODUCT_COPY_FILES += $(call find-copy-subdir-files,*,xxxxxx/common,/system)

当所有都配置好之后编译烧录即可,当然前提是系统分区的空间充足,不然可能会编译不通过;
之后烧录进设备,联网之后会出现设备未认证的提示,根据提示进行认证操作即可,这部分网上教程很多,这里也不展开。

AutoMotive系统GooglePlay预置问题

预置时需要注意除了GooglePlay以外基本都不太会受设备类型的影响,而GooglePlay根据不同的设备类型有不同的应用apk,内部可搜索的应用数量也跟设备内支持的feature有关(这部门的内容比较细,没有研究的很深入,后续有机会再整理)。虽然有不同的apk,但它们对应的包名都为com.android.vending

根据设备类型可分为如下几种:

类型 描述
手机版Phonesky 应用类型最多,最丰富的版本,我们所需要的也是这种版本。
电视版Tubesky 电视版本的谷歌商店,经过测试,触摸无法控制,需要靠外部的输入设备(如键盘,鼠标,遥控器)等才可控制应用内部的内容,对触摸设备没做支持?所以也不考虑使用。
车机(AutoMotive)版 目前AutoMotive版本没有对应的apk,我所用的apk为安装手机版GooglePlay,然后由于系统存在AutoMotiveFeature,所以GooglePlay自动更新匹配我的设备,从而得来的apk(/data/app/com.android.vendingxxxx 目录下),这种方式拿到的apk是被分包处理后的,若要预置到系统中,暂时还没找到合适的方式;若只预置base.apk会出现某些资源缺失的问题,暂时不太可用。
手表版 为穿戴类安卓设备定制的谷歌商店,我这边没有研究,也就不展开。

那么如何在我们的车机预置手机版的GooglePlay

预置不同于设备类型的GooglePlay

前面有提到GooglePlay受设备类型的影响,正常情况我们将手机版GooglePlay安装在车机设备上是无法运行的,打开GooglePlay会有如下的弹窗:

google_play_store_error.png

此时其实后台后自动帮我们将GooglePlay更新到车机版本,等待更新完成之后也能打开。

automotve_googleplay.png

车机版GooglePlay界面

可以看到车机版的应用数量较少,可能不太符合我们的需求,所以需要兼容运行手机版GooglePlay

通过jadx-gui对手机版GooglePlayPhonesky.apk进行反编译,搜索automotive我们可以看到内部有一些针对automotive, watch,tvfeature类型的判断,虽然无法具体的看到是什么逻辑,但是我们可以大致猜测出GooglePlay的内容和启动跟feature有一定的关系,这部分在官网也有一定的介绍。

jadx_1.png

这里我们取其中一个代码来看一下:

package defpackage;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import com.google.firebase.FirebaseCommonRegistrar;

/* compiled from: PG */
/* renamed from: aepj  reason: default package */
/* loaded from: classes.dex */
public final /* synthetic */ class aepj implements aetv {
    private final /* synthetic */ int e;
    public static final /* synthetic */ aepj d = new aepj(3);
    public static final /* synthetic */ aepj c = new aepj(2);
    public static final /* synthetic */ aepj b = new aepj(1);
    public static final /* synthetic */ aepj a = new aepj(0);

    private /* synthetic */ aepj(int i) {
        this.e = i;
    }

    @Override // defpackage.aetv
    public final String a(Object obj) {
        int i = this.e;
        if (i == 0) {
            ApplicationInfo applicationInfo = ((Context) obj).getApplicationInfo();
            return (applicationInfo == null || Build.VERSION.SDK_INT < 24) ? "" : String.valueOf(applicationInfo.minSdkVersion);
        } else if (i == 1) {
            ApplicationInfo applicationInfo2 = ((Context) obj).getApplicationInfo();
            return applicationInfo2 != null ? String.valueOf(applicationInfo2.targetSdkVersion) : "";
        } else if (i != 2) {
            Context context = (Context) obj;
            String installerPackageName = context.getPackageManager().getInstallerPackageName(context.getPackageName());
            return installerPackageName != null ? FirebaseCommonRegistrar.a(installerPackageName) : "";
        } else {
            Context context2 = (Context) obj;
            return context2.getPackageManager().hasSystemFeature("android.hardware.type.television") ?
            "tv" : context2.getPackageManager().hasSystemFeature("android.hardware.type.watch") ?
            "watch" : (Build.VERSION.SDK_INT < 23 || !context2.getPackageManager().hasSystemFeature("android.hardware.type.automotive")) ?
            (Build.VERSION.SDK_INT < 26 || !context2.getPackageManager().hasSystemFeature("android.hardware.type.embedded")) ?
            "" : "embedded" : "auto";
        }
    }
}

可以看到代码内通过PackageManagerhasSystemFeature方法去判断设备的类型,并返回对应的类型字符串,在源码中找到对应的方法和文件(framework/base/core/java/android/app/ApplicationPackageManager.java),结合内部的方法来看,除了hasSystemFeature,还有一个获得系统所有支持的Feature的方法:getSystemAvailableFeatures,我们同样在反编译后的代码中搜索。

jadx_2.png

看到同样有调用,那么我们就都一起改掉。

GMS适配修改方案

framework/base/core/java/android/app/ApplicationPackageManager.java

public class ApplicationPackageManager extends PackageManager {
    ....
    public FeatureInfo[] getSystemAvailableFeatures() {
        try {
            ParceledListSlice<FeatureInfo> parceledList =
                    mPM.getSystemAvailableFeatures();
            if (parceledList == null) {
                return new FeatureInfo[0];
            }
            final List<FeatureInfo> list = parceledList.getList();
            // add start ——————————————————————
            if ("com.android.vending".equals(mContext.getPackageName()) || "com.google.android.gms".equals(mContext.getPackageName())) {
                // googleplay and gms service rmove automotvie feature
                for (int i = 0; i < list.size(); i++) {
                    if(PackageManager.FEATURE_AUTOMOTIVE.equals(list.get(i).name)) {
                        Log.i(TAG, "getSystemAvailableFeatures mContext.getPackageName() = " + mContext.getPackageName() + " i = " + i);
                        list.remove(i);
                    }
                }
            }
            // add end ——————————————————————
            final FeatureInfo[] res = new FeatureInfo[list.size()];
            for (int i = 0; i < res.length; i++) {
                res[i] = list.get(i);
            }
            return res;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @Override
    public boolean hasSystemFeature(String name) {
        return hasSystemFeature(name, 0);
    }

    @Override
    public boolean hasSystemFeature(String name, int version) {
        // add start ——————————————————————
        if ("com.android.vending".equals(mContext.getPackageName()) || "com.google.android.gms".equals(mContext.getPackageName())) {
            // googleplay and gms service rmove automotvie feature
            Log.i(TAG, "hasSystemFeature mGmsFeatureType = " + mGmsFeatureType + " name = "+ name);
            if (PackageManager.FEATURE_AUTOMOTIVE.equals(name)) {
                return false;
            }
        }
        // add end ——————————————————————
        try {
            return mPM.hasSystemFeature(name, version);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}

逻辑很简单,就是根据对应的包名移除对应的feature。之后再次编译操作,就可以成功在我们的设备上运行手机版的GooglePlay了,运行效果如下:

automotve_googleplay.png

GMS一次性认证方案

前面有提到,我们的设备烧录之后都需要根据Google官方流程对设备进行验证,如果是大量出货的设备,若没有与Google合作,那么每一台都需要走一遍验证流程,非常耗时和繁琐,所以我们需要通过一些方式跳过这个认证的过程,保证我们的设备在烧录之后不需要进行认证也可以使用谷歌服务,下面介绍一下具体的实现方式。

根据官网的认证操作流程,我们知道注册认证的android_id的获取方式如下:

adb root 
abd shell
sqlite3 /data/data/com.google.android.gsf/databases/gservices.db
select * from main where name = "android_id";

所以我们只需要保证我们每台设备的android_id都是我们已经认证过的设备id即可。
针对这一方案,可用的实现方案如下:

  1. 将已经认证设备的Google服务相关的初始数据库 /data/data/com.google.android.gsf/databases/data/data/com.google.android.gms/databases 复制出来;
  2. 将已认证的Google服务数据库打包进系统固件中;
  3. 编写脚本,第一次开机时启动,使用已认证的Google服务数据库覆盖设备内对应的数据库;
  4. 检查设备android_id,确认成功替换,且没有设备待认证的通知;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容