#!/usr/bin/env python2

import os, sys
reload(sys)
sys.setdefaultencoding("utf8")
import tempfile

sys.path.append(os.path.join(os.path.dirname(__file__), "androguard"))
import xml.dom.minidom
from androguard.core.bytecodes import apk

STUB_ACTIVITY="com.google.android.stub.PlayStoreRedirectActivity"

MANIFEST_TEMPLATE="""<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:installLocation="internalOnly" 
    android:versionCode="1"
    android:versionName="0.1 (stub)"
    package="%(pkg)s" >

    <uses-sdk android:targetSdkVersion="3" />

    <application android:allowBackup="false" >
        <activity
            android:launchMode="singleTask"
            android:name="%(stub)s"
            android:noHistory="true"
            android:theme="@android:style/Theme.NoDisplay" >
        </activity>
    </application>
</manifest>"""

ANDROID_MK_TEMPLATE = """
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, ../stubs/src)
LOCAL_DEX_PREOPT := false
LOCAL_PACKAGE_NAME := %(pkgname)s
LOCAL_MODULE_PATH := %(outdir)s

include $(BUILD_PACKAGE)
"""

def copy_attribute(src, dst, key, res):
    if not src.attributes.has_key(key):
        return (0, None)
    v = src.attributes[key].value
    if v.startswith("@"):
        resid = int(v[1:], 16)
        for pkgname in res.get_packages_names():
            foundres = res.get_id(pkgname, resid)
            if foundres:
                restype, value, resid = foundres
                v = "@" + "/".join((restype, value))
                break
        else:
            return (0, None)
    else:
        resid = 0

    dst.setAttribute(key, v)
    return resid, v

def create_alias(out, el, res):
    if not el.getElementsByTagName("intent-filter"):
        return []

    alias = out.createElement('activity-alias')

    resources = []

    for k in ("name", "icon", "label"):
        keyname = "android:" + k
        resid, resname = copy_attribute(el, alias, keyname, res)
        if resname:
            resources.append((resid, resname))

    alias.setAttribute("android:targetActivity", STUB_ACTIVITY)

    if el.attributes["android:name"].value in disabled:
        alias.setAttribute("android:enabled", "false")

    app = out.getElementsByTagName('application')[0]

    label = "android:label"
    #for child in el.childNodes:
    for child in el.getElementsByTagName("intent-filter"):
        node = child.cloneNode(True)
        if node.hasAttribute(label):
            node.removeAttribute(label)
        alias.appendChild(node)

    app.appendChild(alias)
    return resources

apks = {
    "com.google.android.youtube": "Youtube",
    "com.google.android.apps.books": "Books",
    "com.google.android.videos": "Videos",
    "com.google.android.apps.docs": "Drive",
    "com.google.android.apps.cloudprint": "CloudPrint2",
    "com.google.android.apps.plus": "PlusOne",
    "com.google.android.apps.translate": "Translate",
    "com.google.android.apps.fitness": "PrebuiltFitness",
    "com.google.android.apps.maps": "Maps",
    "com.google.android.talk": "Hangouts",
}

disabled = (
    "com.google.android.apps.plus.phone.ConversationListActivity",
)

APKOUTDIR = "vendor/google/apps/stubs2/"
TOP = os.getenv("ANDROID_BUILD_TOP")
DIRNAME = 'vendor/google/src'

class TempDir:
    def __init__(self):
        pass

    def __enter__(self):
        self.tempdir = tempfile.mkdtemp()
        return self.tempdir

    def __exit__(self, type, value, traceback):
        os.system("rm -rf " + self.tempdir)
        return value

