# -*- coding: utf-8 -*-

import binascii
import glob
import hashlib
import os
import unittest
from unittest.mock import MagicMock, patch

from androguard.core import apk, axml
from androguard.core.analysis.analysis import Analysis
from androguard.core.apk import APK
from androguard.core.axml import AXMLPrinter
from androguard.core.dex import DEX
from androguard.misc import AnalyzeAPK

test_dir = os.path.dirname(os.path.abspath(__file__))


class APKTest(unittest.TestCase):
    def testAPK(self):
        for f in glob.glob(os.path.join(test_dir, 'data/APK/*.apk')):
            with open(f, "rb") as fd:
                a = apk.APK(fd.read(), True)
                self.assertTrue(a)

    def testAPKWrapper(self):
        a, d, dx = AnalyzeAPK(
            os.path.join(test_dir, 'data/APK/TestActivity.apk')
        )

        self.assertIsInstance(a, APK)
        self.assertIsInstance(d[0], DEX)
        self.assertIsInstance(dx, Analysis)

        self.assertEqual(a.get_signature_name(), "META-INF/CERT.RSA")
        self.assertEqual(a.get_signature_names(), ["META-INF/CERT.RSA"])

        self.assertIsNotNone(a.get_certificate(a.get_signature_name()))

    def testAPKWrapperRaw(self):
        with open(
            os.path.join(test_dir, 'data/APK/TestActivity.apk'), 'rb'
        ) as file_obj:
            file_contents = file_obj.read()
        a, d, dx = AnalyzeAPK(file_contents, raw=True)
        self.assertIsInstance(a, APK)
        self.assertIsInstance(d[0], DEX)
        self.assertIsInstance(dx, Analysis)

        self.assertEqual(a.get_signature_name(), "META-INF/CERT.RSA")
        self.assertEqual(a.get_signature_names(), ["META-INF/CERT.RSA"])

        self.assertIsNotNone(a.get_certificate(a.get_signature_name()))

    def testMultiDexAPK(self):
        a, d, dx = AnalyzeAPK(
            os.path.join(test_dir, 'data/APK/app-prod-debug.apk')
        )

        self.assertIsInstance(a, APK)
        self.assertIsInstance(d[0], DEX)
        self.assertIsInstance(d[1], DEX)
        self.assertIsInstance(dx, Analysis)

    def testAPKCert(self):
        """
        Test if certificates are correctly unpacked from the SignatureBlock files
        :return:
        """
        a = APK(
            os.path.join(test_dir, 'data/APK/TestActivity.apk'),
            skip_analysis=True,
        )
        cert = a.get_certificate_der(a.get_signature_name())
        expected = (
            "308201E53082014EA00302010202045114FECF300D06092A864886F70D010105"
            "05003037310B30090603550406130255533110300E060355040A1307416E6472"
            "6F6964311630140603550403130D416E64726F6964204465627567301E170D31"
            "33303230383133333430375A170D3433303230313133333430375A3037310B30"
            "090603550406130255533110300E060355040A1307416E64726F696431163014"
            "0603550403130D416E64726F696420446562756730819F300D06092A864886F7"
            "0D010101050003818D00308189028181009903975EC93F0F3CCB54BD1A415ECF"
            "3505993715B8B9787F321104ACC7397D186F01201341BCC5771BB28695318E00"
            "6E47C888D3C7EE9D952FF04DF06EDAB1B511F51AACDCD02E0ECF5AA7EC6B51BA"
            "08C601074CF2DA579BD35054E4F77BAAAAF0AA67C33C1F1C3EEE05B5862952C0"
            "888D39179C0EDD785BA4F47FB7DF5D5F030203010001300D06092A864886F70D"
            "0101050500038181006B571D685D41E77744F5ED20822AE1A14199811CE649BB"
            "B29248EB2F3CC7FB70F184C2A3D17C4F86B884FCA57EEB289ECB5964A1DDBCBD"
            "FCFC60C6B7A33D189927845067C76ED29B42D7F2C7F6E2389A4BC009C01041A3"
            "6E666D76D1D66467416E68659D731DC7328CB4C2E989CF59BB6D2D2756FDE7F2"
            "B3FB733EBB4C00FD3B"
        )

        self.assertEqual(
            binascii.hexlify(cert).decode("ascii").upper(), expected
        )

    def testAPKCertFingerprint(self):
        """
        Test if certificates are correctly unpacked from the SignatureBlock files
        Check if fingerprints matches
        :return:
        """

        from hashlib import md5, sha1, sha256

        a = APK(
            os.path.join(test_dir, 'data/APK/TestActivity.apk'),
            skip_analysis=True,
        )
        # this one is not signed v2, it is v1 only
        self.assertTrue(a.is_signed_v1())
        self.assertFalse(a.is_signed_v2())
        self.assertTrue(a.is_signed())
        self.assertEqual(a.get_certificates_der_v2(), [])
        self.assertEqual(a.get_certificates_v2(), [])

        self.assertEqual(a.get_signature_name(), "META-INF/CERT.RSA")
        self.assertEqual(a.get_signature_names(), ["META-INF/CERT.RSA"])

        cert = a.get_certificate(a.get_signature_name())
        cert_der = a.get_certificate_der(a.get_signature_name())

        # Keytool are the hashes collected by keytool -printcert -file CERT.RSA
        for h2, keytool in [
            (md5, "99:FF:FC:37:D3:64:87:DD:BA:AB:F1:7F:94:59:89:B5"),
            (
                sha1,
                "1E:0B:E4:01:F9:34:60:E0:8D:89:A3:EF:6E:27:25:55:6B:E1:D1:6B",
            ),
            (
                sha256,
                "6F:5C:31:60:8F:1F:9E:28:5E:B6:34:3C:7C:8A:F0:7D:E8:1C:1F:B2:14:8B:53:49:BE:C9:06:44:41:44:57:6D",
            ),
        ]:
            x = h2()
            x.update(cert_der)
            hash_hashlib = x.hexdigest()

            self.assertEqual(
                hash_hashlib.lower(), keytool.replace(":", "").lower()
            )

    def testAPKv2Signature(self):
        a = APK(
            os.path.join(test_dir, 'data/APK/TestActivity_signed_both.apk')
        )

        self.assertTrue(a.is_signed_v1())
        self.assertTrue(a.is_signed_v2())
        self.assertTrue(a.is_signed())

        # Signing name is maximal 8 chars...
        self.assertEqual(a.get_signature_name(), "META-INF/ANDROGUA.RSA")
        self.assertEqual(len(a.get_certificates_der_v2()), 1)
        # As we signed with the same certificate, both methods should return the
        # same content
        self.assertEqual(
            a.get_certificate_der(a.get_signature_name()),
            a.get_certificates_der_v2()[0],
        )

        from asn1crypto import x509

        self.assertIsInstance(a.get_certificates_v2()[0], x509.Certificate)

        # Test if the certificate is also the same as on disk
        with open(
            os.path.join(test_dir, 'data/APK/certificate.der'), "rb"
        ) as f:
            cert = f.read()
        cert_der_v1 = a.get_certificate_der(a.get_signature_name())
        cert_der_v2 = a.get_certificates_der_v2()[0]

        for fun in [hashlib.md5, hashlib.sha1, hashlib.sha256, hashlib.sha512]:
            h1 = fun(cert).hexdigest()
            h2 = fun(cert_der_v1).hexdigest()
            h3 = fun(cert_der_v2).hexdigest()

            self.assertEqual(h1, h2)
            self.assertEqual(h1, h3)
            self.assertEqual(h2, h3)

    def testApksignAPKs(self):
        # These APKs are from the apksign testcases and cover
        # all different signature algorithms as well as some error cases
        from asn1crypto import pem, x509

        root = os.path.join(test_dir, 'data/APK/apksig')

        # Correct values generated with openssl:
        # In the apksig repo:src/test/resources/com/android/apksig
        # for f in *.pem; do openssl x509 -in $f -noout -sha256 -fingerprint; done
        certfp = {
            'dsa-1024.x509.pem': 'fee7c19ff9bfb4197b3727b9fd92d95406b1bd96db99ea642f5faac019a389d7',
            'dsa-2048.x509.pem': '97cce0bab292c2d5afb9de90e1810b41a5d25c006a10d10982896aa12ab35a9e',
            'dsa-3072.x509.pem': '966a4537058d24098ea213f12d4b24e37ff5a1d8f68deb8a753374881f23e474',
            'ec-p256.x509.pem': '6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599',
            'ec-p384.x509.pem': '5e7777ada7ee7ce8f9c4d1b07094876e5604617b7988b4c5d5b764a23431afbe',
            'ec-p521.x509.pem': '69b50381d98bebcd27df6d7df8af8c8b38d0e51e9168a95ab992d1a9da6082da',
            'rsa-1024_2.x509.pem': 'eba3685e799f59804684abebf0363e14ccb1c213e2b954a22669714ed97f61e9',
            'rsa-1024.x509.pem': 'bc5e64eab1c4b5137c0fbc5ed05850b3a148d1c41775cffa4d96eea90bdd0eb8',
            'rsa-16384.x509.pem': 'f3c6b37909f6df310652fbd7c55ec27d3079dcf695dc6e75e22ba7c4e1c95601',
            'rsa-2048_2.x509.pem': '681b0e56a796350c08647352a4db800cc44b2adc8f4c72fa350bd05d4d50264d',
            'rsa-2048_3.x509.pem': 'bb77a72efc60e66501ab75953af735874f82cfe52a70d035186a01b3482180f3',
            'rsa-2048.x509.pem': 'fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8',
            'rsa-3072.x509.pem': '483934461229a780010bc07cd6eeb0b67025fc4fe255757abbf5c3f2ed249e89',
            'rsa-4096.x509.pem': '6a46158f87753395a807edcc7640ac99c9125f6b6e025bdbf461ff281e64e685',
            'rsa-8192.x509.pem': '060d0a24fea9b60d857225873f78838e081795f7ef2d1ea401262bbd75a58234',
        }

        will_not_validate_correctly = [
            "targetSandboxVersion-2.apk",
            "targetSandboxVersion-2.apk",
            "v1-only-with-cr-in-entry-name.apk",
            "v1-only-with-lf-in-entry-name.apk",
            "v1-only-with-nul-in-entry-name.apk",
            "v1-only-with-rsa-1024-cert-not-der2.apk",
            "v2-only-cert-and-public-key-mismatch.apk",
            "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk",
            "debuggable-boolean.apk",
            "debuggable-resource.apk",
            "mismatched-compression-method.apk",
        ]

        v1_only_signed_attrs_fail = [
            "v1-only-with-signed-attrs-missing-content-type.apk",
            "v1-only-with-signed-attrs-missing-digest.apk",
            "v1-only-with-signed-attrs-multiple-good-digests.apk",
            "v1-only-with-signed-attrs-signerInfo1-missing-content-type-signerInfo2-good.apk",
            "v1-only-with-signed-attrs-signerInfo1-missing-digest-signerInfo2-good.apk",
            "v1-only-with-signed-attrs-signerInfo1-multiple-good-digests-signerInfo2-good.apk",
            "v1-only-with-signed-attrs-signerInfo1-wrong-content-type-signerInfo2-good.apk",
            "v1-only-with-signed-attrs-signerInfo1-wrong-digest-signerInfo2-good.apk",
            "v1-only-with-signed-attrs-signerInfo1-wrong-signature-signerInfo2-good.apk",
            "v1-only-with-signed-attrs-wrong-content-type.apk",
            "v1-only-with-signed-attrs-wrong-digest.apk",
            "v1-only-with-signed-attrs-wrong-signature.apk",
        ]

        # Collect possible hashes for certificates
        # Unfortunately, not all certificates are supplied...
        for apath in os.listdir(root):
            if apath in certfp:
                with open(os.path.join(root, apath), "rb") as fp:
                    cert = x509.Certificate.load(pem.unarmor(fp.read())[2])
                    h = cert.sha256_fingerprint.replace(" ", "").lower()
                    self.assertEqual(h, certfp[apath])
                    self.assertIn(h, certfp.values())

        for apath in os.listdir(root):
            if apath.endswith(".apk"):
                if apath in will_not_validate_correctly:
                    # These APKs are faulty (by design) and will return a not correct fingerprint.
                    # TODO: we need to check if we can prevent such errors...
                    continue

                a = APK(os.path.join(root, apath))

                self.assertIsInstance(a, APK)

                # Test if the correct method returns True, while others return
                # False
                m_tests = {
                    '1': a.is_signed_v1,
                    '2': a.is_signed_v2,
                    '3': a.is_signed_v3,
                }

                # These APKs will raise an error
                excluded = [
                    "v1v2v3-with-rsa-2048-lineage-3-signers-no-sig-block.apk",
                    "v2-only-apk-sig-block-size-mismatch.apk",
                    "v2-only-empty.apk",
                    "v2-only-wrong-apk-sig-block-magic.apk",
                    "v2-stripped.apk",
                    "v2-stripped-with-ignorable-signing-schemes.apk",
                    "v2v3-signed-v3-block-stripped.apk",
                    "v3-only-empty.apk",
                    "v3-only-with-ecdsa-sha512-p384-wrong-apk-sig-block-magic.apk",
                    "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk",
                    "v3-stripped.apk",
                ]
                if apath[0] == "v" and apath not in excluded:
                    methods = apath.split("-", 1)[0].split("v")[1:]
                    for m, f in m_tests.items():
                        if m in methods:
                            self.assertTrue(f(), f"expected 'is_signed_v{m}' to be 'True' on '{apath}'")
                        else:
                            self.assertFalse(f(), f"expected 'is_signed_v{m}' to be 'False' on '{apath}'")

                # Special error cases
                if apath == "v2-only-apk-sig-block-size-mismatch.apk":
                    with self.assertRaises(apk.BrokenAPKError):
                        a.is_signed_v2()
                    continue
                elif apath == "v2-only-empty.apk":
                    with self.assertRaises(apk.BrokenAPKError):
                        a.is_signed_v2()
                    continue
                elif (
                    apath
                    == "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk"
                ):
                    with self.assertRaises(apk.BrokenAPKError):
                        a.is_signed_v3()
                    continue

                if a.is_signed_v1():
                    if apath == "v1-only-with-rsa-1024-cert-not-der.apk":
                        for sig in a.get_signature_names():
                            c = a.get_certificate(sig)
                            h = c.sha256_fingerprint.replace(" ", "").lower()
                            self.assertNotIn(h, certfp.values())
                            # print([apath, h]) # I do not know, why put this file?
                            der = a.get_certificate_der(sig)
                            self.assertEqual(
                                hashlib.sha256(der).hexdigest(), h
                            )
                    elif apath in v1_only_signed_attrs_fail:
                        for sig in a.get_signature_names():
                            c = a.get_certificate_der(sig)
                            self.assertEqual(c, None)
                    else:
                        for sig in a.get_signature_names():
                            c = a.get_certificate(sig)
                            h = c.sha256_fingerprint.replace(" ", "").lower()
                            self.assertIn(h, certfp.values())

                            # Check that we get the same signature if we take the DER
                            der = a.get_certificate_der(sig)
                            self.assertEqual(
                                hashlib.sha256(der).hexdigest(), h
                            )

                if a.is_signed_v2():
                    if apath == "weird-compression-method.apk":
                        with self.assertRaises(NotImplementedError):
                            a.get_certificates_der_v2()
                    elif (
                        apath
                        == "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk"
                    ):
                        # FIXME
                        # Not sure what this one should do... but the certificate fingerprint is weird
                        # as the hash over the DER is not the same when using the certificate
                        continue
                    else:
                        for c in a.get_certificates_der_v2():
                            cert = x509.Certificate.load(c)
                            h = cert.sha256_fingerprint.replace(
                                " ", ""
                            ).lower()
                            self.assertIn(h, certfp.values())
                            # Check that we get the same signature if we take the DER
                            self.assertEqual(hashlib.sha256(c).hexdigest(), h)

                if a.is_signed_v3():
                    if apath == "weird-compression-method.apk":
                        with self.assertRaises(NotImplementedError):
                            a.get_certificates_der_v3()
                    elif (
                        apath
                        == "v3-only-with-rsa-pkcs1-sha256-3072-sig-does-not-verify.apk"
                        or apath == "v3-only-cert-and-public-key-mismatch.apk"
                    ):
                        cert = x509.Certificate.load(
                            a.get_certificates_der_v3()[0]
                        )
                        h = cert.sha256_fingerprint.replace(" ", "").lower()
                        self.assertNotIn(h, certfp.values())
                    else:
                        for c in a.get_certificates_der_v3():
                            cert = x509.Certificate.load(c)
                            h = cert.sha256_fingerprint.replace(
                                " ", ""
                            ).lower()
                            self.assertIn(h, certfp.values())
                            # Check that we get the same signature if we take the DER
                            self.assertEqual(hashlib.sha256(c).hexdigest(), h)

    def testMultipleCertsReturnTheCorrect(self):
        sha256_fingerprint = (
            '01e1999710a82c2749b4d50c445dc85d670b6136089d0a766a73827c82a1eac9'
        )
        a = APK(os.path.join(test_dir, 'data/APK/CertChain.apk'))
        sig = a.get_signature_names()[0]
        c = a.get_certificate(sig)
        self.assertEqual(
            hashlib.sha256(c.dump()).hexdigest(), sha256_fingerprint
        )

    def testAPKWrapperUnsigned(self):
        a, d, dx = AnalyzeAPK(
            os.path.join(test_dir, 'data/APK/TestActivity_unsigned.apk')
        )
        self.assertIsInstance(a, APK)
        self.assertIsInstance(d[0], DEX)
        self.assertIsInstance(dx, Analysis)

        self.assertIsNone(a.get_signature_name())
        self.assertEqual(a.get_signature_names(), [])

    def testAPKManifest(self):
        a = APK(
            os.path.join(test_dir, 'data/APK/TestActivity.apk'), testzip=True
        )
        self.assertEqual(a.get_app_name(), "TestsAndroguardApplication")
        self.assertEqual(a.get_app_icon(), "res/drawable-hdpi/icon.png")
        self.assertEqual(
            a.get_app_icon(max_dpi=120), "res/drawable-ldpi/icon.png"
        )
        self.assertEqual(
            a.get_app_icon(max_dpi=160), "res/drawable-mdpi/icon.png"
        )
        self.assertEqual(
            a.get_app_icon(max_dpi=240), "res/drawable-hdpi/icon.png"
        )
        self.assertIsNone(a.get_app_icon(max_dpi=1))
        self.assertEqual(
            a.get_main_activity(), "tests.androguard.TestActivity"
        )
        self.assertEqual(a.get_package(), "tests.androguard")
        self.assertEqual(a.get_androidversion_code(), '1')
        self.assertEqual(a.get_androidversion_name(), "1.0")
        self.assertEqual(a.get_min_sdk_version(), "9")
        self.assertEqual(a.get_target_sdk_version(), "16")
        self.assertIsNone(a.get_max_sdk_version())
        self.assertEqual(a.get_permissions(), [])
        self.assertEqual(a.get_declared_permissions(), [])
        self.assertTrue(a.is_valid_APK())

    def testAPKPermissions(self):
        a = APK(
            os.path.join(test_dir, 'data/APK/a2dp.Vol_137.apk'), testzip=True
        )

        self.assertEqual(a.get_package(), "a2dp.Vol")
        self.assertListEqual(
            sorted(a.get_permissions()),
            sorted(
                [
                    "android.permission.RECEIVE_BOOT_COMPLETED",
                    "android.permission.CHANGE_WIFI_STATE",
                    "android.permission.ACCESS_WIFI_STATE",
                    "android.permission.KILL_BACKGROUND_PROCESSES",
                    "android.permission.BLUETOOTH",
                    "android.permission.BLUETOOTH_ADMIN",
                    "com.android.launcher.permission.READ_SETTINGS",
                    "android.permission.RECEIVE_SMS",
                    "android.permission.MODIFY_AUDIO_SETTINGS",
                    "android.permission.READ_CONTACTS",
                    "android.permission.ACCESS_COARSE_LOCATION",
                    "android.permission.ACCESS_FINE_LOCATION",
                    "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS",
                    "android.permission.WRITE_EXTERNAL_STORAGE",
                    "android.permission.READ_PHONE_STATE",
                    "android.permission.BROADCAST_STICKY",
                    "android.permission.GET_ACCOUNTS",
                ]
            ),
        )

    def testAPKActivitiesAreString(self):
        a = APK(
            os.path.join(test_dir, 'data/APK/a2dp.Vol_137.apk'), testzip=True
        )
        activities = a.get_activities()
        self.assertTrue(
            isinstance(activities[0], str), 'activities[0] is not of type str'
        )

    def testAPKIntentFilters(self):
        from androguard.util import set_log

        set_log("ERROR")
        a = APK(
            os.path.join(test_dir, 'data/APK/a2dp.Vol_137.apk'), testzip=True
        )
        activities = a.get_activities()
        receivers = a.get_receivers()
        services = a.get_services()
        filter_list = []
        for i in activities:
            filters = a.get_intent_filters("activity", i)
            if len(filters) > 0:
                filter_list.append(filters)
        self.assertEqual(
            [
                {
                    'action': ['android.intent.action.MAIN'],
                    'category': ['android.intent.category.LAUNCHER'],
                }
            ],
            filter_list,
        )
        filter_list = []
        for i in receivers:
            filters = a.get_intent_filters("receiver", i)
            if len(filters) > 0:
                filter_list.append(filters)
        for expected in [
            {
                'action': [
                    'android.intent.action.BOOT_COMPLETED',
                    'android.intent.action.MY_PACKAGE_REPLACED',
                ],
                'category': ['android.intent.category.HOME'],
            },
            {'action': ['android.appwidget.action.APPWIDGET_UPDATE']},
        ]:
            assert expected in filter_list
        filter_list = []
        for i in services:
            filters = a.get_intent_filters("service", i)
            if len(filters) > 0:
                filter_list.append(filters)
        self.assertEqual(
            filter_list,
            [
                {
                    'action': [
                        'android.service.notification.NotificationListenerService'
                    ]
                }
            ],
        )

        a = APK(
            os.path.join(test_dir, 'data/APK/com.test.intent_filter.apk'),
            testzip=True,
        )

        activities = a.get_activities()
        receivers = a.get_receivers()
        services = a.get_services()
        filter_list = []
        for i in activities:
            filters = a.get_intent_filters("activity", i)
            if len(filters) > 0:
                filter_list.append(filters)
        for expected in [
            {
                'action': ['android.intent.action.VIEW'],
                'category': [
                    'android.intent.category.APP_BROWSER',
                    'android.intent.category.DEFAULT',
                    'android.intent.category.BROWSABLE',
                ],
                'data': [
                    {
                        'scheme': 'testscheme',
                        'host': 'testhost',
                        'port': '0301',
                        'path': '/testpath',
                        'pathPattern': 'testpattern',
                        'mimeType': 'text/html',
                    }
                ],
            },
            {
                'action': ['android.intent.action.MAIN'],
                'category': ['android.intent.category.LAUNCHER'],
            },
        ]:
            assert expected in filter_list
        filter_list = []
        for i in receivers:
            filters = a.get_intent_filters("receiver", i)
            if len(filters) > 0:
                filter_list.append(filters)
        self.assertEqual(
            filter_list,
            [
                {
                    'action': ['android.intent.action.VIEW'],
                    'category': [
                        'android.intent.category.DEFAULT',
                        'android.intent.category.BROWSABLE',
                    ],
                    'data': [
                        {
                            'scheme': 'testhost',
                            'host': 'testscheme',
                            'port': '0301',
                            'path': '/testpath',
                            'pathPattern': 'testpattern',
                            'mimeType': 'text/html',
                        }
                    ],
                }
            ],
        )
        filter_list = []
        for i in services:
            filters = a.get_intent_filters("service", i)
            if len(filters) > 0:
                filter_list.append(filters)
        self.assertEqual(
            filter_list,
            [
                {
                    'action': ['android.intent.action.RESPOND_VIA_MESSAGE'],
                    'data': [
                        {
                            'scheme': 'testhost',
                            'host': 'testscheme',
                            'port': '0301',
                            'path': '/testpath',
                            'pathPattern': 'testpattern',
                            'mimeType': 'text/html',
                        },
                        {
                            'scheme': 'testscheme2',
                            'host': 'testhost2',
                            'port': '0301',
                            'path': '/testpath2',
                            'pathPattern': 'testpattern2',
                            'mimeType': 'image/png',
                        },
                    ],
                }
            ],
        )

    def testEffectiveTargetSdkVersion(self):

        a = APK(os.path.join(test_dir, 'data/APK/app-prod-debug.apk'))
        self.assertEqual(27, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/Invalid.apk'))
        self.assertEqual(15, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/TC-debug.apk'))
        self.assertEqual(1, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/TCDiff-debug.apk'))
        self.assertEqual(1, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/TestActivity.apk'))
        self.assertEqual(16, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/TestActivity_unsigned.apk'))
        self.assertEqual(16, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/Test-debug.apk'))
        self.assertEqual(1, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/Test-debug-unaligned.apk'))
        self.assertEqual(1, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/a2dp.Vol_137.apk'))
        self.assertEqual(25, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/hello-world.apk'))
        self.assertEqual(25, a.get_effective_target_sdk_version())

        a = APK(
            os.path.join(
                test_dir, 'data/APK/duplicate.permisssions_9999999.apk'
            )
        )
        self.assertEqual(27, a.get_effective_target_sdk_version())

        a = APK(os.path.join(test_dir, 'data/APK/com.politedroid_4.apk'))
        self.assertEqual(3, a.get_effective_target_sdk_version())

    def testUsesImpliedPermissions(self):

        a = APK(os.path.join(test_dir, 'data/APK/app-prod-debug.apk'))
        self.assertEqual(
            [
                ['android.permission.READ_EXTERNAL_STORAGE', None],
            ],
            a.get_uses_implied_permission_list(),
        )
        a = APK(os.path.join(test_dir, 'data/APK/Invalid.apk'))
        self.assertEqual([], a.get_uses_implied_permission_list())
        a = APK(os.path.join(test_dir, 'data/APK/TC-debug.apk'))
        self.assertEqual(
            [
                ['android.permission.WRITE_EXTERNAL_STORAGE', None],
                ['android.permission.READ_PHONE_STATE', None],
                ['android.permission.READ_EXTERNAL_STORAGE', None],
            ],
            a.get_uses_implied_permission_list(),
        )
        a = APK(os.path.join(test_dir, 'data/APK/TCDiff-debug.apk'))
        self.assertEqual(
            [
                ['android.permission.WRITE_EXTERNAL_STORAGE', None],
                ['android.permission.READ_PHONE_STATE', None],
                ['android.permission.READ_EXTERNAL_STORAGE', None],
            ],
            a.get_uses_implied_permission_list(),
        )
        a = APK(os.path.join(test_dir, 'data/APK/TestActivity.apk'))
        self.assertEqual([], a.get_uses_implied_permission_list())
        a = APK(os.path.join(test_dir, 'data/APK/TestActivity_unsigned.apk'))
        self.assertEqual([], a.get_uses_implied_permission_list())
        a = APK(os.path.join(test_dir, 'data/APK/Test-debug.apk'))
        self.assertEqual(
            [
                ['android.permission.WRITE_EXTERNAL_STORAGE', None],
                ['android.permission.READ_PHONE_STATE', None],
                ['android.permission.READ_EXTERNAL_STORAGE', None],
            ],
            a.get_uses_implied_permission_list(),
        )
        a = APK(os.path.join(test_dir, 'data/APK/Test-debug-unaligned.apk'))
        self.assertEqual(
            [
                ['android.permission.WRITE_EXTERNAL_STORAGE', None],
                ['android.permission.READ_PHONE_STATE', None],
                ['android.permission.READ_EXTERNAL_STORAGE', None],
            ],
            a.get_uses_implied_permission_list(),
        )
        a = APK(os.path.join(test_dir, 'data/APK/a2dp.Vol_137.apk'))
        self.assertEqual(
            [
                ['android.permission.READ_EXTERNAL_STORAGE', None],
            ],
            a.get_uses_implied_permission_list(),
        )
        a = APK(os.path.join(test_dir, 'data/APK/com.politedroid_4.apk'))
        self.assertEqual(
            [
                ['android.permission.WRITE_EXTERNAL_STORAGE', None],
                ['android.permission.READ_PHONE_STATE', None],
                ['android.permission.READ_EXTERNAL_STORAGE', None],
            ],
            a.get_uses_implied_permission_list(),
        )
        a = APK(
            os.path.join(
                test_dir, 'data/APK/duplicate.permisssions_9999999.apk'
            )
        )
        self.assertEqual(
            [
                ['android.permission.READ_EXTERNAL_STORAGE', 18],
            ],
            a.get_uses_implied_permission_list(),
        )
        a = APK(os.path.join(test_dir, 'data/APK/hello-world.apk'))
        self.assertEqual([], a.get_uses_implied_permission_list())

        a = APK(
            os.path.join(
                test_dir,
                'data/APK/urzip-πÇÇπÇÇ现代汉语通用字-български-عربي1234.apk',
            )
        )
        self.assertEqual([], a.get_uses_implied_permission_list())

    def testNewZipWithoutModification(self):
        a = APK(
            os.path.join(test_dir, 'data/APK/a2dp.Vol_137.apk'), testzip=True
        )
        with patch('zipfile.ZipFile') as zipFile:
            mockZip = MagicMock()
            zipFile.return_value = mockZip
            a.new_zip("testout.apk")
            self.assertEqual(mockZip.writestr.call_count, 48)
            self.assertTrue(mockZip.close.called)

    def testNewZipWithDeletedFile(self):
        a = APK(
            os.path.join(test_dir, 'data/APK/a2dp.Vol_137.apk'), testzip=True
        )
        with patch('zipfile.ZipFile') as zipFile:
            mockZip = MagicMock()
            zipFile.return_value = mockZip
            a.new_zip("testout.apk", deleted_files="res/menu/menu.xml")
            self.assertEqual(mockZip.writestr.call_count, 47)
            self.assertTrue(mockZip.close.called)

    def testNewZipWithNewFile(self):
        a = APK(
            os.path.join(test_dir, 'data/APK/a2dp.Vol_137.apk'), testzip=True
        )
        with patch('zipfile.ZipFile') as zipFile:
            mockZip = MagicMock()
            zipFile.return_value = mockZip
            a.new_zip(
                "testout.apk", new_files={'res/menu/menu.xml': 'content'}
            )
            self.assertEqual(mockZip.writestr.call_count, 48)
            self.assertTrue(mockZip.close.called)

    def testFeatures(self):
        a = APK(
            os.path.join(
                test_dir, 'data/APK/com.example.android.tvleanback.apk'
            )
        )
        self.assertListEqual(
            sorted(list(a.get_features())),
            [
                "android.hardware.microphone",
                "android.hardware.touchscreen",
                "android.software.leanback",
            ],
        )
        self.assertTrue(a.is_androidtv())
        self.assertFalse(a.is_wearable())
        self.assertTrue(a.is_leanback())

        # Second Demo App
        a = APK(
            os.path.join(
                test_dir,
                'data/APK/com.example.android.wearable.wear.weardrawers.apk',
            )
        )
        self.assertListEqual(
            list(a.get_features()), ["android.hardware.type.watch"]
        )
        self.assertTrue(a.is_wearable())
        self.assertFalse(a.is_leanback())
        self.assertFalse(a.is_androidtv())
        self.assertListEqual(
            list(a.get_libraries()), ["com.google.android.wearable"]
        )

    def testAdaptiveIcon(self):
        # See https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive.html
        a = APK(
            os.path.join(
                test_dir, 'data/APK/com.android.example.text.styling.apk'
            )
        )

        self.assertEqual(
            a.get_app_icon(), "res/mipmap-anydpi-v26/ic_launcher.xml"
        )
        x = AXMLPrinter(a.get_file(a.get_app_icon())).get_xml().decode("UTF-8")
        self.assertIn("adaptive-icon", x)

        # * ldpi (low) ~120dpi
        # * mdpi (medium) ~160dpi
        # * hdpi (high) ~240dpi
        # * xhdpi (extra-high) ~320dpi
        # * xxhdpi (extra-extra-high) ~480dpi
        # * xxxhdpi (extra-extra-extra-high) ~640dpi
        self.assertIsNone(a.get_app_icon(max_dpi=120))  # No LDPI icon
        self.assertIn("mdpi", a.get_app_icon(max_dpi=160))
        self.assertIn("hdpi", a.get_app_icon(max_dpi=240))
        self.assertIn("xhdpi", a.get_app_icon(max_dpi=320))
        self.assertIn("xxhdpi", a.get_app_icon(max_dpi=480))
        self.assertIn("xxxhdpi", a.get_app_icon(max_dpi=640))

        self.assertIn(".png", a.get_app_icon(max_dpi=65533))
        self.assertIn(".xml", a.get_app_icon(max_dpi=65534))

    def testPartialSignature(self):
        a = APK(
            os.path.join(test_dir, 'data/APK/partialsignature.apk'),
            skip_analysis=True,
        )

        self.assertIn("META-INF/CERT.RSA", a.get_files())
        self.assertIn("META-INF/6AD89F48.RSA", a.get_files())

        self.assertNotIn("META-INF/CERT.RSA", a.get_signature_names())
        self.assertIn("META-INF/6AD89F48.RSA", a.get_signature_names())

    def testFrameworkResAPK(self):
        a = APK(
            os.path.join(
                test_dir, 'data/APK/lineageos_nexus5_framework-res.apk'
            )
        )

        self.assertEqual(a.get_app_name(), 'Android System')
        self.assertEqual(a.get_package(), 'android')

    def testPermissionLoading(self):
        """Test if fallbacks for permission lists are working"""
        import re

        from androguard.core.androconf import (
            CONF,
            InvalidResourceError,
            load_api_specific_resource_module,
        )
        from androguard.core.api_specific_resources import load_permissions

        aosp_permissions = os.path.join(
            test_dir,
            '../androguard/core/api_specific_resources/aosp_permissions',
        )
        levels = filter(
            lambda x: re.match(r'^permissions_\d+\.json$', x),
            os.listdir(aosp_permissions),
        )
        levels = list(map(lambda x: int(x[:-5].split('_')[1]), levels))

        min_level = min(levels)
        max_level = max(levels)

        self.assertGreater(min_level, 0)
        self.assertGreater(max_level, 0)

        self.assertNotEqual(load_permissions(min_level), {})
        self.assertNotEqual(load_permissions(min_level, 'groups'), {})
        self.assertNotEqual(load_permissions(max_level), {})
        self.assertNotEqual(load_permissions(max_level, 'groups'), {})

        self.assertNotEqual(load_permissions(max_level - 1), {})
        self.assertNotEqual(load_permissions(max_level - 1, 'groups'), {})

        self.assertNotEqual(load_permissions(min_level + 1), {})
        self.assertNotEqual(load_permissions(min_level + 1, 'groups'), {})

        self.assertEqual(
            load_permissions(min_level - 1), load_permissions(min_level)
        )
        self.assertEqual(
            load_permissions(max_level + 1), load_permissions(max_level)
        )

        self.assertEqual(load_permissions(0), load_permissions(min_level))
        self.assertEqual(load_permissions(1337), load_permissions(max_level))

        with self.assertRaises(ValueError):
            load_permissions(23, 'foobar')

        with self.assertRaises(InvalidResourceError):
            load_api_specific_resource_module('blablabla')

        self.assertEqual(
            load_permissions(16),
            load_api_specific_resource_module('aosp_permissions', 16),
        )
        self.assertEqual(
            load_permissions(CONF['DEFAULT_API']),
            load_api_specific_resource_module('aosp_permissions'),
        )

        for level in levels:
            perm = load_permissions(level)
            self.assertIn('android.permission.INTERNET', perm)
            self.assertIsInstance(perm, dict)
            self.assertIsInstance(perm['android.permission.INTERNET'], dict)
            self.assertIn('description', perm['android.permission.INTERNET'])
            self.assertIn('label', perm['android.permission.INTERNET'])
            self.assertIn(
                'protectionLevel', perm['android.permission.INTERNET']
            )
            self.assertIn(
                'permissionGroup', perm['android.permission.INTERNET']
            )

    def testCustomPermissionProtectionLevel(self):
        a = APK(
            os.path.join(
                test_dir, 'data/APK/com.example.android.tvleanback.apk'
            )
        )
        self.assertEqual(
            a.get_details_permissions()[
                "com.example.android.tvleanback.ACCESS_VIDEO_DATA"
            ][0],
            'signature',
        )

    def testShortNamesInManifest(self):
        """Test if shortnames are correctly handled"""
        a = apk.APK(
            os.path.join(test_dir, 'data/APK/AndroidManifest_ShortName.apk')
        )

        self.assertEqual(a.get_package(), 'com.android.galaxy4')

        self.assertEqual(len(a.get_activities()), 1)
        self.assertEqual(len(a.get_services()), 1)

        self.assertEqual(a.get_activities()[0], 'com.android.galaxy4.Galaxy4')
        self.assertEqual(
            a.get_services()[0], 'com.android.galaxy4.Galaxy4Wallpaper'
        )

        self.assertEqual(
            list(a.get_all_attribute_value("activity", "name"))[0],
            'com.android.galaxy4.Galaxy4',
        )
        self.assertEqual(
            list(
                a.get_all_attribute_value(
                    "activity", "name", format_value=False
                )
            )[0],
            '.Galaxy4',
        )

        # Test some formatting
        self.assertEqual(a._format_value('foo'), 'com.android.galaxy4.foo')
        self.assertEqual(a._format_value('.foo'), 'com.android.galaxy4.foo')
        self.assertEqual(
            a._format_value('com.android.galaxy4.foo'),
            'com.android.galaxy4.foo',
        )
        self.assertEqual(a._format_value('bla.bar.foo'), 'bla.bar.foo')
        self.assertEqual(a._format_value(None), None)

        a.package = None
        self.assertEqual(a._format_value('foo'), 'foo')
        self.assertEqual(a._format_value('.foo'), '.foo')
        self.assertEqual(
            a._format_value('com.android.galaxy4.foo'),
            'com.android.galaxy4.foo',
        )
        self.assertEqual(a._format_value('bla.bar.foo'), 'bla.bar.foo')
        self.assertEqual(a._format_value(None), None)

    def testMultipleLocaleAppName(self):
        """Test multiple locale appname"""
        a = apk.APK(
            os.path.join(test_dir, 'data/APK/multiple_locale_appname_test.apk')
        )
        self.assertEqual(a.get_app_name(), "values")
        self.assertEqual(a.get_app_name(locale='en'), "values-en")
        self.assertEqual(a.get_app_name(locale='zh-rCN'), "values-zh-rCN")
        self.assertEqual(a.get_app_name(locale='zh-rTW'), "values-zh-rTW")
        self.assertEqual(a.get_app_name(locale='ru-rRU'), "values-ru-rRU")

    def testPublicKeysofApk(self):
        a = APK(
            os.path.join(
                test_dir,
                'data/APK/com.example.android.wearable.wear.weardrawers.apk',
            )
        )
        pkeys = set(a.get_public_keys_der_v3() + a.get_public_keys_der_v2())
        for public_key in pkeys:
            from androguard.util import calculate_fingerprint, parse_public

            parsed_key = parse_public(public_key)
            self.assertEqual(parsed_key.algorithm, 'rsa')
            self.assertEqual(parsed_key.bit_size, 2048)
            self.assertEqual(
                calculate_fingerprint(parsed_key).hex(),
                '98917cd03c6277d73d58b661d614c442f2981a35a5aa122a61049215ba85c1d4',
            )


if __name__ == '__main__':
    unittest.main(failfast=True)
