Android AutoMotive系統(tǒng)預置GMS

GMS介紹

GMS全稱為GoogleMobile Service,即谷歌移動服務GMS是Google開發(fā)并推動Android的動力,是谷歌程序運行的基礎。

GMS提供有GooglePlay、Search、Google語音、Gmail、Contact Sync、Calendar SyncTalk、Google Maps、Street View、YouTube、Android Market等服務,GMS為安卓上的谷歌公司系列應用提供支持

GMS資源獲取

由于Google官方未開放相關的服務,應用的資源下載,這時我們可以選擇再Open GApps網站上進行下載, Open GApps項目是一個開源項目,每晚(歐洲時間)更新一次Google相關的APP,包含適配各種架構,安卓版本的Google相關的服務,應用的資源文件。

版本的選擇

當我們進入Open GApps后可看到如下的選項界面,比較清晰,根據(jù)需求進行選擇下載:

針對不同的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相關的內容后可以正常運行,根據(jù)需要去super版本內拿其余的apk。

資源包的解析

下載后解壓可看到如下目錄形式的文件結構:


其中

  • Core是核心應用和資源的目錄,也就是我們需要預置的部分;
  • GApps是其他的原生apk,根據(jù)需求判斷是否需要添加;
  • Optional是一些可選的庫,可一并導入進系統(tǒng);
  • tar-armunzip-arm、zip-arm是一些解壓縮的工具;
  • 其余的文件還包含一些安裝腳本,可自動將相關的內容安裝至我們的手機中,由于我們是預置進系統(tǒng)內,所以不需要關注這部分的東西;

由于提取相關的apk和資源手動弄起來比較慢,這里提供一個我自己寫的腳本,放到解壓后的目錄的根目錄,然后執(zhí)行腳本就可以將所有我們需要的東西整理輸出到一個out目錄,我們再根據(jù)目錄的結構預置進我們的系統(tǒng)即可;

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/

執(zhí)行后對應的out目錄的內部結構如下:

Gapps_dir.png

這里介紹其中幾個比較重要的應用

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

注意:我們預置相關的應用,編寫相應Android.mk時,保留應用原有的簽名,不要使用我們的系統(tǒng)簽名去覆蓋它

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目錄下即可,根據(jù)需要寫在對應的mk

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

當所有都配置好之后編譯燒錄即可,當然前提是系統(tǒng)分區(qū)的空間充足,不然可能會編譯不通過;
之后燒錄進設備,聯(lián)網之后會出現(xiàn)設備未認證的提示,根據(jù)提示進行認證操作即可,這部分網上教程很多,這里也不展開。

AutoMotive系統(tǒng)GooglePlay預置問題

預置時需要注意除了GooglePlay以外基本都不太會受設備類型的影響,而GooglePlay根據(jù)不同的設備類型有不同的應用apk,內部可搜索的應用數(shù)量也跟設備內支持的feature有關(這部門的內容比較細,沒有研究的很深入,后續(xù)有機會再整理)。雖然有不同的apk,但它們對應的包名都為com.android.vending。

根據(jù)設備類型可分為如下幾種:

類型 描述
手機版Phonesky 應用類型最多,最豐富的版本,我們所需要的也是這種版本。
電視版Tubesky 電視版本的谷歌商店,經過測試,觸摸無法控制,需要靠外部的輸入設備(如鍵盤,鼠標,遙控器)等才可控制應用內部的內容,對觸摸設備沒做支持?所以也不考慮使用。
車機(AutoMotive)版 目前AutoMotive版本沒有對應的apk,我所用的apk為安裝手機版GooglePlay,然后由于系統(tǒng)存在AutoMotiveFeature,所以GooglePlay自動更新匹配我的設備,從而得來的apk(/data/app/com.android.vendingxxxx 目錄下),這種方式拿到的apk是被分包處理后的,若要預置到系統(tǒng)中,暫時還沒找到合適的方式;若只預置base.apk會出現(xiàn)某些資源缺失的問題,暫時不太可用。
手表版 為穿戴類安卓設備定制的谷歌商店,我這邊沒有研究,也就不展開。

那么如何在我們的車機預置手機版的GooglePlay

預置不同于設備類型的GooglePlay

前面有提到GooglePlay受設備類型的影響,正常情況我們將手機版GooglePlay安裝在車機設備上是無法運行的,打開GooglePlay會有如下的彈窗:

google_play_store_error.png

此時其實后臺后自動幫我們將GooglePlay更新到車機版本,等待更新完成之后也能打開。

automotve_googleplay.png

車機版GooglePlay界面

可以看到車機版的應用數(shù)量較少,可能不太符合我們的需求,所以需要兼容運行手機版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,還有一個獲得系統(tǒng)所有支持的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();
        }
    }
}

邏輯很簡單,就是根據(jù)對應的包名移除對應的feature。之后再次編譯操作,就可以成功在我們的設備上運行手機版的GooglePlay了,運行效果如下:

automotve_googleplay.png

GMS一次性認證方案

前面有提到,我們的設備燒錄之后都需要根據(jù)Google官方流程對設備進行驗證,如果是大量出貨的設備,若沒有與Google合作,那么每一臺都需要走一遍驗證流程,非常耗時和繁瑣,所以我們需要通過一些方式跳過這個認證的過程,保證我們的設備在燒錄之后不需要進行認證也可以使用谷歌服務,下面介紹一下具體的實現(xiàn)方式。

根據(jù)官網的認證操作流程,我們知道注冊認證的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即可。
針對這一方案,可用的實現(xiàn)方案如下:

  1. 將已經認證設備的Google服務相關的初始數(shù)據(jù)庫 /data/data/com.google.android.gsf/databases/data/data/com.google.android.gms/databases 復制出來;
  2. 將已認證的Google服務數(shù)據(jù)庫打包進系統(tǒng)固件中;
  3. 編寫腳本,第一次開機時啟動,使用已認證的Google服務數(shù)據(jù)庫覆蓋設備內對應的數(shù)據(jù)庫;
  4. 檢查設備android_id,確認成功替換,且沒有設備待認證的通知;
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容