#This file is part of The Open GApps script of @mfonville. # # The Open GApps scripts are free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # These scripts are distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # LOWESTAPI_all="19" LOWESTAPI_arm="19" LOWESTAPI_arm64="21" LOWESTAPI_x86="19" LOWESTAPI_x86_64="21" LOWESTAPI_leanback="22" GOOGLECERT="Issuer: C=US, ST=C(A|alifornia), L=Mountain View, O=(Google((|,) Inc(|.)|)|Android), OU=(Google((|,) Inc(|.)|)|Android), CN=" #IMPORTCERTS="" #if this value is set to non-zero, new certificates matching above regexp will be imported INCOMPLETEFILES=1 INVALIDCERT=2 UNSIGNEDFILES=3 APKTOOLFAILED=11 checktools bc install -d "$CACHE" touch "$CACHE/signaturecache.txt" addsignaturetocache() { if [ ! -s "$CACHE/signaturecache.txt" ]; then echo -e " " >> "$CACHE/signaturecache.txt" # our sed replacement does not work on empty file fi sha1sum="$(sha1sum "$1" | cut -d ' ' -f 1)" sed -i "\#^[0-9a-f]\{40\}=$1#{h;s#[0-9a-f]\{40\}=#$sha1sum=#};\${x;/^\$/{s##$sha1sum=$1#;H};x}" "$CACHE/signaturecache.txt" } getapkproperties(){ apkproperties="$(aapt dump badging "$1" 2>/dev/null)" name="$(echo "$apkproperties" | grep -a "application-label:" | sed 's/application-label://g' | sed "s/'//g")" package="$(echo "$apkproperties" | awk '/package:/ {print $2}' | sed s/name=//g | sed s/\'//g | awk '{print tolower($0)}')" versionname="$(echo "$apkproperties" | awk -F="'" '/versionName=/ {print $4}' | sed "s/'.*//g")" versioncode="$(echo "$apkproperties" | awk -F="'" '/versionCode=/ {print $3}' | sed "s/'.*//g")" sdkversion="$(echo "$apkproperties" | grep -a "sdkVersion:" | sed 's/sdkVersion://' | sed "s/'//g")" compatiblescreens="$(echo "$apkproperties" | grep -a "compatible-screens:'")" #the ' is added to prevent detection of lines that only have compatiblescreens but without any values native="$(echo "$apkproperties" | grep -av "alt-native-code:" | grep -a "native-code:" | sed 's/native-code://g' | sed "s/'//g") " # add a space at the end altnative="$(echo "$apkproperties" | grep -a "alt-native-code:" | sed 's/alt-native-code://g' | sed "s/'//g") " # add a space at the end leanback="$(echo "$apkproperties" | grep -a "android.software.leanback" | grep -v "\-not\-required" | awk -F [.\'] '{print $(NF-1)}')" # 'leanback' vrmode="$(echo "$apkproperties" | grep -a "android.software.vr.mode" | grep -v "\-not\-required" | awk -F [.\'] '{print $(NF-2)$(NF-1)}')" # 'vrmode' watch="$(echo "$apkproperties" | grep -a "android.hardware.type.watch" | awk -F [.\'] '{print $(NF-1)}')" # 'watch' ram="$(echo "$apkproperties" | grep -a "android.hardware.ram" | grep -v "\-not\-required" | awk -F [.\'] '{print $(NF-1)}')" # 'low' => means Android Go (https://developer.android.com/docs/quality-guidelines/build-for-billions/device-capacity#androidgo) dialer_go_experience="$(echo "$apkproperties" | grep -a "com.google.android.apps.dialer.GO_EXPERIENCE" | grep -v "\-not\-required" | awk -F [.\'] '{print $(NF-1)}')" # 'GO_EXPERIENCE' => means Android Go case "$versionname" in *leanback*) leanback="leanback";; esac if [ -n "$leanback" ]; then case "$package" in *inputmethod*) ;; #if package is an inputmethod, it will have leanback as feature described, but we don't want it recognized as such *.leanback) ;; #if package already has leanback at the end of its name, we don't need to add it ourselves *) package="$package.$leanback";; #special leanback versions need a different packagename esac fi sdkversionhacks case $package in "com.android.vending" |\ "com.android.vending.leanback" |\ "com.google.android.androidforwork" |\ "com.google.android.apps.gcs" |\ "com.google.android.apps.mediashell.leanback" |\ "com.google.android.apps.nexuslauncher" |\ "com.google.android.apps.pixelmigrate" |\ "com.google.android.apps.restore" |\ "com.google.android.apps.wellbeing" |\ "com.google.android.apps.tv.launcherx.leanback" |\ "com.google.android.as" |\ "com.google.android.backuptransport" |\ "com.google.android.carriersetup" |\ "com.google.android.configupdater" |\ "com.google.android.contacts" |\ "com.google.android.dialer" |\ "com.google.android.ext.services" |\ "com.google.android.feedback" |\ "com.google.android.gms" |\ "com.google.android.gms.leanback" |\ "com.google.android.gms.setup" |\ "com.google.android.googlequicksearchbox" |\ "com.google.android.gsf" |\ "com.google.android.gsf.login" |\ "com.google.android.katniss.leanback" |\ "com.google.android.leanbacklauncher.leanback" |\ "com.google.android.leanbacklauncher.recommendations.leanback" |\ "com.google.android.onetimeinitializer" |\ "com.google.android.packageinstaller" |\ "com.google.android.pano.packageinstaller" |\ "com.google.android.partnersetup" |\ "com.google.android.setupwizard" |\ "com.google.android.storagemanager" |\ "com.google.android.tag" |\ "com.google.android.tungsten.overscan" |\ "com.google.android.tungsten.setupwraith" |\ "com.google.android.tv.leanback" |\ "com.google.android.tv.remote" |\ "com.google.android.tv.remotepairing" |\ "com.google.android.tv.remote.service.leanback") type="priv-app";; *) type="app";; esac # Put the Go edition apps in a dir suffixed by "-go" if [ "a$ram" == "alow" ] \ || [ "a$dialer_go_experience" == "aGO_EXPERIENCE" ] \ || [ "$package" == "com.google.android.apps.cameralite" ] \ || [ "$package" == "com.google.android.apps.mapslite" ] \ || [ "$package" == "com.google.android.apps.navlite" ] \ || [ "$package" == "com.google.android.apps.nbu.files" ] \ || [ "$package" == "com.google.android.apps.photosgo" ] \ || [ "$package" == "com.google.android.apps.searchlite" ] \ || [ "$package" == "com.google.android.apps.youtube.mango" ]; then type="${type}${GO_DIR_SUFFIX}" fi #we do this on purpose after the priv-app detection to emulate the priv-app of the normal app if [ -n "$watch" ]; then case "$package" in com.android.vending* |\ com.google.android.apps.enterprise.dmagent* |\ com.google.android.apps.fitness* |\ com.google.android.apps.maps* |\ com.google.android.apps.messaging* |\ com.google.android.apps.walletnfcrel* |\ com.google.android.calculator* |\ com.google.android.deskclock* |\ com.google.android.gms* |\ com.google.android.googlequicksearchbox* |\ com.google.android.inputmethod.latin* |\ com.google.android.marvin.talkback* |\ com.google.android.keep* |\ com.google.android.music* |\ com.google.android.talk*) package="$package.watch" ;; # special watch versions need a different packagename *) ;; # Otherwise ignore the watch flag esac fi if [ -n "$vrmode" ]; then case "$package" in com.google.android.apps.photos* |\ com.google.android.videos*) package="$package.vrmode" ;; # special vrmode versions need a different packagename *) ;; # Otherwise ignore the vrmode flag esac fi if [ -n "$STUB" ]; then package="$package.$STUB" # xxx.stub fi if [ -n "$BETA" ]; then package="$package.$BETA" # xxx.stub.beta fi stub="" #make sure value is initialized beta="" #make sure value is initialized case "$1" in *.stub/*) stub="stub" #report stub status as a property case "$package" in *.stub);; *) package="$package.stub";; # set .stub in package name if not set yet esac;; esac case "$1" in *.beta/*) beta="beta" #report beta status as a property case "$package" in *.beta);; *) package="$package.beta";; # set .beta in package name if not set yet esac;; esac if [ "$(echo $compatiblescreens)" = "" ]; then # we can't use -z here, because there can be a linecontrol character or such in it dpis="nodpi" else dpis=$(echo "$compatiblescreens" | grep "compatible-screens:" | grep -oE "/([0-9][0-9])[0-9]" | sort -u | tr -d '\012\015' | tr '/' '-' | cut -c 2-) fi } getarchitecturesfromlib() { # Some packages don't have native-code specified, but are still depending on it # If multiple architectures are found; we assume it to be only compatible with the highest architecture and not multi-arch architectures="" libfiles=$(unzip -qqql "$1" "lib/*" | tr -s ' ' | cut -d ' ' -f5-) for lib in $libfiles; do #this gives all files found in the lib-folder(s), check their paths for which architectures' libs are included arch="$(echo "$lib" | awk 'BEGIN { FS = "/" } ; {print $2}')" #add a space at the end if ! echo "$architectures" | grep -q "$arch "; then #only add if this architecture is not yet in the list; use a space to distinguish substrings (e.g. x86 vs x86_64) architectures="$architectures$arch " fi done if [ -z "$architectures" ]; then #If the package really has no native code architectures="all" fi } getsignaturefromcache() { sha1sum="$(sha1sum "$1" | cut -d ' ' -f 1)" return $(grep -oqE "^$sha1sum=$1\$" "$CACHE/signaturecache.txt") } getsetupwizardproduct() { # Setupwizard has various variations, depending on product type. We need to decompile the APK to find this value # this function is not part of the regular getapkproperties script because it is heavy and only necessary when adding an APK tmpframedir="$(mktemp -d)" tmpapkdir="$(mktemp -d)" if java -jar "$APKTOOL" -q d -b -f -s -o "$tmpapkdir" -p "$tmpframedir" "$1"; then product="$(grep '' "$tmpapkdir/res/values/strings.xml" | sed -r 's#.*([^<]*)#\1#')" rm -rf "$tmpapkdir" rm -rf "$tmpframedir" else return $APKTOOLFAILED fi } verifyapk() { # Check if we have a verification "ok" in signaturecache if getsignaturefromcache "$1"; then return 0 # it is in cache fi notinzip="" if importcert "$1" "$2"; then #always import, because sometimes new certificates are supplied but it would never be detected because the exitcode of jarsigner -verify would be 0, because the existing certificates would suffice if ! timeout 1m jarsigner -verify -keystore "$CERTIFICATES/opengapps.keystore" -strict "$1" 1>/dev/null 2>&1; then # timeout added because sometimes jarsigner can get stuck for an unknown reason return $UNSIGNEDFILES #contains files not signed by Google. APK not imported fi else return $INVALIDCERT #no valid Google certificate. Certificate and APK not imported fi manifestlist="$(unzip -p "$1" "META-INF/MANIFEST.MF" | sed ':a;N;$!ba;s/\r\n //g' | tr -d '\r' | awk -F' ' '/Name:/ {print $NF}')" ziplist="$(unzip -Z -1 "$1")" RSAFILE="META-INF/CERT" unzip -l "$1" | grep -q "$RSAFILE.RSA" if [ "$?" != "0" ]; then RSAFILE="META-INF/GOOGPLAY" fi notinzip="$(printf "%s\n%s\n" "$manifestlist" "$ziplist" | grep -vxF -e "$RSAFILE.RSA" -e "$RSAFILE.SF" -e "META-INF/MANIFEST.MF" | sort | uniq -u)" if [ -n "$notinzip" ]; then return $INCOMPLETEFILES #files were mentioned in the signed manifest but are not present in the APK fi # add the "ok" to signaturecache addsignaturetocache "$1" } importcert() { RSAFILE="META-INF/CERT" unzip -l "$1" | grep -q "$RSAFILE.RSA" if [ "$?" != "0" ]; then RSAFILE="META-INF/GOOGPLAY" fi unzip -p "$1" "$RSAFILE.RSA" | openssl pkcs7 -inform DER -print_certs -text | grep -q -E "$GOOGLECERT" || return 1 #Certificate is not issued by Google. alias="$(unzip -p "$1" "$RSAFILE.RSA" | openssl pkcs7 -inform DER -print_certs -text | awk -F' ' '/Serial Number:/ {if(NF==2){getline nextline;gsub(/[ \t:]/,"",nextline);print "ibase=16;",toupper(nextline)}else{print "ibase=10;",$(NF-1)}}' | bc)" if ! timeout 1m keytool -list -keystore "$CERTIFICATES/opengapps.keystore" -storepass "opengapps" -noprompt -alias "$alias" 1>/dev/null 2>&1; then if [ -n "$IMPORTCERTS" ]; then #set this variable in your environment if you want to permit the script to update the keystore unzip -p "$1" "$RSAFILE.RSA" | openssl pkcs7 -inform DER -print_certs -text | keytool -importcert -keystore "$CERTIFICATES/opengapps.keystore" -storepass "opengapps" -noprompt -alias "$alias" 1>/dev/null 2>&1 if [ -n "$2" ]; then #silent mode if value is set echo "Certificate with alias $alias is signed by Google and added to the keystore" fi elif [ -z "$2" ]; then #only output if no silent mode value is set echo "APK contains a new Google certificate not yet available in the keystore, please contact Open GApps maintainer to get it included" fi fi return 0 }