class GenerateStub:
    def __init__(self, apkfile):
        a = apk.APK(apkfile)
        self.pkgname = a.get_package()
        self.apk = a

    def getPkgName(self):
        return self.pkgname

    def build(self):
        a = self.apk

        apkname = apks[self.pkgname] + "Stub"
        outdir = os.path.join(TOP, DIRNAME, apkname)
        if not os.path.isdir(outdir):
            os.makedirs(outdir)
        self.apkname = apkname
        self.outdir = outdir
        apkfile = os.path.join(TOP, APKOUTDIR, apkname, apkname + ".apk")

        self.res = a.get_android_resources()
        self.axml = a.get_android_manifest_xml()

        self.resources = {}

        androidmk = self.generateAndroidMk()
        self.generateAndroidManifest()
        self.generateBooleans()
        self.generateStrings()
        self.generateDrawables()

        build_cmd = ("env",
            "ONE_SHOT_MAKEFILE=%s"%androidmk[len(TOP) + 1:],
            "make", "-C",
            TOP,
            "-f", "build/core/main.mk", "all_modules")
        assert os.system(" ".join(build_cmd)) == 0

        self.overridePublicCert(apkfile)

    def prepare_resources(self, resource_key):
        rid, res_name = resource_key
        if rid == 0:
            return
        if not res_name.startswith('@'):
            print ("invalid resname:", rid, res_name)
            return
        restype, resname = res_name.split('/')
        restype = restype[1:]
        self.resources.setdefault(restype, set()).add(resource_key)

    def overridePublicCert(self, apkfile):
        CERT = "META-INF/CERT.RSA"
        def system(cmd):
            print(cmd)
            assert os.system(cmd) == 0

        with TempDir() as tmpdir:
            script = ("7z d %(dstfile)s %(cert)s",
                "7z x -o%(tmpdir)s %(srcfile)s %(cert)s",
                    "(cd %(tmpdir)s && 7z a %(dstfile)s %(cert)s)",
                    "zipalign -f -p 4 %(dstfile)s %(dstfile)s.aligned",
                    "mv -f %(dstfile)s.aligned %(dstfile)s",
            )
            script = "&&".join(script)
            script = script%{
                    "dstfile": apkfile,
                    "srcfile": self.apk.filename,
                    "cert" : CERT,
                    "tmpdir": tmpdir
            }
            system(script)

    def generateBooleans(self):
        bools = {}
        for resid, resname in self.resources.get('bool', []):
            for config, value in self.res.get_resolved_res_configs(resid):
                version = config._get_tuple()[5]
                if version == 0:
                    version = ''
                else:
                    version = "-v%d"%version
                boolres = bools.setdefault(version, {})
                boolres[resname.split('/')[1]] = value

        for version in bools:
            boolres = bools[version]
            values = "values" + version

            resdir = os.path.join(self.outdir, "res", values)
            resfile = os.path.join(resdir, "bools.xml")
            resdom = xml.dom.minidom.getDOMImplementation().createDocument(None, "resources", None)
            has_value = False
            for res in boolres:
                v = boolres[res]
                if not v.strip(): continue
                text = resdom.createElement("bool")
                text.setAttribute("name", res)
                text.appendChild(resdom.createTextNode(v))
                resdom.documentElement.appendChild(text)
                has_value = True
            if has_value:
                if not os.path.isdir(resdir):
                    os.makedirs(resdir)

                with open(resfile, "w") as fp:
                    fp.write(resdom.toxml())


    def generateStrings(self):
        strings = {}
        for resid, resname in self.resources.get('string', []):
            for config, value in self.res.get_resolved_res_configs(resid):
                lang = config.get_language()
                region = config.get_country()
                if region == '\x00\x00':
                    locale = lang
                else:
                    locale = '-r'.join((lang, region))
                stringres = strings.setdefault(locale, {})
                stringres[resname.split('/')[1]] = value

        for locale in strings:
            stringres = strings[locale]
            if locale == '\x00\x00':
                values = 'values'
            else:
                values = 'values-' + locale

            resdir = os.path.join(self.outdir, "res", values)
            resfile = os.path.join(resdir, "strings.xml")
            resdom = xml.dom.minidom.getDOMImplementation().createDocument(None, "resources", None)
            has_value = False
            for res in stringres:
                v = stringres[res]
                if not v.strip(): continue
                v = v.replace("'", "\\'")
                v = v.replace('"', '\\"')
                text = resdom.createElement("string")
                text.setAttribute("name", res)
                text.appendChild(resdom.createTextNode(v))
                resdom.documentElement.appendChild(text)
                has_value = True
            if has_value:
                if not os.path.isdir(resdir):
                    os.makedirs(resdir)

                with open(resfile, "w") as fp:
                    fp.write(resdom.toxml())

    def generateDrawables(self):
        drawables = set()
        for restype in 'drawable', 'mipmap':
            resources = self.resources.get(restype, [])
            drawables.update(resources)
        for rid, resname in drawables:
            for config, filename in self.res.get_resolved_res_configs(rid):
                newfile = os.path.join(self.outdir, filename)
                dname = os.path.dirname(newfile)
                if not os.path.isdir(dname):
                    os.makedirs(dname)
                if not newfile.endswith(".xml"):
                    with open(newfile, "w") as fp:
                        buf = self.apk.get_file(filename)
                        fp.write(buf)
                else:
                    pass
                    #doc = xml.dom.minidom.parseString(apk.AXMLPrinter(buf).get_buff())
                    #fp.write(doc.toxml())


    def generateAndroidMk(self):
        androidmk = os.path.join(self.outdir, "Android.mk")
        with open(androidmk, "w") as fp:
            fp.write(ANDROID_MK_TEMPLATE%{
                "pkgname":self.apkname,
                "outdir":APKOUTDIR,
            })
        return androidmk

    def generateAndroidManifest(self):
        axml = self.axml
        manifest = axml.getElementsByTagName("manifest")[0]
        app = axml.getElementsByTagName("application")[0]

        d = {
            'pkg': self.pkgname,
            'stub': STUB_ACTIVITY,
        }

        out = xml.dom.minidom.parseString(MANIFEST_TEMPLATE%d)
        outmanifest = out.getElementsByTagName("manifest")[0]
        outapp = out.getElementsByTagName("application")[0]

        self.prepare_resources(
                copy_attribute(manifest, outmanifest, "android:sharedUserId",
                    self.res))
        for key in ("android:icon", "android:label"):
            self.prepare_resources(
                copy_attribute(app, outapp, key, self.res))

        activities = axml.getElementsByTagName("activity")
        aliases = axml.getElementsByTagName("activity-alias")

        for activity in activities + aliases:
            resources = create_alias(out, activity, self.res)
            for res in resources:
                self.prepare_resources(res)

        with open(os.path.join(self.outdir, "AndroidManifest.xml"), "w") as fp:
            fp.write(out.toxml())


if __name__ == '__main__':
    for F in sys.argv[1:]:
        stub = GenerateStub(F)
        if not stub.getPkgName() in apks:
            continue
        stub.build()

