From 65c9a13cfb2cf7c4c974d321a6274bec5760592f Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Wed, 4 Aug 2021 16:18:13 +0200 Subject: [PATCH 01/78] Update guest hole servers --- app/config/GuestHoleServers.json | 397 +++++++++++++------------------ 1 file changed, 160 insertions(+), 237 deletions(-) diff --git a/app/config/GuestHoleServers.json b/app/config/GuestHoleServers.json index 0bb824e75..165ac6eec 100644 --- a/app/config/GuestHoleServers.json +++ b/app/config/GuestHoleServers.json @@ -1,330 +1,253 @@ [ { - "Name": "NO#17", - "EntryCountry": "NO", - "ExitCountry": "NO", - "Domain": "no-17.protonvpn.net", - "Tier": 1, + "Name": "LT#6", + "EntryCountry": "LT", + "ExitCountry": "LT", + "Domain": "85.206.163.144", + "Tier": 2, "Features": 0, "Region": null, - "City": "Oslo", - "Score": 2.19324114, + "City": "Siauliai", + "Score": 1.20026203, "HostCountry": null, - "ID": "r3cIsp8UYmT9dhDHAIg2-CQMajRRaM4Xb0CwX3cks6FwOHggAfhzPH6SUd2JES9vmPy_3OMSGGPvO07couufdw==", + "ID": "4zzEVouX_i77J4x3fGAssTJ1xYp20LBgCNK49D2K65UvKULmo4TvyHdk7SI8N-alWEeUqu_GLNH1m_XlkwoD8g==", "Location": { - "Lat": 59.909999999999997, - "Long": 10.75 + "Lat": 55.93, + "Long": 23.32 }, "Status": 1, "Servers": [ { - "EntryIP": "91.219.215.155", - "ExitIP": "91.219.215.155", - "Domain": "no-17.protonvpn.net", - "ID": "Cvq9Lpq9lgu-i-iugUZf8BOrw4YsTGalssVWMy5Gpgm3F5rHmNO5fK99kKgGmG4Szd6jxsC98DPS9Uq6NqcaWw==", - "Label": "0", - "X25519PublicKey": null, + "EntryIP": "85.206.163.144", + "ExitIP": "85.206.163.150", + "Domain": "node-lt-01.protonvpn.net", + "ID": "VrU6Q2JNh73TmbIbM01l14hgdlU6vNyo8bP6piXdD9-cYZxgUBloWFDrG61QbIWHl25I_mdVhNF0Sa2T6s3-bQ==", + "Label": "2", + "X25519PublicKey": "t84vWcN20dsay78OFAopB9Vk/4GjL9IbK7SiHhtmug8=", "Generation": 0, "Status": 1, "ServicesDownReason": null } ], - "Load": 8 + "Load": 30 }, { - "Name": "NL-FREE#11", - "EntryCountry": "NL", - "ExitCountry": "NL", - "Domain": "nl-free-11.protonvpn.com", - "Tier": 0, - "Features": 0, + "Name": "DE#3", + "EntryCountry": "DE", + "ExitCountry": "DE", + "Domain": "node-de-01.protonvpn.net", + "Tier": 2, + "Features": 12, "Region": null, - "City": null, - "Score": 3.3690582299999998, + "City": "Frankfurt", + "Score": 1.1859171799999999, "HostCountry": null, - "ID": "2GXummgWOD9gH120_CGus9Mn24yCso4LTAW12QvQd93k6d2wYk-8LRGkUPx0OwJ_azpQwHcKbk3-OiNQWcXNYw==", + "ID": "7J7smwDoOZD537x3sohypBmu8phtWjoc7NmddefXLbHy76M8iTpcU9Zn0QsZhN9tRpJ8ILZ2GZVhaeCbku4IPQ==", "Location": { - "Lat": 52.369999999999997, - "Long": 4.8899999999999997 + "Lat": 51, + "Long": 9 }, "Status": 1, "Servers": [ { - "EntryIP": "185.107.95.225", - "ExitIP": "185.107.95.226", - "Domain": "lxc-nl-21.protonvpn.com", - "ID": "ZBgs_RZxKps6xayd6N0LBGzoKELElbllt3RX03ocDDyrUVd_lRMhQo0iI5Pz3XnjLKN1gkOAv3AmvgiWDOYifQ==", - "Label": "0", - "X25519PublicKey": null, - "Generation": 0, - "Status": 1, - "ServicesDownReason": null - }, - { - "EntryIP": "185.107.95.225", - "ExitIP": "185.107.95.227", - "Domain": "lxc-nl-21.protonvpn.com", - "ID": "9BpPJvBw9sYzrKzh2MH90OWSjAx0OUw9Js-RwAuwNqYTi-9sIdRtlDSwf1VISW68MwGei9OisG4C7iea2lkjFw==", - "Label": "1", - "X25519PublicKey": null, - "Generation": 0, - "Status": 1, - "ServicesDownReason": null - }, - { - "EntryIP": "185.107.95.225", - "ExitIP": "185.107.95.228", - "Domain": "lxc-nl-21.protonvpn.com", - "ID": "ctv52fQ5svlV-rIomJHGWxD0Ppimd2b17Kzb8IoC9WvVtggnljzIR2s3rcNBMQiiA5Gjq43-DdL8PwdyXq7kDQ==", - "Label": "2", - "X25519PublicKey": null, - "Generation": 0, - "Status": 1, - "ServicesDownReason": null - }, - { - "EntryIP": "185.107.95.225", - "ExitIP": "185.107.95.229", - "Domain": "lxc-nl-21.protonvpn.com", - "ID": "V_9Ok43My7ZJmR2eT7Em2SmlEUpRJdIulLRbyO8wexl93EsUKgAfzobsp6OqCXaQy3jKcJAlDpNECpIwvGxsgQ==", - "Label": "3", - "X25519PublicKey": null, - "Generation": 0, - "Status": 1, - "ServicesDownReason": null - }, - { - "EntryIP": "185.107.80.216", - "ExitIP": "185.107.80.217", - "Domain": "lxc-nl-36.protonvpn.com", - "ID": "Kh3etPmEOxjVqDGmlTtpQ6B5vWp8h4G8IgjzOeaPpN355Ynxcu47eIioMhP6R8o06TMOSFni_Hd8YSzADZgiJA==", + "EntryIP": "46.165.221.35", + "ExitIP": "37.58.58.231", + "Domain": "node-de-01.protonvpn.net", + "ID": "7J7smwDoOZD537x3sohypBmu8phtWjoc7NmddefXLbHy76M8iTpcU9Zn0QsZhN9tRpJ8ILZ2GZVhaeCbku4IPQ==", "Label": "0", - "X25519PublicKey": "JZB+EhgDp8jaPVI26NY/YsBt087NdmbZtb0ppZJQYHk=", - "Generation": 0, - "Status": 1, - "ServicesDownReason": null - }, - { - "EntryIP": "185.107.80.216", - "ExitIP": "185.107.80.218", - "Domain": "lxc-nl-36.protonvpn.com", - "ID": "A-8upgSfO1uExZf5h7dL3GsEZPB3IZ_nMtdjGAYJLgiMZCVQiVbPVwmGxiaxIaxpIBBws2-zEDzfeoSja7GAzA==", - "Label": "1", - "X25519PublicKey": "JZB+EhgDp8jaPVI26NY/YsBt087NdmbZtb0ppZJQYHk=", - "Generation": 0, - "Status": 1, - "ServicesDownReason": null - }, - { - "EntryIP": "185.107.80.216", - "ExitIP": "185.107.80.219", - "Domain": "lxc-nl-36.protonvpn.com", - "ID": "fbGksHD7EceGw96wwcyclCkbT2LCHOM9XuxCLqtQcKb5V5_g6FZDxwnjoY12-XjGWIJv3ckcHOCaBtfWA630aA==", - "Label": "2", - "X25519PublicKey": "JZB+EhgDp8jaPVI26NY/YsBt087NdmbZtb0ppZJQYHk=", - "Generation": 0, - "Status": 1, - "ServicesDownReason": null - }, - { - "EntryIP": "185.107.80.216", - "ExitIP": "185.107.80.220", - "Domain": "lxc-nl-36.protonvpn.com", - "ID": "-BMuO3iNatuqgK21Z_vzQ683-KkozLe-wHaxFJtESWlC-14gFXEIzG2MQBT2CzKBgFnsRpiLXrSyeJXDrWPN9g==", - "Label": "3", - "X25519PublicKey": "JZB+EhgDp8jaPVI26NY/YsBt087NdmbZtb0ppZJQYHk=", + "X25519PublicKey": "hi0OnyLL8AHVv5SGlyVa5FxruisqP7gCrQQwUHMXm0k=", "Generation": 0, "Status": 1, "ServicesDownReason": null } ], - "Load": 93 + "Load": 65 }, { - "Name": "US-FL#18", + "Name": "US-CA#33", "EntryCountry": "US", "ExitCountry": "US", - "Domain": "us-fl-17.protonvpn.net", + "Domain": "us-ca-33.protonvpn.net", "Tier": 1, "Features": 4, "Region": null, - "City": "Miami", - "Score": 2.46893282, + "City": "Los Angeles", + "Score": 2.5061782799999999, "HostCountry": null, - "ID": "jw2nBd6BHrQK-HXkMo3cPuX1pboI4OSrnpd_3z9sSVS3P3krEl70XZwwtJTnSjWy3-6sKcnrJQykz6_6LmVj8A==", + "ID": "7ZEsSm9JHoeMums0sAzeE9IRZMH9U4fs55xmgdF5BGJm7TDBUfAOwFMASlqgjZmPGnP3A2I0FCTNsrdm7zJyXQ==", "Location": { - "Lat": 25.77, - "Long": -80.189999999999998 + "Lat": 34.049999999999997, + "Long": -118.26000000000001 }, "Status": 1, "Servers": [ { - "EntryIP": "45.87.214.195", - "ExitIP": "45.87.214.196", - "Domain": "us-fl-17.protonvpn.net", - "ID": "AR-mFh4kdfrSbtpzEG_ARQ2sFsSJ117kLd2QYU1VGyU5iLM3FWnK_DJ6osfCd7h7o-xARfQ0Jinhr59cr0JMmA==", - "Label": "1", - "X25519PublicKey": null, + "EntryIP": "185.230.126.19", + "ExitIP": "185.230.126.19", + "Domain": "us-ca-33.protonvpn.net", + "ID": "NHK7gMw8XcVds7OhOCnhu-U7vmj_o_e-a_6728_vW_UMlj60ezJfZqFGz3gvZLOuGNMcAx6tAgtGigBGRTGFGg==", + "Label": "0", + "X25519PublicKey": "02VE3OuUf61AlT6CVndSyGfQtM1ed1U1ShsLF/xdzGk=", "Generation": 0, "Status": 1, "ServicesDownReason": null } ], - "Load": 34 + "Load": 20 }, { - "Name": "AU#38", - "EntryCountry": "AU", - "ExitCountry": "AU", - "Domain": "au-38.protonvpn.com", + "Name": "UA#16", + "EntryCountry": "UA", + "ExitCountry": "UA", + "Domain": "node-ua-02.protonvpn.net", "Tier": 2, - "Features": 8, + "Features": 0, "Region": null, - "City": "Adelaide", - "Score": 1.7451756700000001, + "City": "Kyiv", + "Score": 1.23236061, "HostCountry": null, - "ID": "pMiBfuWJWx5fSFYSOSsdphlRSKQWX0KanHY2pS0Kj0DAKFY5-lSOpp7HFcCO-SWtnUd5O0jW9ABJgxbivg2fCg==", + "ID": "1NlUNuCHSwxs9EStt82xZFtoViKd6gPw_Mqbcv50ZbFIayUKKUO7Z-ja0LDRqRWdqoZoRBoNdFFNQt-K6VfXCQ==", "Location": { - "Lat": -34.93, - "Long": 138.59999999999999 + "Lat": 50.450000000000003, + "Long": 30.52 }, "Status": 1, "Servers": [ { - "EntryIP": "116.206.231.186", - "ExitIP": "116.206.231.188", - "Domain": "lxc-au-09.protonvpn.com", - "ID": "PhrRot836G-etkC_3fIfVL8THHavpnMQgG8uvD9czPRiRPc4fCNr8D7D5IHqwLtVqnWlyHK3J8TenCACpdiRnA==", - "Label": "0", - "X25519PublicKey": null, + "EntryIP": "156.146.50.5", + "ExitIP": "156.146.50.9", + "Domain": "node-ua-02.protonvpn.net", + "ID": "_TgqmwyCLU8pMHg8KiZ1JNgaBupvoUz0a6lIY3V-a_6qvLxxesg6dS6AQzXGDBX8THBr9OwH2XM_ZTH7KgJnDA==", + "Label": "4", + "X25519PublicKey": "eqjhoqO6K1nLiej026+RkpSTHloVrOHLlMQaB0Tl5GM=", "Generation": 0, "Status": 1, "ServicesDownReason": null } ], - "Load": 69 + "Load": 43 }, { - "Name": "US-FL#9", + "Name": "US-CO#8", "EntryCountry": "US", "ExitCountry": "US", - "Domain": "us-fl-09.protonvpn.com", - "Tier": 1, - "Features": 0, + "Domain": "node-us-58.protonvpn.net", + "Tier": 2, + "Features": 8, "Region": null, - "City": "Miami", - "Score": 2.4573603199999998, + "City": "Denver", + "Score": 1.47804998, "HostCountry": null, - "ID": "g1t2gCfDnOr7SfVKNZb9Aodj0QjWdTAPHtgXgKbtkFju85tDnuNHiW-d28BwBwX7aF1pVOcc2bpTBkIyvJ6YLQ==", + "ID": "9vv4sMOJegrTiDnJ9mxvXC6mXCmjpifyUVJn5iiFK6Rt7lXSZG7jVHIe52hAy6eAPOQE6orGoaoPkLRFwWfJvQ==", "Location": { - "Lat": 25.77, - "Long": -80.189999999999998 + "Lat": 39.740000000000002, + "Long": -104.98999999999999 }, "Status": 1, "Servers": [ { - "EntryIP": "37.120.215.227", - "ExitIP": "37.120.215.227", - "Domain": "us-fl-09.protonvpn.com", - "ID": "PfFfhiYNeFgIOqwd3lm6BMI_UsKieujQsNYncUKDdvctgb_BLXhLkE9627aUkF9jwTRJMfMzzQFWy43o1I087w==", - "Label": "0", - "X25519PublicKey": null, + "EntryIP": "84.17.63.8", + "ExitIP": "84.17.63.16", + "Domain": "node-us-58.protonvpn.net", + "ID": "khXXigiKQ1lRgN4B0KfYpSdGl3CbKnTbx48sqOG_HJtqduvB3c6lYAYM_q_J4NfRwOzlOjtaxO1lIGg7ABOVuA==", + "Label": "4", + "X25519PublicKey": "Yu2fgynXUAASCkkrXWj76LRriFxKMTQq+zjTzyOKG1Q=", "Generation": 0, "Status": 1, "ServicesDownReason": null } ], - "Load": 9 + "Load": 29 }, { - "Name": "MX#8", - "EntryCountry": "MX", - "ExitCountry": "MX", - "Domain": "node-mx-01.protonvpn.net", + "Name": "US-FL#28", + "EntryCountry": "US", + "ExitCountry": "US", + "Domain": "node-us-64.protonvpn.net", "Tier": 2, - "Features": 0, + "Features": 12, "Region": null, - "City": "Chiapas", - "Score": 999.50317835999999, + "City": "Miami", + "Score": 1.53215572, "HostCountry": null, - "ID": "CWX9YsBLbmBk1hoF3m-W8hM2XFcNuwJjtMPMHq6TjwC5_485C77M_TpWOdaoBKoRieCJ3FLgN8dTqD6ClizUuA==", + "ID": "IFIhm_XLFyW-B65rz5gDouB8g4DSwoFhBj4bLIkldvaJM-pEHVTgMFSWOzw_nSf-FUlgjixJqoeYTK_1jQr_JA==", "Location": { - "Lat": 16.760000000000002, - "Long": -93.109999999999999 + "Lat": 25.77, + "Long": -80.189999999999998 }, "Status": 1, "Servers": [ { - "EntryIP": "191.96.145.238", - "ExitIP": "191.96.145.246", - "Domain": "node-mx-01.protonvpn.net", - "ID": "Ta2AZ8DN3vZSDSUImW7ebT5zKhTSJGNwVcDiDEshiJ_R7_OblllTgbb8Fojt00evg7Yhdu2OBh7ii94h8PMmvw==", - "Label": "4", - "X25519PublicKey": null, + "EntryIP": "45.87.214.210", + "ExitIP": "45.87.214.214", + "Domain": "node-us-64.protonvpn.net", + "ID": "-U7jRsK11OFdAjOZ1R4EyzZIP2ABQA2oaXlPkE0QixVC4x7qfuLY2QTVw8ru7UJJ1O7uzuYDdpxlEUzos_wvHw==", + "Label": "0", + "X25519PublicKey": "7/eRyWTEpZBO8ete4Ur39sUyrR8RVZpsU4D/v8NM7F0=", "Generation": 0, "Status": 1, "ServicesDownReason": null } ], - "Load": 10 + "Load": 63 }, { - "Name": "AU#26", - "EntryCountry": "AU", - "ExitCountry": "AU", - "Domain": "au-26.protonvpn.com", + "Name": "NZ#9", + "EntryCountry": "NZ", + "ExitCountry": "NZ", + "Domain": "node-nz-02.protonvpn.net", "Tier": 2, - "Features": 8, + "Features": 0, "Region": null, - "City": "Sydney", - "Score": 1.72291211, + "City": "Auckland", + "Score": 999.70318736000002, "HostCountry": null, - "ID": "wOx--bv6mvHGl16H9L29Z3tlk98LPso5WXfs8b_dj72kAJ_mummcf1ic6CUF6N-Q5HjkrgbiPCUOSOJciVpPtQ==", + "ID": "AMhNBA1JukVBd9Fhu93p1RVcglsmZXmgYAI9En7RbwEiNIYH-W3kov1P-CHZt9MMuj59FQyEA8htG2Cmfd683Q==", "Location": { - "Lat": -33.859999999999999, - "Long": 151.19999999999999 + "Lat": -36.848999999999997, + "Long": 174.77500000000001 }, "Status": 1, "Servers": [ { - "EntryIP": "137.59.253.55", - "ExitIP": "137.59.253.57", - "Domain": "lxc-au-07.protonvpn.com", - "ID": "XZBqLPUCGvnl0au-AV_JCOfb-3VpYuxViLl8fHGZ0MWukpv6ZfSDFt_s7i5AZVTH9HOplNZBpHkyl7K0L6OMLQ==", + "EntryIP": "116.90.74.242", + "ExitIP": "116.90.74.247", + "Domain": "node-nz-02.protonvpn.net", + "ID": "SxQUBkOGzUUyVHl5Rabp_qAMY-ciZ3hNVLVbtQRLlmQq2sWz1dtDx7d0j0yTlJz3zIjojdRvIE9crOSFMUW73Q==", "Label": "1", - "X25519PublicKey": null, + "X25519PublicKey": "ErCIBur8/FyI+SpFAXyfJTDOF1rvi3nlYvHnPBz3K3c=", "Generation": 0, "Status": 1, "ServicesDownReason": null } ], - "Load": 58 + "Load": 22 }, { - "Name": "US-FL#46", - "EntryCountry": "US", - "ExitCountry": "US", - "Domain": "node-us-102.protonvpn.net", - "Tier": 2, - "Features": 8, + "Name": "AU#29", + "EntryCountry": "AU", + "ExitCountry": "AU", + "Domain": "au-29.protonvpn.net", + "Tier": 1, + "Features": 0, "Region": null, - "City": "Miami", - "Score": 1.46607892, + "City": "Perth", + "Score": 2.6150790100000001, "HostCountry": null, - "ID": "RQFcGLh_H4CtmIWn7_yQVJK1P7FM8KJDtnFip_c_2mFIV6FmUpKlV393AHLRav2ICmkLTvPEfcociVPyyVoLrQ==", + "ID": "hgcLm_yZkwMKUoToa2p3XGA60FGwaD3U4GIkQ3VAE6ef59hhm5pJ5fshBZXJQITMMxqeB_k40uHyAYBgU2oA9w==", "Location": { - "Lat": 25.77, - "Long": -80.189999999999998 + "Lat": -32.039999999999999, + "Long": 115.68000000000001 }, "Status": 1, "Servers": [ { - "EntryIP": "89.38.227.138", - "ExitIP": "89.38.227.140", - "Domain": "node-us-102.protonvpn.net", - "ID": "kCVw_2Q3kgdcAPYb-xGLsAaGarLOolKD_9Vn-PdyHX2Z46K0J0sNw_KBA8UWHReR87fDq15FJgeCCxH4SYXxDQ==", + "EntryIP": "103.107.197.3", + "ExitIP": "103.107.197.3", + "Domain": "au-29.protonvpn.net", + "ID": "7mg-8Dw1wRhLhBRhPEPIo2-mtUcb_hvu_kjetpCwFN48gdO_zd2NHEzBYADZ8SqrMyKZuBOzN_msfgla3FUyrQ==", "Label": "0", - "X25519PublicKey": null, + "X25519PublicKey": "mM2CdcIn8d64ITQEZqN6mWJ09qFIkAuhWDZWcQqR7Gs=", "Generation": 0, "Status": 1, "ServicesDownReason": null @@ -333,67 +256,67 @@ "Load": 31 }, { - "Name": "NL#44", - "EntryCountry": "NL", - "ExitCountry": "NL", - "Domain": "nl-44.protonvpn.com", + "Name": "IL#2", + "EntryCountry": "IL", + "ExitCountry": "IL", + "Domain": "node-il-01c.protonvpn.net", "Tier": 2, "Features": 0, "Region": null, - "City": "Amsterdam", - "Score": 1.1287636299999999, + "City": "Tel Aviv", + "Score": 999.27922446000002, "HostCountry": null, - "ID": "aGzmBU9zGouUD-zVIoT1z3Lk9c5xJjC0PHbmRxYYczWSA7O7940onaLEWLUnWYNqgih5HqQYJL83IxgWmkVj9Q==", + "ID": "r7-20s78k3ejJuW6NV1Hj0tJ6DE079Szw32pmSwpI67HUO6zk14Z3XY43zQhLbIBLei27chonwiXr7DBA4_dFw==", "Location": { - "Lat": 52.369999999999997, - "Long": 4.8899999999999997 + "Lat": 32.079999999999998, + "Long": 34.799999999999997 }, "Status": 1, "Servers": [ { - "EntryIP": "190.2.132.124", - "ExitIP": "190.2.132.136", - "Domain": "lxc-nl-29.protonvpn.com", - "ID": "dfs0rWMEv0SHX3i5YJHr3dnIpgddqoV585itvVvpZxA7Tmgou0YzZArvomfRBdnvaYEAv0_7QW0y65LLjca9WQ==", - "Label": "3", - "X25519PublicKey": null, + "EntryIP": "87.239.255.100", + "ExitIP": "87.239.255.102", + "Domain": "node-il-01c.protonvpn.net", + "ID": "H4ZiPDHqj8-uRdzmNAIUrFSWJuBHfthiyin_B1kboo9rJl7aHHnTCZhtYYf7Br2zIQ-14MWs6beSzrbrk6UBuQ==", + "Label": "0", + "X25519PublicKey": "YY1lfYuNfNkBOSfFhzuixH+lzNXgCI4GKIo2T0IsOj0=", "Generation": 0, "Status": 1, "ServicesDownReason": null } ], - "Load": 15 + "Load": 28 }, { - "Name": "US-WA#16", + "Name": "US-IL#28", "EntryCountry": "US", "ExitCountry": "US", - "Domain": "us-wa-16.protonvpn.com", + "Domain": "us-il-28.protonvpn.com", "Tier": 2, "Features": 8, "Region": null, - "City": "Seattle", - "Score": 1.4869068999999999, + "City": "Chicago", + "Score": 1.50397526, "HostCountry": null, - "ID": "L1TMJKnZg3Dns6oJa5QMYZeXu3Xkcd0KrB-OnyA1PxwLWpXy77aqCRv8QQ2DwO578UpgRsGdadzTRiSk6EyJ_w==", + "ID": "tbzT86Ytpvj_NkChyn_fgdXYLW7bF_3B3nVV3UdciBAvtO3xSCyVSOqHKrMAYVnC52XKAIEwWEKJAa494mbB5w==", "Location": { - "Lat": 47.607999999999997, - "Long": -122.33499999999999 + "Lat": 41.850000000000001, + "Long": -87.650000000000006 }, "Status": 1, "Servers": [ { - "EntryIP": "199.187.211.103", - "ExitIP": "199.187.211.107", - "Domain": "lxc-us-35.protonvpn.com", - "ID": "xUEZ8qdrwuCwpUQKznELEP8L5WKTFrDMgA5a2IE0wVlHepVbCgFnFrpKbHP2wUAkqFhI0p0fO5RBR3IFbZ1Alg==", - "Label": "3", - "X25519PublicKey": null, + "EntryIP": "173.0.77.11", + "ExitIP": "173.0.77.15", + "Domain": "lxc-us-53.protonvpn.com", + "ID": "iewws3fsZHkb3oFAT-2KTriNdPIEux5X9sU5oV-Qcr9b9Y9Z7VjGVyDZCC4oQX1DyJcdkyV6Cdpsf0Dcq0FdjA==", + "Label": "0", + "X25519PublicKey": "GKzlkcd4hUxfmJpmsZi03km4S2k3R/snVXeIhA9NXlI=", "Generation": 0, "Status": 1, "ServicesDownReason": null } ], - "Load": 35 + "Load": 61 } ] \ No newline at end of file From d48609b0d28e1cf529d33721bf21d342a1e82495 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Wed, 4 Aug 2021 16:23:58 +0200 Subject: [PATCH 02/78] Add changelog for 2.8.75 --- metadata/en-US/changelogs/102087500.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 metadata/en-US/changelogs/102087500.txt diff --git a/metadata/en-US/changelogs/102087500.txt b/metadata/en-US/changelogs/102087500.txt new file mode 100644 index 000000000..62a1b3467 --- /dev/null +++ b/metadata/en-US/changelogs/102087500.txt @@ -0,0 +1,3 @@ +What's new: +Adds beta support for WireGuard®, a lightweight and high-speed VPN protocol. +To start using WireGuard®, navigate to the Settings, disable Smart Protocol, and select WireGuard® from the menu. From 4877f6c7f8b689dccff4f93d3534bd7cf3658685 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Tue, 31 Aug 2021 16:03:52 +0200 Subject: [PATCH 03/78] Always ping server ports before connection [VPNAND-606] --- .../protonvpn/tests/vpn/VpnConnectionTests.kt | 19 +++++++++---------- .../android/vpn/ProtonVpnBackendProvider.kt | 7 ++++--- .../com/protonvpn/android/vpn/VpnBackend.kt | 7 ++++++- .../android/vpn/VpnConnectionManager.kt | 4 +++- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt b/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt index 32db9f3f9..3b6413590 100644 --- a/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt +++ b/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt @@ -219,8 +219,8 @@ class VpnConnectionTests { // When scanning fails we'll fallback to attempt connecting with default manual protocol coVerify(exactly = 1) { - mockOpenVpn.prepareForConnection(any(), any(), false) - mockOpenVpn.connect(any()) + mockStrongSwan.prepareForConnection(any(), any(), false) + mockStrongSwan.connect(any()) } Assert.assertEquals(VpnState.Connected, monitor.state) @@ -229,16 +229,15 @@ class VpnConnectionTests { @Test fun smartNoInternet() = runBlockingTest { MockNetworkManager.currentStatus = NetworkStatus.Disconnected - userData.manualProtocol = VpnProtocol.OpenVPN manager.connect(context, profileSmart) yield() coVerify(exactly = 0) { - mockStrongSwan.prepareForConnection(any(), any(), any()) + mockOpenVpn.prepareForConnection(any(), any(), any()) } coVerify(exactly = 1) { - mockOpenVpn.prepareForConnection(any(), any(), false) - mockOpenVpn.connect(any()) + mockStrongSwan.prepareForConnection(any(), any(), false) + mockStrongSwan.connect(any()) } Assert.assertEquals(VpnState.Connected, monitor.state) @@ -251,7 +250,7 @@ class VpnConnectionTests { scope.advanceUntilIdle() coVerify(exactly = 1) { - mockWireguard.prepareForConnection(any(), any(), false) + mockWireguard.prepareForConnection(any(), any(), true) mockWireguard.createAgentConnection(any(), any(), any()) } Assert.assertEquals(VpnState.Connected, monitor.state) @@ -278,7 +277,7 @@ class VpnConnectionTests { manager.connect(context, profileWireguard) coVerify(exactly = 1) { - mockWireguard.prepareForConnection(any(), any(), false) + mockWireguard.prepareForConnection(any(), any(), true) } coVerify(exactly = 0) { mockWireguard.connectToLocalAgent() @@ -298,9 +297,9 @@ class VpnConnectionTests { @Test fun guestHoleFail() = runBlockingTest { - mockStrongSwan.failScanning = true mockOpenVpn.failScanning = true - mockOpenVpn.stateOnConnect = VpnState.Disabled + mockStrongSwan.failScanning = true + mockStrongSwan.stateOnConnect = VpnState.Disabled val guestHole = GuestHole(scope, serverManager, monitor, manager) val result = guestHole.call(context) { diff --git a/app/src/main/java/com/protonvpn/android/vpn/ProtonVpnBackendProvider.kt b/app/src/main/java/com/protonvpn/android/vpn/ProtonVpnBackendProvider.kt index 9d78d9cdc..7eea340cd 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/ProtonVpnBackendProvider.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/ProtonVpnBackendProvider.kt @@ -42,13 +42,14 @@ class ProtonVpnBackendProvider( override suspend fun prepareConnection( protocol: VpnProtocol, profile: Profile, - server: Server + server: Server, + alwaysScan: Boolean ): PrepareResult? { ProtonLogger.log("Preparing connection with protocol: " + protocol.name) return when (protocol) { VpnProtocol.IKEv2 -> strongSwan.prepareForConnection(profile, server, scan = false) - VpnProtocol.OpenVPN -> openVpn.prepareForConnection(profile, server, scan = false) - VpnProtocol.WireGuard -> wireGuard.prepareForConnection(profile, server, scan = false) + VpnProtocol.OpenVPN -> openVpn.prepareForConnection(profile, server, scan = alwaysScan) + VpnProtocol.WireGuard -> wireGuard.prepareForConnection(profile, server, scan = alwaysScan) VpnProtocol.Smart -> { val backends = mutableListOf() with(config.getSmartProtocolConfig()) { diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt index ab98afefb..1574827e6 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt @@ -57,7 +57,12 @@ data class RetryInfo(val timeoutSeconds: Int, val retryInSeconds: Int) data class PrepareResult(val backend: VpnBackend, val connectionParams: ConnectionParams) : java.io.Serializable interface VpnBackendProvider { - suspend fun prepareConnection(protocol: VpnProtocol, profile: Profile, server: Server): PrepareResult? + suspend fun prepareConnection( + protocol: VpnProtocol, + profile: Profile, + server: Server, + alwaysScan: Boolean = true + ): PrepareResult? // Returns first from [preferenceList] that responded in a given time frame or null // [fullScanServer] when set will have all ports scanned. diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt index 75024e35c..ab9f55bce 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt @@ -60,6 +60,8 @@ import me.proton.core.network.domain.NetworkManager import javax.inject.Singleton import kotlin.coroutines.coroutineContext +private val FALLBACK_PROTOCOL = VpnProtocol.IKEv2 + @Singleton open class VpnConnectionManager( private val appContext: Context, @@ -288,7 +290,7 @@ open class VpnConnectionManager( // If port scanning fails (because e.g. some temporary network situation) just connect // without smart protocol - preparedConnection = backendProvider.prepareConnection(userData.manualProtocol, profile, server)!! + preparedConnection = backendProvider.prepareConnection(FALLBACK_PROTOCOL, profile, server, false)!! } preparedConnect(preparedConnection) From 52aeb8916e49afd857cf1417de2007851c26e5fd Mon Sep 17 00:00:00 2001 From: Algirdas Pundzius Date: Wed, 1 Sep 2021 15:19:24 +0300 Subject: [PATCH 04/78] Add cambodia coordinates to map view --- app/src/main/java/com/protonvpn/android/utils/CountryTools.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/protonvpn/android/utils/CountryTools.kt b/app/src/main/java/com/protonvpn/android/utils/CountryTools.kt index 503def136..269326099 100644 --- a/app/src/main/java/com/protonvpn/android/utils/CountryTools.kt +++ b/app/src/main/java/com/protonvpn/android/utils/CountryTools.kt @@ -134,6 +134,7 @@ object CountryTools { "UA" to CountryData(2715.0, 517.0, Continent.Europe), "NG" to CountryData(2385.0, 1235.0, Continent.AfricaAndMiddleEast), "PH" to CountryData(4159.0, 1135.0, Continent.Asia), + "KH" to CountryData(3911.0, 1194.0, Continent.Asia), "AE" to CountryData(3103.0, 976.0, Continent.AfricaAndMiddleEast), "CO" to CountryData(1100.0, 1339.0, Continent.America), "PE" to CountryData(1056.0, 1589.0, Continent.America), From e18421c4575ea507c58f7a81e7962a98c87bec79 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Thu, 2 Sep 2021 18:18:19 +0200 Subject: [PATCH 05/78] Support smart reconnections for WireGuard when server is unreachable --- .../java/com/protonvpn/di/MockAppModule.kt | 3 ++- .../com/protonvpn/di/MockVpnStateMonitor.kt | 5 +++-- .../protonvpn/tests/vpn/VpnConnectionTests.kt | 3 ++- .../com/protonvpn/android/di/AppModule.kt | 2 +- .../com/protonvpn/android/vpn/VpnBackend.kt | 2 +- .../android/vpn/VpnConnectionManager.kt | 21 +++++++++++++++---- .../android/vpn/ikev2/StrongSwanBackend.kt | 17 +-------------- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/app/src/androidTest/java/com/protonvpn/di/MockAppModule.kt b/app/src/androidTest/java/com/protonvpn/di/MockAppModule.kt index eda33b60f..0cf887574 100644 --- a/app/src/androidTest/java/com/protonvpn/di/MockAppModule.kt +++ b/app/src/androidTest/java/com/protonvpn/di/MockAppModule.kt @@ -254,7 +254,8 @@ class MockAppModule { vpnStateMonitor, notificationHelper, serverManager, - scope + scope, + System::currentTimeMillis ) @Singleton diff --git a/app/src/androidTest/java/com/protonvpn/di/MockVpnStateMonitor.kt b/app/src/androidTest/java/com/protonvpn/di/MockVpnStateMonitor.kt index 5a3829630..50579de93 100644 --- a/app/src/androidTest/java/com/protonvpn/di/MockVpnStateMonitor.kt +++ b/app/src/androidTest/java/com/protonvpn/di/MockVpnStateMonitor.kt @@ -39,9 +39,10 @@ class MockVpnConnectionManager( vpnStateMonitor: VpnStateMonitor, notificationHelper: NotificationHelper, serverManager: ServerManager, - scope: CoroutineScope + scope: CoroutineScope, + now: () -> Long ) : VpnConnectionManager(ProtonApplication.getAppContext(), userData, vpnBackendProvider, networkManager, - vpnErrorHandler, vpnStateMonitor, notificationHelper, serverManager, scope) { + vpnErrorHandler, vpnStateMonitor, notificationHelper, serverManager, scope, now) { override fun prepare(context: Context): Intent? = null } diff --git a/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt b/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt index 3b6413590..f95c0a284 100644 --- a/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt +++ b/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt @@ -119,6 +119,7 @@ class VpnConnectionTests { private val badCert = CertificateRepository.CertificateResult.Success("bad_cert", "bad_key") private lateinit var currentCert: CertificateRepository.CertificateResult.Success + private var time = 0L @Before fun setup() { @@ -150,7 +151,7 @@ class VpnConnectionTests { monitor = VpnStateMonitor() manager = MockVpnConnectionManager(userData, backendProvider, networkManager, vpnErrorHandler, monitor, - mockk(relaxed = true), mockk(relaxed = true), scope) + mockk(relaxed = true), mockk(relaxed = true), scope, ::time) MockNetworkManager.currentStatus = NetworkStatus.Unmetered diff --git a/app/src/main/java/com/protonvpn/android/di/AppModule.kt b/app/src/main/java/com/protonvpn/android/di/AppModule.kt index 88aa43cae..23199e9c2 100644 --- a/app/src/main/java/com/protonvpn/android/di/AppModule.kt +++ b/app/src/main/java/com/protonvpn/android/di/AppModule.kt @@ -262,6 +262,7 @@ class AppModule { notificationHelper, serverManager, scope, + System::currentTimeMillis ) @Singleton @@ -310,7 +311,6 @@ class AppModule { random, networkManager, scope, - System::currentTimeMillis, userData, appConfig, certificateRepository, diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt index 1574827e6..aae093c79 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt @@ -279,7 +279,7 @@ abstract class VpnBackend( VpnState.Connected agentConstants.stateConnectionError, agentConstants.stateServerUnreachable -> - VpnState.Error(ErrorType.UNREACHABLE) + VpnState.Error(ErrorType.UNREACHABLE_INTERNAL) agentConstants.stateClientCertificateExpiredError -> { refreshCertOnLocalAgent(force = false) VpnState.Connecting diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt index ab9f55bce..eefd75be6 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt @@ -57,10 +57,12 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import me.proton.core.network.domain.NetworkManager +import java.util.concurrent.TimeUnit import javax.inject.Singleton import kotlin.coroutines.coroutineContext private val FALLBACK_PROTOCOL = VpnProtocol.IKEv2 +private val UNREACHABLE_MIN_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1) @Singleton open class VpnConnectionManager( @@ -72,7 +74,8 @@ open class VpnConnectionManager( private val vpnStateMonitor: VpnStateMonitor, private val notificationHelper: NotificationHelper, private val serverManager: ServerManager, - private val scope: CoroutineScope + private val scope: CoroutineScope, + private val now: () -> Long ) : VpnStateSource { companion object { @@ -91,6 +94,7 @@ open class VpnConnectionManager( private var connectionParams: ConnectionParams? = null private var lastProfile: Profile? = null + private var lastUnreachable = Long.MIN_VALUE override val selfStateObservable = MutableLiveData(VpnState.Disabled) @@ -127,9 +131,11 @@ open class VpnConnectionManager( if (errorType != null && errorType in RECOVERABLE_ERRORS) { if (ongoingFallback?.isActive != true) { - ongoingFallback = scope.launch { - handleRecoverableError(errorType, connectionParams!!) - ongoingFallback = null + if (!skipFallback(errorType)) { + ongoingFallback = scope.launch { + handleRecoverableError(errorType, connectionParams!!) + ongoingFallback = null + } } } } else { @@ -168,6 +174,13 @@ open class VpnConnectionManager( initialized = true } + private fun skipFallback(errorType: ErrorType) = + errorType == ErrorType.UNREACHABLE_INTERNAL && + (lastUnreachable > now() - UNREACHABLE_MIN_INTERVAL_MS).also { skip -> + if (!skip) + lastUnreachable = now() + } + private fun activateBackend(newBackend: VpnBackend) { DebugUtils.debugAssert { activeBackend == null || activeBackend == newBackend diff --git a/app/src/main/java/com/protonvpn/android/vpn/ikev2/StrongSwanBackend.kt b/app/src/main/java/com/protonvpn/android/vpn/ikev2/StrongSwanBackend.kt index f5c6e824f..cc7570598 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/ikev2/StrongSwanBackend.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/ikev2/StrongSwanBackend.kt @@ -46,13 +46,11 @@ import me.proton.core.network.domain.NetworkStatus import me.proton.core.util.kotlin.DispatcherProvider import org.strongswan.android.logic.VpnStateService import java.util.Random -import java.util.concurrent.TimeUnit class StrongSwanBackend( val random: Random, networkManager: NetworkManager, mainScope: CoroutineScope, - val now: () -> Long, userData: UserData, appConfig: AppConfig, certificateRepository: CertificateRepository, @@ -69,7 +67,6 @@ class StrongSwanBackend( private var vpnService: VpnStateService? = null private val serviceProvider = Channel() - private var lastUnreachable = 0L init { bindCharonMonitor() @@ -163,23 +160,11 @@ class StrongSwanBackend( override fun stateChanged() { vpnService?.let { - val newState = translateState(it.state, it.errorState) - if (newState.isUnreachable()) { - // Limit frequency of unreachable notifications - if (vpnProtocolState.isUnreachable() && now() - lastUnreachable < UNREACHABLE_MIN_INTERVAL_MS) - return - lastUnreachable = now() - } - vpnProtocolState = newState + vpnProtocolState = translateState(it.state, it.errorState) } } - private fun VpnState.isUnreachable() = (this as? VpnState.Error)?.type.let { - it == ErrorType.UNREACHABLE_INTERNAL || it == ErrorType.UNREACHABLE - } - companion object { private val STRONGSWAN_PORTS = listOf(500, 4500) - private val UNREACHABLE_MIN_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1) } } From 13bca647ba9266673390487e7bca7fe1603c72f1 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Thu, 2 Sep 2021 18:50:23 +0200 Subject: [PATCH 06/78] Set error state on unknown final errors --- app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt index aae093c79..1c4343f49 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt @@ -72,6 +72,7 @@ interface VpnBackendProvider { interface AgentConnectionInterface { val state: String + val status: StatusMessage? fun setFeatures(features: Features) fun setConnectivity(connectivity: Boolean) fun close() @@ -133,6 +134,7 @@ abstract class VpnBackend( ) override val state: String get() = agent.state + override val status: StatusMessage? get() = agent.status override fun setFeatures(features: Features) { agent.setFeatures(features) @@ -158,6 +160,10 @@ abstract class VpnBackend( override fun onError(code: Long, description: String) { ProtonLogger.log("Local agent error: $code $description") + if (agent?.status?.reason?.final == true) { + setLocalAgentError(description) + return + } when (code) { agentConstants.errorCodeMaxSessionsBasic, agentConstants.errorCodeMaxSessionsFree, From 40ff0f511b50c6c4ce5674f1e02bad8eb0723d85 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Fri, 3 Sep 2021 12:08:53 +0200 Subject: [PATCH 07/78] Log unhandled java exceptions in app log file --- .../protonvpn/android/ProtonApplication.java | 4 +++ .../android/utils/ProtonExceptionHandler.kt | 32 +++++++++++++++++++ .../android/utils/ProtonLoggerImpl.kt | 19 +++++++++-- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/protonvpn/android/utils/ProtonExceptionHandler.kt diff --git a/app/src/main/java/com/protonvpn/android/ProtonApplication.java b/app/src/main/java/com/protonvpn/android/ProtonApplication.java index 51873c1fa..284030880 100644 --- a/app/src/main/java/com/protonvpn/android/ProtonApplication.java +++ b/app/src/main/java/com/protonvpn/android/ProtonApplication.java @@ -31,6 +31,7 @@ import com.protonvpn.android.di.DaggerAppComponent; import com.protonvpn.android.utils.AndroidUtils; import com.protonvpn.android.utils.DefaultActivityLifecycleCallbacks; +import com.protonvpn.android.utils.ProtonExceptionHandler; import com.protonvpn.android.utils.ProtonLogger; import com.protonvpn.android.utils.ProtonPreferences; import com.protonvpn.android.utils.Storage; @@ -120,6 +121,9 @@ private void initPreferences() { private void initSentry() { String sentryDsn = BuildConfig.DEBUG ? null : BuildConfig.Sentry_DSN; Sentry.init(sentryDsn, new AndroidSentryClientFactory(this)); + + Thread.UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(new ProtonExceptionHandler(currentHandler)); } private void initLeakCanary() { diff --git a/app/src/main/java/com/protonvpn/android/utils/ProtonExceptionHandler.kt b/app/src/main/java/com/protonvpn/android/utils/ProtonExceptionHandler.kt new file mode 100644 index 000000000..4f1d74e5c --- /dev/null +++ b/app/src/main/java/com/protonvpn/android/utils/ProtonExceptionHandler.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN is 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. + * + * ProtonVPN is 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. + * + * You should have received a copy of the GNU General Public License + * along with ProtonVPN. If not, see . + */ +package com.protonvpn.android.utils + +import java.io.PrintWriter +import java.io.StringWriter + +class ProtonExceptionHandler(val innerHandler: Thread.UncaughtExceptionHandler?) : Thread.UncaughtExceptionHandler { + + override fun uncaughtException(t: Thread, e: Throwable) { + val writer = StringWriter() + e.printStackTrace(PrintWriter(writer)) + ProtonLogger.logBlocking(writer.toString()) + innerHandler?.uncaughtException(t, e) + } +} diff --git a/app/src/main/java/com/protonvpn/android/utils/ProtonLoggerImpl.kt b/app/src/main/java/com/protonvpn/android/utils/ProtonLoggerImpl.kt index 935b28467..3dfdd16e7 100644 --- a/app/src/main/java/com/protonvpn/android/utils/ProtonLoggerImpl.kt +++ b/app/src/main/java/com/protonvpn/android/utils/ProtonLoggerImpl.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.sendBlocking +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.callbackFlow @@ -49,6 +50,7 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory import java.io.File @@ -191,8 +193,7 @@ open class ProtonLoggerImpl( private suspend fun processLogs() { messages.collect { message -> - logContext.putProperty("timeZone", timeZoneSuffix(GregorianCalendar())) - logger.debug(message) + logInternal(message) } } @@ -236,6 +237,16 @@ open class ProtonLoggerImpl( start() } + fun logInternal(msg: String) { + logContext.putProperty("timeZone", timeZoneSuffix(GregorianCalendar())) + logger.debug(msg) + } + + fun logBlocking(msg: String) = runBlocking(loggerDispatcher) { + logInternal(msg) + delay(100) + } + private class ChannelAdapter( private val channel: SendChannel, private val encoder: Encoder @@ -272,6 +283,10 @@ open class ProtonLoggerImpl( logMessageQueue.tryEmit(message) } + fun logBlocking(msg: String) { + backgroundLogger.logBlocking(msg) + } + fun getLogLines() = backgroundLogger.getLogLines() @Suppress("BlockingMethodInNonBlockingContext") From cb1329b534db21fd69ed1138048cee3007cbd36e Mon Sep 17 00:00:00 2001 From: Algirdas Pundzius Date: Fri, 3 Sep 2021 18:21:37 +0300 Subject: [PATCH 08/78] Add Puerto Rico coordinates to map view --- app/src/main/java/com/protonvpn/android/utils/CountryTools.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/protonvpn/android/utils/CountryTools.kt b/app/src/main/java/com/protonvpn/android/utils/CountryTools.kt index 269326099..ed77b782c 100644 --- a/app/src/main/java/com/protonvpn/android/utils/CountryTools.kt +++ b/app/src/main/java/com/protonvpn/android/utils/CountryTools.kt @@ -138,6 +138,7 @@ object CountryTools { "AE" to CountryData(3103.0, 976.0, Continent.AfricaAndMiddleEast), "CO" to CountryData(1100.0, 1339.0, Continent.America), "PE" to CountryData(1056.0, 1589.0, Continent.America), + "PR" to CountryData(1216.0, 1076.0, Continent.America), "VN" to CountryData(3961.0, 1144.0, Continent.Asia)) val codeToMapCountryName = mapOf( From 4211b11acd615f859a3f4919c25f24f67cb1cfa9 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Wed, 8 Sep 2021 12:07:57 +0200 Subject: [PATCH 09/78] Cherry-pick missing translations to 2.9.0.x releases --- app/src/main/res/values-de/strings.xml | 6 ++++++ app/src/main/res/values-es-rES/strings.xml | 8 +++++++- app/src/main/res/values-es-rMX/strings.xml | 8 +++++++- app/src/main/res/values-fa/strings.xml | 8 +++++++- app/src/main/res/values-fr/strings.xml | 8 +++++++- app/src/main/res/values-it/strings.xml | 8 +++++++- app/src/main/res/values-nl/strings.xml | 8 +++++++- app/src/main/res/values-pl/strings.xml | 8 +++++++- app/src/main/res/values-pt-rBR/strings.xml | 8 +++++++- app/src/main/res/values-pt-rPT/strings.xml | 8 +++++++- app/src/main/res/values-ru/strings.xml | 8 +++++++- 11 files changed, 76 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9bb3412f8..cf07349dd 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -443,4 +443,10 @@ Folgen Sie diesen Schritten, um VPN und Kill Switch dauerhaft zu aktivieren:”< Mittel Hoch + Um den „Secure Core”-Modus zu ändern, muss Ihre Verbindung neu gestartet werden. + Weiter + Verbindung zum VPN fehlgeschlagen. Bitte überprüfen Sie Ihre Verbindung und versuchen Sie es erneut. + WireGuard-Fehler %1$s + Der Server dieses Profils unterstützt WireGuard derzeit nicht. Um dennoch eine Verbindung herzustellen, versuchen Sie ein anderes Protokoll oder einen Server, der WireGuard unterstützt. + „VPN Accelerator“ ermöglicht eine Reihe einzigartiger leistungssteigernder Technologien, die die VPN-Geschwindigkeit um bis zu 400 %% erhöhen können. <a href="%1$s" >Mehr erfahren</a>. diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index ded5290d3..ac7831fc7 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -394,7 +394,7 @@ Siga estos pasos para activar VPN siempre activa y Kill Switch: " Lista de servidores No disponible VPN Accelerator - Notificaciones de VPN Accelerator + Notificaciones de VPN Accelerator Recibe notificaciones cuando VPN Accelerator te cambie a un servidor más rápido Reconexión necesaria Tu conexión debe reiniciarse para cambiar el modo VPN Accelerator. @@ -446,4 +446,10 @@ Siga estos pasos para activar VPN siempre activa y Kill Switch: " Media Alta + Tu conexión necesita ser reiniciada para cambiar al modo Secure Core. + Continuar + Couldn\'t connect to the VPN. Please check your connection and try again. + WireGuard error %1$s + This profile\'s server does not yet support WireGuard. To connect, try another protocol or a server supporting WireGuard. + VPN Accelerator permite un conjunto de tecnologías de mejora de rendimiento únicas que pueden aumentar la velocidade de la VPN hasta un 400%%. <a href="%1$s">Saber más</a>. diff --git a/app/src/main/res/values-es-rMX/strings.xml b/app/src/main/res/values-es-rMX/strings.xml index 76c2f068d..284be0e29 100644 --- a/app/src/main/res/values-es-rMX/strings.xml +++ b/app/src/main/res/values-es-rMX/strings.xml @@ -394,7 +394,7 @@ Siga estos pasos para activar la VPN Siempre Activa y Kill Switch:" Lista de servidores No disponible VPN Accelerator - Notificaciones de VPN Accelerator + Notificaciones de VPN Accelerator Recibir notificaciones cuando VPN Accelerator le cambie a un servidor más rápido Reconexión necesaria Su conexión debe reiniciarse para cambiar el modo VPN Accelerator. @@ -446,4 +446,10 @@ Siga estos pasos para activar la VPN Siempre Activa y Kill Switch:" Media Alta + Su conexión necesita ser reiniciada para cambiar el modo Secure Core. + Continuar + No se pudo conectar a la VPN. Por favor, verifique la conexión y vuelva a intentarlo. + Error de WireGuard %1$s + El servidor de este perfil aún no es compatible con WireGuard. Para conectarse, pruebe con otro protocolo o un servidor que admita WireGuard. + VPN Accelerator habilita un conjunto de tecnologías únicas que mejoran el rendimiento y pueden aumentar la velocidad de VPN hasta en un 400%%. <a href="%1$s">Más información</a>. diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 3d67578fe..043f48765 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -394,7 +394,7 @@ Kill Switch در صورت قطع شدن VPN دستگاه شما را از برق لیست سرور غیر قابل دسترس VPN Accelerator - اعلان‌های VPN Accelerator + اعلان‌های VPN Accelerator هنگامی که VPN Accelerator شما را به سروری سریع‌تر وصل می‌کند باخبر شوید اتصال مجدد لازم است برای تغییر حالت VPN Accelerator اتصال شما باید دوباره راه‌اندازی شود. @@ -446,4 +446,10 @@ Kill Switch در صورت قطع شدن VPN دستگاه شما را از برق متوسط بالا + برای تغییر حالت Secure Core اتصال شما باید دوباره راه‌اندازی شود. + ادامه + اتصال به VPN ممکن نبود. لطفاً اتصال خود را بررسی و دوباره امتحان کنید. + خطای %1$s WireGuard + سرور این پروفایل هنوز از WireGuard پشتیبانی نمی‌کند. برای اتصال، از یک پروتکل دیگر یا سروری که از WireGuard پشتیبانی می‌کند استفاده کنید. + VPN Accelerator مجموعه‌ای منحصر به فرد از فناوری‌های بهبود کارایی است که می‌تواند سرعت‌های VPN را تا ٪۴۰۰ افزایش دهد. <a href="%1$s">بیشتر یاد بگیرید</a>. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 802cdf1cb..73a7d9330 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -393,7 +393,7 @@ Suivez ces étapes pour activer le VPN permanent et Kill Switch : » Liste de serveurs Indisponible VPN Accelerator - Notifications VPN Accelerator + Notifications VPN Accelerator Soyez averti lorsque VPN Accelerator vous permet de passer à un serveur plus rapide Reconnexion nécessaire Votre connexion doit être redémarrée pour changer le mode VPN Accelerator. @@ -445,4 +445,10 @@ Suivez ces étapes pour activer le VPN permanent et Kill Switch : » Moyenne Élevée + Votre connexion doit être relancée pour changer le mode Secure Core. + Continuer + Impossible de se connecter au VPN. Veuillez vérifier votre connexion et réessayer. + Erreur WireGuard %1$s + Le serveur de ce profil ne prend pas encore en charge WireGuard. Pour vous connecter, essayez un autre protocole ou un serveur prenant en charge WireGuard. + VPN Accelerator active un ensemble de technologies uniques pour améliorer les performances qui peuvent augmenter les vitesses VPN jusqu\'à 400 %%. <a href="%1$s">En savoir plus</a>. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2623b61fe..11a8efd52 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -394,7 +394,7 @@ Segui questi passi per attivare VPN Sempre Attiva e Kill Switch:" Lista dei server Non disponibile VPN Accelerator - Notifiche VPN Accelerator + Notifiche VPN Accelerator Ricevi una notifica quando VPN Accelerator ti passa a un server più veloce Riconnessione necessaria La tua connessione deve essere riavviata per cambiare la modalità VPN Accelerator. @@ -446,4 +446,10 @@ Segui questi passi per attivare VPN Sempre Attiva e Kill Switch:" Medio Alto + La connessione deve essere riavviata per cambiare la modalità Secure Core. + Continua + Impossibile connettersi alla VPN. Controlla la tua connessione e riprova. + WireGuard errore %1$s + Il server di questo profilo non supporta ancora WireGuard. Per connetterti, prova un altro protocollo o un server che supporti WireGuard. + VPN Accelerator abilita una serie di tecnologie uniche di miglioramento delle prestazioni che possono aumentare la velocità della VPN fino al 400%%. <a href="%1$s">Scopri di più</a>. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index c647feb0a..afdc4b86e 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -394,7 +394,7 @@ Volg deze stappen om Altijd-aan VPN en Kill Switch in te schakelen:" Serverlijst Niet beschikbaar VPN Accelerator - VPN Accelerator meldingen + VPN Accelerator meldingen Krijg een melding wanneer VPN Accelerator u naar een snellere server schakelt Opnieuw verbinden nodig Uw verbinding moet opnieuw worden gestart om de VPN-Accelerator-modus te wijzigen. @@ -446,4 +446,10 @@ Volg deze stappen om Altijd-aan VPN en Kill Switch in te schakelen:" Medium Hoog + Your connection needs to be restarted to change the Secure Core mode. + Doorgaan + Couldn\'t connect to the VPN. Please check your connection and try again. + WireGuard error %1$s + This profile\'s server does not yet support WireGuard. To connect, try another protocol or a server supporting WireGuard. + VPN Accelerator enables a set of unique performance enhancing technologies which can increase VPN speed by up to 400%%. <a href="%1$s">Learn more</a>. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 5047bcee9..15bc70047 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -408,7 +408,7 @@ Wykonaj następujące kroki, aby włączyć stale aktywny VPN i funkcję Kill Sw Lista serwerów Niedostępny VPN Accelerator - Powiadomienia funkcji VPN Accelerator + Powiadomienia funkcji VPN Accelerator Otrzymaj powiadomienie, kiedy funkcja VPN Accelerator zmieni serwer na szybszy Ponowne połączenie jest wymagane Aby zmienić ustawienia funkcji VPN Accelerator, musisz ponownie nawiązać połączenie. @@ -460,4 +460,10 @@ Wykonaj następujące kroki, aby włączyć stale aktywny VPN i funkcję Kill Sw Średnie Duże + Aby zmienić tryb Secure Core, musisz ponownie nawiązać połączenie. + Kontynuuj + Nie można połączyć z usługą VPN. Sprawdź swoje połączenie i spróbuj ponownie. + Błąd protokołu WireGuard %1$s + Ten profil serwera nie obsługuje protokołu WireGuard. Aby połączyć się, wybierz inny protokół lub serwer wspierający WireGuard. + Funkcja VPN Accelerator udostępnia zestaw unikalnych technologii zwiększających wydajność, które mogą przyśpieszyć prędkość połączenia VPN do 400%%. <a href="%1$s">Dowiedz się więcej</a>. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 2719c1ee6..83aa9d445 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -394,7 +394,7 @@ Siga estes passos para ativar a VPN sempre ativa e o Kill Switch:" Lista de servidores Indisponível VPN Accelerator - Notificações do VPN Accelerator + Notificações do VPN Accelerator Seja notificado quando o VPN Accelerator mudar você para um servidor mais rápido Reconexão necessária Your connection needs to be restarted to change VPN Accelerator mode. @@ -446,4 +446,10 @@ Siga estes passos para ativar a VPN sempre ativa e o Kill Switch:" Média Alta + Sua conexão precisa ser reiniciada para alterar o modo Secure Core. + Continuar + Não foi possível conectar à VPN. Por favor verifique sua conexão e tente novamente. + Erro WireGuard%1$s + This profile\'s server does not yet support WireGuard. To connect, try another protocol or a server supporting WireGuard. + O VPN Acelerador permite um conjunto de tecnologias únicas de aprimoramento de desempenho que podem aumentar a velocidade da VPN em até 400%%. <a href="%1$s"> Saiba mais </a>. diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index fc802102a..7ca85eb1a 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -394,7 +394,7 @@ Siga estes passos para activar a VPN Sempre ligada e o Kill Switch:" Lista de servidores Indisponível VPN Accelerator - Notificações do VPN Accelerator + Notificações do VPN Accelerator Receba notificações quando o VPN Accelerator mudar para um servidor mais rápido Requerida nova ligação A sua ligação tem de ser reiniciada para alterar o modo do VPN Accelerator. @@ -446,4 +446,10 @@ Siga estes passos para activar a VPN Sempre ligada e o Kill Switch:" Média Alta + A ligação tem de ser reiniciada para alterar o modo Secure Core. + Continuar + Impossível ligar à VPN. Por favor, verifique a sua ligação e tente novamente. + Erro WireGuard %1$s + O servidor deste perfil ainda não suporta o WireGuard. Para ligar, tente outro protocolo ou um servidor que suporte o WireGuard. + O VPN Accelerator activa um conjunto único de tecnologias que melhoram o desempenho, podendo aumentar a velocidade da VPN até 400%%. <a href="%1$s">Saiba mais</a>. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index bb7454794..7f8b081d7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -408,7 +408,7 @@ Kill Switch не позволяет вашему устройству совер Список серверов Недоступно VPN Accelerator - Уведомления VPN Accelerator + Уведомления VPN Accelerator Получать уведомления при переключении на более быстрый сервер с помощью VPN Accelerator Требуется переподключение Необходимо перезапустить ваше подключение для изменения режима VPN Accelerator. @@ -460,4 +460,10 @@ Kill Switch не позволяет вашему устройству совер Средняя Высокая + Необходимо перезапустить ваше подключение для изменения режима Secure Core. + Продолжить + Не могу подключиться к VPN. Пожалуйста, проверьте своё подключение и попробуйте снова. + Ошибка WireGuard %1$s + This profile\'s server does not yet support WireGuard. To connect, try another protocol or a server supporting WireGuard. + VPN Accelerator позволяет использовать набор уникальных технологий для улучшения производительности, которые могут увеличить скорость VPN до 400%%. <a href="%1$s">Узнать больше</a>. From 6c64ca96328648337fbb80fa587a5e350867cdd9 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Wed, 8 Sep 2021 12:17:28 +0200 Subject: [PATCH 10/78] Fix comparing connection params for WireGuard --- .../protonvpn/android/models/vpn/ConnectionParamsWireguard.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/protonvpn/android/models/vpn/ConnectionParamsWireguard.kt b/app/src/main/java/com/protonvpn/android/models/vpn/ConnectionParamsWireguard.kt index a110266cb..959ccaee8 100644 --- a/app/src/main/java/com/protonvpn/android/models/vpn/ConnectionParamsWireguard.kt +++ b/app/src/main/java/com/protonvpn/android/models/vpn/ConnectionParamsWireguard.kt @@ -95,4 +95,7 @@ class ConnectionParamsWireguard( // Also ::/0 CIDR should not be used for IPv6 as it causes LAN connection issues return ipRangeSet.subnets().joinToString(", ") + ", 2000::/3" } + + override fun hasSameProtocolParams(other: ConnectionParams) = + super.hasSameProtocolParams(other) && other is ConnectionParamsWireguard && other.port == port } From ee12fc42d4d29779406235bda7b3ca83c8acc4ba Mon Sep 17 00:00:00 2001 From: Marcin Simonides Date: Mon, 13 Sep 2021 10:22:48 +0200 Subject: [PATCH 11/78] Try to release memory used by reading app packages [VPNAND-620]. --- .../java/com/protonvpn/di/MockAppModule.kt | 6 +++ .../com/protonvpn/android/di/AppModule.kt | 6 +++ .../android/ui/splittunneling/AppsDialog.kt | 44 ++++++++++++++++++- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/com/protonvpn/di/MockAppModule.kt b/app/src/androidTest/java/com/protonvpn/di/MockAppModule.kt index 0cf887574..923f473a9 100644 --- a/app/src/androidTest/java/com/protonvpn/di/MockAppModule.kt +++ b/app/src/androidTest/java/com/protonvpn/di/MockAppModule.kt @@ -18,6 +18,8 @@ */ package com.protonvpn.di +import android.app.ActivityManager +import android.content.Context import android.os.SystemClock import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingResource @@ -91,6 +93,10 @@ class MockAppModule { fun provideServerManager(userData: UserData) = ServerManager(ProtonApplication.getAppContext(), userData) + @Provides + fun provideActivityManager(): ActivityManager = + ProtonApplication.getAppContext().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + @Singleton @Provides fun provideServerListUpdater( diff --git a/app/src/main/java/com/protonvpn/android/di/AppModule.kt b/app/src/main/java/com/protonvpn/android/di/AppModule.kt index 23199e9c2..955642fa0 100644 --- a/app/src/main/java/com/protonvpn/android/di/AppModule.kt +++ b/app/src/main/java/com/protonvpn/android/di/AppModule.kt @@ -18,6 +18,8 @@ */ package com.protonvpn.android.di +import android.app.ActivityManager +import android.content.Context import android.os.SystemClock import com.google.gson.Gson import com.protonvpn.android.BuildConfig @@ -100,6 +102,10 @@ class AppModule { fun provideServerManager(userData: UserData) = ServerManager(ProtonApplication.getAppContext(), userData) + @Provides + fun provideActivityManager(): ActivityManager = + ProtonApplication.getAppContext().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + @Singleton @Provides fun provideServerListUpdater( diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt index 3ea7a0e17..2bb5730c8 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt @@ -19,7 +19,9 @@ package com.protonvpn.android.ui.splittunneling import android.Manifest +import android.app.ActivityManager import android.content.pm.PackageManager +import android.os.Process import android.view.View import android.widget.ProgressBar import android.widget.TextView @@ -32,12 +34,20 @@ import com.protonvpn.android.R import com.protonvpn.android.components.BaseDialog import com.protonvpn.android.components.ContentLayout import com.protonvpn.android.models.config.UserData +import com.protonvpn.android.utils.ProtonLogger import com.protonvpn.android.utils.sortedByLocaleAware import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull import javax.inject.Inject +// Since API23, @see MemoryInfo.getMemoryStat +private const val MEMORY_STAT_CODE = "summary.code" +private const val GC_TIMEOUT_MS = 1000L +private const val GC_CHECK_DELAY_MS = 100L + @ContentLayout(R.layout.dialog_split_tunnel) class AppsDialog : BaseDialog() { private lateinit var adapter: AppsAdapter @@ -56,6 +66,8 @@ class AppsDialog : BaseDialog() { @Inject lateinit var userData: UserData + @Inject + lateinit var activityManager: ActivityManager override fun onViewCreated() { list.layoutManager = LinearLayoutManager(activity) @@ -100,13 +112,41 @@ class AppsDialog : BaseDialog() { private suspend fun getInstalledInternetApps( packageManager: PackageManager ): List = withContext(Dispatchers.IO) { - packageManager.getInstalledApplications( - PackageManager.GET_META_DATA + val initialCodeSizeKb = getCodeMemoryKb() + ProtonLogger.log("getInstalledInternetApps: initial code size: $initialCodeSizeKb KB") + + val apps = packageManager.getInstalledApplications( + 0 ).filter { appInfo -> (packageManager.checkPermission(Manifest.permission.INTERNET, appInfo.packageName) == PackageManager.PERMISSION_GRANTED) }.map { appInfo -> SelectedApplicationEntry(packageManager, appInfo) } + + ProtonLogger.log("getInstalledInternetApps: final code size: ${getCodeMemoryKb()} KB") + tryReleaseMemory(initialCodeSizeKb) + apps + } + + @Suppress("ExplicitGarbageCollectionCall") + private suspend fun tryReleaseMemory(initialCodeSizeKb: Int) { + // Loading application metadata with loadLabel and loadIcon increases memory use in the + // "code" category. On some devices it's not released immediately causing OOMs. + // Try to force GC (System.gc() doesn't work). + Runtime.getRuntime().gc() + withTimeoutOrNull(GC_TIMEOUT_MS) { + do { + delay(GC_CHECK_DELAY_MS) + val codeSizeKb = getCodeMemoryKb() + ProtonLogger.log("getInstalledInternetApps: code size $codeSizeKb KB") + } while (codeSizeKb > 2 * initialCodeSizeKb) + } + } + + private fun getCodeMemoryKb(): Int { + val myPid = intArrayOf(Process.myPid()) + val memoryInfo = activityManager.getProcessMemoryInfo(myPid)[0] + return memoryInfo.getMemoryStat(MEMORY_STAT_CODE).toInt() } } From ae45f69f3eec306d32f28dacb90589b79afd4813 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Tue, 14 Sep 2021 12:09:12 +0200 Subject: [PATCH 12/78] Log VPN ports selected for pinging --- app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt | 1 + .../java/com/protonvpn/android/vpn/openvpn/OpenVpnBackend.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt index 1c4343f49..126b62e30 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt @@ -398,6 +398,7 @@ abstract class VpnBackend( else ports.shuffled() + ProtonLogger.log("${connectingDomain.entryDomain}/$vpnProtocol port scan: $candidatePorts") candidatePorts.parallelSearch(waitForAll) { VpnPing.pingSync(connectingDomain.entryIp, it.toLong(), connectingDomain.publicKeyX25519, SCAN_TIMEOUT_MILLIS) diff --git a/app/src/main/java/com/protonvpn/android/vpn/openvpn/OpenVpnBackend.kt b/app/src/main/java/com/protonvpn/android/vpn/openvpn/OpenVpnBackend.kt index a47f73198..af6360133 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/openvpn/OpenVpnBackend.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/openvpn/OpenVpnBackend.kt @@ -122,6 +122,7 @@ class OpenVpnBackend( val tcpPingData = getPingData(tcp = true) val tcpPorts = async { val ports = samplePorts(openVpnPorts.tcpPorts, numberOfPorts) + ProtonLogger.log("${connectingDomain.entryDomain}/OpenVPN/TCP port scan: $ports") ports.parallelSearch(waitForAll) { port -> NetUtils.ping(connectingDomain.entryIp, port, tcpPingData, tcp = true) } From 7f897f98dea9e3b7b7509b72176a5a373ec62408 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Tue, 14 Sep 2021 12:13:31 +0200 Subject: [PATCH 13/78] Use default port for WireGuard when not pinging --- .../com/protonvpn/android/vpn/wireguard/WireguardBackend.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/protonvpn/android/vpn/wireguard/WireguardBackend.kt b/app/src/main/java/com/protonvpn/android/vpn/wireguard/WireguardBackend.kt index e9463e4fa..c8ffc6f41 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/wireguard/WireguardBackend.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/wireguard/WireguardBackend.kt @@ -89,7 +89,7 @@ class WireguardBackend( val selectedPorts = if (scan) scanUdpPorts(connectingDomain, ports, numberOfPorts, waitForAll) else - listOfNotNull(ports.randomOrNull()) + listOfNotNull(ports.first()) return selectedPorts.map { port -> PrepareResult( this, From 42bfaeaeac4d418f1548f68351ccb6c52cc42dd2 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Tue, 14 Sep 2021 11:54:05 +0200 Subject: [PATCH 14/78] Use IKEv2 when all pings fail only with Smart Protocol [VPNAND-622] --- .../protonvpn/tests/vpn/VpnConnectionTests.kt | 7 +++---- .../android/vpn/VpnConnectionManager.kt | 16 ++++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt b/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt index f95c0a284..1cc6ce22e 100644 --- a/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt +++ b/app/src/androidTest/java/com/protonvpn/tests/vpn/VpnConnectionTests.kt @@ -251,7 +251,7 @@ class VpnConnectionTests { scope.advanceUntilIdle() coVerify(exactly = 1) { - mockWireguard.prepareForConnection(any(), any(), true) + mockWireguard.prepareForConnection(any(), any(), false) mockWireguard.createAgentConnection(any(), any(), any()) } Assert.assertEquals(VpnState.Connected, monitor.state) @@ -278,7 +278,7 @@ class VpnConnectionTests { manager.connect(context, profileWireguard) coVerify(exactly = 1) { - mockWireguard.prepareForConnection(any(), any(), true) + mockWireguard.prepareForConnection(any(), any(), false) } coVerify(exactly = 0) { mockWireguard.connectToLocalAgent() @@ -299,8 +299,7 @@ class VpnConnectionTests { @Test fun guestHoleFail() = runBlockingTest { mockOpenVpn.failScanning = true - mockStrongSwan.failScanning = true - mockStrongSwan.stateOnConnect = VpnState.Disabled + mockOpenVpn.stateOnConnect = VpnState.Disabled val guestHole = GuestHole(scope, serverManager, monitor, manager) val result = guestHole.call(context) { diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt index eefd75be6..9522d54a2 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt @@ -294,16 +294,16 @@ open class VpnConnectionManager( setSelfState(VpnState.ScanningPorts) var protocol = profile.getProtocol(userData) - if (!networkManager.isConnectedToNetwork() && protocol == VpnProtocol.Smart) - protocol = userData.manualProtocol - var preparedConnection = backendProvider.prepareConnection(protocol, profile, server) + val hasNetwork = networkManager.isConnectedToNetwork() + if (!hasNetwork && protocol == VpnProtocol.Smart) + protocol = FALLBACK_PROTOCOL + var preparedConnection = backendProvider.prepareConnection(protocol, profile, server, alwaysScan = hasNetwork) if (preparedConnection == null) { - ProtonLogger.log("Smart protocol: no protocol available for ${server.domain}, " + - "falling back to ${userData.manualProtocol}") + val fallbackProtocol = if (protocol == VpnProtocol.Smart) FALLBACK_PROTOCOL else protocol + ProtonLogger.log("No response for ${server.domain}, using fallback $fallbackProtocol") - // If port scanning fails (because e.g. some temporary network situation) just connect - // without smart protocol - preparedConnection = backendProvider.prepareConnection(FALLBACK_PROTOCOL, profile, server, false)!! + // If port scanning fails (because e.g. some temporary network situation) just connect without pinging + preparedConnection = backendProvider.prepareConnection(fallbackProtocol, profile, server, false)!! } preparedConnect(preparedConnection) From 51e67403ed742fab36c1cc036ed7611edb3b8031 Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Wed, 15 Sep 2021 13:21:26 +0200 Subject: [PATCH 15/78] Don't trigger reconnection on local agent's unreachable --- app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt index 126b62e30..033e56d5c 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt @@ -285,7 +285,9 @@ abstract class VpnBackend( VpnState.Connected agentConstants.stateConnectionError, agentConstants.stateServerUnreachable -> - VpnState.Error(ErrorType.UNREACHABLE_INTERNAL) + // When unreachable comes from local agent it means VPN tunnel is still active, set UNREACHABLE + // instead of UNREACHABLE_INETRNAL to skip recovery with pings, as those won't help in this situation. + VpnState.Error(ErrorType.UNREACHABLE) agentConstants.stateClientCertificateExpiredError -> { refreshCertOnLocalAgent(force = false) VpnState.Connecting From 80d8d2bc09d2b2a8177554c84da12849c447a85b Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Wed, 15 Sep 2021 16:17:17 +0200 Subject: [PATCH 16/78] Handle generic final errors from Local Agent after specific errors --- app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt index 033e56d5c..ccd35fcd4 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnBackend.kt @@ -160,10 +160,6 @@ abstract class VpnBackend( override fun onError(code: Long, description: String) { ProtonLogger.log("Local agent error: $code $description") - if (agent?.status?.reason?.final == true) { - setLocalAgentError(description) - return - } when (code) { agentConstants.errorCodeMaxSessionsBasic, agentConstants.errorCodeMaxSessionsFree, @@ -197,6 +193,10 @@ abstract class VpnBackend( agentConstants.errorCodeRestrictedServer -> // Server should unblock eventually, but we need to keep track and provide watchdog if necessary. ProtonLogger.log("Local agent: Restricted server, waiting...") + else -> { + if (agent?.status?.reason?.final == true) + setLocalAgentError(description) + } } } From db165a84a1f6d9a1325f3d2fb000bf239ad76f4e Mon Sep 17 00:00:00 2001 From: Mateusz Markowicz Date: Thu, 16 Sep 2021 17:08:40 +0200 Subject: [PATCH 17/78] Fix NPE in NetworkUtils.getLocalNetworks --- openvpn/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openvpn/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java b/openvpn/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java index 404491184..25a180c3f 100644 --- a/openvpn/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java +++ b/openvpn/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java @@ -27,6 +27,9 @@ public static Vector getLocalNetworks(Context c, boolean ipv6) { NetworkCapabilities nc = conn.getNetworkCapabilities(network); + if (nc == null) + continue; + // Skip VPN networks like ourselves if (nc.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) continue; From c429db00ae5d788540c7a6b97abc20f875feea1c Mon Sep 17 00:00:00 2001 From: Marcin Simonides Date: Mon, 20 Sep 2021 14:35:58 +0200 Subject: [PATCH 18/78] Move creation logic out of SelectedApplicationEntry. --- .../ui/splittunneling/AppsAdapter.java | 4 ++-- .../android/ui/splittunneling/AppsDialog.kt | 16 ++++++++++++--- .../SelectedApplicationEntry.java | 20 +++++++++---------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsAdapter.java b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsAdapter.java index 361dc55a7..9cdcc7a33 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsAdapter.java +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsAdapter.java @@ -110,10 +110,10 @@ public AppsViewHolder(View view) { public void layoutAddRemove() { item.setSelected(!item.isSelected()); if (item.isSelected()) { - addApp(item.getInfo().packageName); + addApp(item.getPackageName()); } else { - removeApp(item.getInfo().packageName); + removeApp(item.getPackageName()); } initSelection(); } diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt index 2bb5730c8..02c051ca8 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt @@ -20,6 +20,7 @@ package com.protonvpn.android.ui.splittunneling import android.Manifest import android.app.ActivityManager +import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.os.Process import android.view.View @@ -82,7 +83,7 @@ class AppsDialog : BaseDialog() { val allApps = getInstalledInternetApps(requireContext().packageManager) val sortedApps = withContext(Dispatchers.Default) { allApps.forEach { app -> - if (selection.contains(app.info.packageName)) { + if (selection.contains(app.packageName)) { app.isSelected = true } } @@ -103,7 +104,7 @@ class AppsDialog : BaseDialog() { private fun removeUninstalledApps(userData: UserData, allApps: List) { val userDataAppPackages = userData.splitTunnelApps val allAppPackages = HashSet(allApps.size) - allApps.mapTo(allAppPackages) { it.info.packageName } + allApps.mapTo(allAppPackages) { it.packageName } userDataAppPackages .filterNot { allAppPackages.contains(it) } .forEach { userData.removeAppFromSplitTunnel(it) } @@ -121,7 +122,7 @@ class AppsDialog : BaseDialog() { (packageManager.checkPermission(Manifest.permission.INTERNET, appInfo.packageName) == PackageManager.PERMISSION_GRANTED) }.map { appInfo -> - SelectedApplicationEntry(packageManager, appInfo) + getAppMetadata(packageManager, appInfo) } ProtonLogger.log("getInstalledInternetApps: final code size: ${getCodeMemoryKb()} KB") @@ -129,6 +130,15 @@ class AppsDialog : BaseDialog() { apps } + private fun getAppMetadata( + packageManager: PackageManager, + appInfo: ApplicationInfo + ): SelectedApplicationEntry { + val label = appInfo.loadLabel(packageManager) + val icon = appInfo.loadIcon(packageManager) + return SelectedApplicationEntry(appInfo.packageName, label.toString(), icon) + } + @Suppress("ExplicitGarbageCollectionCall") private suspend fun tryReleaseMemory(initialCodeSizeKb: Int) { // Loading application metadata with loadLabel and loadIcon increases memory use in the diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/SelectedApplicationEntry.java b/app/src/main/java/com/protonvpn/android/ui/splittunneling/SelectedApplicationEntry.java index 96551fa35..6a19773fc 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/SelectedApplicationEntry.java +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/SelectedApplicationEntry.java @@ -18,8 +18,6 @@ */ package com.protonvpn.android.ui.splittunneling; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import java.text.Collator; @@ -28,16 +26,15 @@ public class SelectedApplicationEntry implements Comparable { - private final ApplicationInfo mInfo; + private final String mPackageName; private final Drawable mIcon; private final String mName; private boolean mSelected; - public SelectedApplicationEntry(PackageManager packageManager, ApplicationInfo info) { - mInfo = info; - CharSequence name = info.loadLabel(packageManager); - mName = name == null ? info.packageName : name.toString(); - mIcon = info.loadIcon(packageManager); + public SelectedApplicationEntry(@NonNull String packageName, @NonNull String label, @NonNull Drawable icon) { + mPackageName = packageName; + mName = label; + mIcon = icon; } public void setSelected(boolean selected) { @@ -48,8 +45,9 @@ public boolean isSelected() { return mSelected; } - public ApplicationInfo getInfo() { - return mInfo; + @NonNull + public String getPackageName() { + return mPackageName; } public Drawable getIcon() { @@ -65,4 +63,4 @@ public String toString() { public int compareTo(@NonNull SelectedApplicationEntry another) { return Collator.getInstance().compare(toString(), another.toString()); } -} \ No newline at end of file +} From 07152813534424168eb101d75ae6ac1f4bc6d58f Mon Sep 17 00:00:00 2001 From: Marcin Simonides Date: Mon, 20 Sep 2021 15:00:15 +0200 Subject: [PATCH 19/78] Load app label and icon via resources [VPNAND-631]. This way it is possible to release resources earlier using Assets.close(). --- .../android/ui/splittunneling/AppsDialog.kt | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt index 02c051ca8..c6287d23c 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt @@ -22,10 +22,15 @@ import android.Manifest import android.app.ActivityManager import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +import android.content.res.Resources +import android.graphics.drawable.Drawable +import android.os.Build import android.os.Process +import android.text.TextUtils import android.view.View import android.widget.ProgressBar import android.widget.TextView +import androidx.core.content.res.ResourcesCompat import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -49,6 +54,9 @@ private const val MEMORY_STAT_CODE = "summary.code" private const val GC_TIMEOUT_MS = 1000L private const val GC_CHECK_DELAY_MS = 100L +private const val MAX_SAFE_LABEL_CHARS = 1000 +private const val MAX_SAFE_LABEL_DP = 500f + @ContentLayout(R.layout.dialog_split_tunnel) class AppsDialog : BaseDialog() { private lateinit var adapter: AppsAdapter @@ -134,11 +142,36 @@ class AppsDialog : BaseDialog() { packageManager: PackageManager, appInfo: ApplicationInfo ): SelectedApplicationEntry { - val label = appInfo.loadLabel(packageManager) - val icon = appInfo.loadIcon(packageManager) + val appResources = packageManager.getResourcesForApplication(appInfo) + val label = getLabel(appResources, appInfo.labelRes) ?: appInfo.packageName + val icon = getIcon(appResources, appInfo.icon) ?: packageManager.defaultActivityIcon + appResources.assets.close() return SelectedApplicationEntry(appInfo.packageName, label.toString(), icon) } + private fun getLabel(appResources: Resources, labelResource: Int): String? = + try { + val label = appResources.getString(labelResource) + if (Build.VERSION.SDK_INT >= 29) { + val flags = TextUtils.SAFE_STRING_FLAG_TRIM or TextUtils.SAFE_STRING_FLAG_FIRST_LINE + TextUtils + .makeSafeForPresentation(label, MAX_SAFE_LABEL_CHARS, MAX_SAFE_LABEL_DP, flags) + .toString() + } else { + label + } + } catch (e: Resources.NotFoundException) { + // For some app packages resources fail to load, as if the ID is incorrect. + null + } + + private fun getIcon(appResources: Resources, iconResource: Int): Drawable? = + try { + ResourcesCompat.getDrawable(appResources, iconResource, null) + } catch (e: Resources.NotFoundException) { + null + } + @Suppress("ExplicitGarbageCollectionCall") private suspend fun tryReleaseMemory(initialCodeSizeKb: Int) { // Loading application metadata with loadLabel and loadIcon increases memory use in the From 0e9dfef12cdf3f8e92620e580d0c2379d656b004 Mon Sep 17 00:00:00 2001 From: Marcin Simonides Date: Mon, 20 Sep 2021 15:03:28 +0200 Subject: [PATCH 20/78] Remove the code that tries to trigger GC. It only helps on one tested device and shouldn't be necessary now. --- .../android/ui/splittunneling/AppsDialog.kt | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt index c6287d23c..a924f1ab9 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt @@ -25,7 +25,6 @@ import android.content.pm.PackageManager import android.content.res.Resources import android.graphics.drawable.Drawable import android.os.Build -import android.os.Process import android.text.TextUtils import android.view.View import android.widget.ProgressBar @@ -40,20 +39,12 @@ import com.protonvpn.android.R import com.protonvpn.android.components.BaseDialog import com.protonvpn.android.components.ContentLayout import com.protonvpn.android.models.config.UserData -import com.protonvpn.android.utils.ProtonLogger import com.protonvpn.android.utils.sortedByLocaleAware import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull import javax.inject.Inject -// Since API23, @see MemoryInfo.getMemoryStat -private const val MEMORY_STAT_CODE = "summary.code" -private const val GC_TIMEOUT_MS = 1000L -private const val GC_CHECK_DELAY_MS = 100L - private const val MAX_SAFE_LABEL_CHARS = 1000 private const val MAX_SAFE_LABEL_DP = 500f @@ -121,9 +112,6 @@ class AppsDialog : BaseDialog() { private suspend fun getInstalledInternetApps( packageManager: PackageManager ): List = withContext(Dispatchers.IO) { - val initialCodeSizeKb = getCodeMemoryKb() - ProtonLogger.log("getInstalledInternetApps: initial code size: $initialCodeSizeKb KB") - val apps = packageManager.getInstalledApplications( 0 ).filter { appInfo -> @@ -132,9 +120,6 @@ class AppsDialog : BaseDialog() { }.map { appInfo -> getAppMetadata(packageManager, appInfo) } - - ProtonLogger.log("getInstalledInternetApps: final code size: ${getCodeMemoryKb()} KB") - tryReleaseMemory(initialCodeSizeKb) apps } @@ -171,25 +156,4 @@ class AppsDialog : BaseDialog() { } catch (e: Resources.NotFoundException) { null } - - @Suppress("ExplicitGarbageCollectionCall") - private suspend fun tryReleaseMemory(initialCodeSizeKb: Int) { - // Loading application metadata with loadLabel and loadIcon increases memory use in the - // "code" category. On some devices it's not released immediately causing OOMs. - // Try to force GC (System.gc() doesn't work). - Runtime.getRuntime().gc() - withTimeoutOrNull(GC_TIMEOUT_MS) { - do { - delay(GC_CHECK_DELAY_MS) - val codeSizeKb = getCodeMemoryKb() - ProtonLogger.log("getInstalledInternetApps: code size $codeSizeKb KB") - } while (codeSizeKb > 2 * initialCodeSizeKb) - } - } - - private fun getCodeMemoryKb(): Int { - val myPid = intArrayOf(Process.myPid()) - val memoryInfo = activityManager.getProcessMemoryInfo(myPid)[0] - return memoryInfo.getMemoryStat(MEMORY_STAT_CODE).toInt() - } } From 463e32e1b70cd1d6204f4ad4c56e00feaa5c0f26 Mon Sep 17 00:00:00 2001 From: Marcin Simonides Date: Mon, 20 Sep 2021 15:21:23 +0200 Subject: [PATCH 21/78] Use dynamic row height to accomodate for custom font size. --- app/src/main/res/layout/item_app.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/item_app.xml b/app/src/main/res/layout/item_app.xml index c68d0b9dd..4b80c0c53 100644 --- a/app/src/main/res/layout/item_app.xml +++ b/app/src/main/res/layout/item_app.xml @@ -21,7 +21,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="58dp" + android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal" tools:background="@color/grey"> @@ -42,9 +42,10 @@ Date: Mon, 20 Sep 2021 21:53:01 +0200 Subject: [PATCH 22/78] Show UI state for port scanning also without Smart Protocol --- .../java/com/protonvpn/android/vpn/VpnConnectionManager.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt b/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt index 9522d54a2..51f4ab932 100644 --- a/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt +++ b/app/src/main/java/com/protonvpn/android/vpn/VpnConnectionManager.kt @@ -290,8 +290,7 @@ open class VpnConnectionManager( ProtonLogger.log("Disconnected, start connecting to new server.") } - if (profile.getProtocol(userData) == VpnProtocol.Smart) - setSelfState(VpnState.ScanningPorts) + setSelfState(VpnState.ScanningPorts) var protocol = profile.getProtocol(userData) val hasNetwork = networkManager.isConnectedToNetwork() From 909eddce06150be99587c5af10c7321886aba8c7 Mon Sep 17 00:00:00 2001 From: Marcin Simonides Date: Tue, 21 Sep 2021 11:21:44 +0200 Subject: [PATCH 23/78] Don't close own assets [VPNAND-631]. Also cancel the mapping loop in getInstalledInternetApps when the coroutine is canceled. --- .../android/ui/splittunneling/AppsDialog.kt | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt index a924f1ab9..57d0344de 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt @@ -20,6 +20,7 @@ package com.protonvpn.android.ui.splittunneling import android.Manifest import android.app.ActivityManager +import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.res.Resources @@ -41,6 +42,7 @@ import com.protonvpn.android.components.ContentLayout import com.protonvpn.android.models.config.UserData import com.protonvpn.android.utils.sortedByLocaleAware import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -79,7 +81,7 @@ class AppsDialog : BaseDialog() { val selection = userData.splitTunnelApps.toSet() viewLifecycleOwner.lifecycleScope.launch { - val allApps = getInstalledInternetApps(requireContext().packageManager) + val allApps = getInstalledInternetApps(requireContext().applicationContext) val sortedApps = withContext(Dispatchers.Default) { allApps.forEach { app -> if (selection.contains(app.packageName)) { @@ -109,28 +111,36 @@ class AppsDialog : BaseDialog() { .forEach { userData.removeAppFromSplitTunnel(it) } } + // Pass app context to avoid problems when the dialog is closed and the IO thread is still + // accessing the PackageManager. private suspend fun getInstalledInternetApps( - packageManager: PackageManager + appContext: Context ): List = withContext(Dispatchers.IO) { + val packageManager = appContext.packageManager + val ourPackageName = appContext.packageName val apps = packageManager.getInstalledApplications( 0 ).filter { appInfo -> (packageManager.checkPermission(Manifest.permission.INTERNET, appInfo.packageName) == PackageManager.PERMISSION_GRANTED) }.map { appInfo -> - getAppMetadata(packageManager, appInfo) + ensureActive() + getAppMetadata(packageManager, appInfo, ourPackageName) } apps } private fun getAppMetadata( packageManager: PackageManager, - appInfo: ApplicationInfo + appInfo: ApplicationInfo, + ourPackageName: String ): SelectedApplicationEntry { val appResources = packageManager.getResourcesForApplication(appInfo) val label = getLabel(appResources, appInfo.labelRes) ?: appInfo.packageName val icon = getIcon(appResources, appInfo.icon) ?: packageManager.defaultActivityIcon - appResources.assets.close() + if (appInfo.packageName != ourPackageName) { + appResources.assets.close() + } return SelectedApplicationEntry(appInfo.packageName, label.toString(), icon) } From 6aafe3c202aa422cc5e9d74e374b0e51262900e1 Mon Sep 17 00:00:00 2001 From: Marcin Simonides Date: Wed, 22 Sep 2021 08:31:49 +0200 Subject: [PATCH 24/78] Revert "Don't close own assets [VPNAND-631]." This reverts commit 909eddce06150be99587c5af10c7321886aba8c7. Calling Resources.getAssets().close() breaks cache of AssetManagers in ResourceManager on Android 9 and older so this approach is not usable. --- .../android/ui/splittunneling/AppsDialog.kt | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt index 57d0344de..a924f1ab9 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt @@ -20,7 +20,6 @@ package com.protonvpn.android.ui.splittunneling import android.Manifest import android.app.ActivityManager -import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.res.Resources @@ -42,7 +41,6 @@ import com.protonvpn.android.components.ContentLayout import com.protonvpn.android.models.config.UserData import com.protonvpn.android.utils.sortedByLocaleAware import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -81,7 +79,7 @@ class AppsDialog : BaseDialog() { val selection = userData.splitTunnelApps.toSet() viewLifecycleOwner.lifecycleScope.launch { - val allApps = getInstalledInternetApps(requireContext().applicationContext) + val allApps = getInstalledInternetApps(requireContext().packageManager) val sortedApps = withContext(Dispatchers.Default) { allApps.forEach { app -> if (selection.contains(app.packageName)) { @@ -111,36 +109,28 @@ class AppsDialog : BaseDialog() { .forEach { userData.removeAppFromSplitTunnel(it) } } - // Pass app context to avoid problems when the dialog is closed and the IO thread is still - // accessing the PackageManager. private suspend fun getInstalledInternetApps( - appContext: Context + packageManager: PackageManager ): List = withContext(Dispatchers.IO) { - val packageManager = appContext.packageManager - val ourPackageName = appContext.packageName val apps = packageManager.getInstalledApplications( 0 ).filter { appInfo -> (packageManager.checkPermission(Manifest.permission.INTERNET, appInfo.packageName) == PackageManager.PERMISSION_GRANTED) }.map { appInfo -> - ensureActive() - getAppMetadata(packageManager, appInfo, ourPackageName) + getAppMetadata(packageManager, appInfo) } apps } private fun getAppMetadata( packageManager: PackageManager, - appInfo: ApplicationInfo, - ourPackageName: String + appInfo: ApplicationInfo ): SelectedApplicationEntry { val appResources = packageManager.getResourcesForApplication(appInfo) val label = getLabel(appResources, appInfo.labelRes) ?: appInfo.packageName val icon = getIcon(appResources, appInfo.icon) ?: packageManager.defaultActivityIcon - if (appInfo.packageName != ourPackageName) { - appResources.assets.close() - } + appResources.assets.close() return SelectedApplicationEntry(appInfo.packageName, label.toString(), icon) } From 080a04566e8c4c90e68e0e7fec4f6147003441cc Mon Sep 17 00:00:00 2001 From: Marcin Simonides Date: Wed, 22 Sep 2021 08:39:13 +0200 Subject: [PATCH 25/78] Revert "Load app label and icon via resources [VPNAND-631]." This reverts commit 07152813534424168eb101d75ae6ac1f4bc6d58f. Calling Resources.getAssets().close() breaks cache of AssetManagers in ResourceManager on Android 9 and older so this approach is not usable. --- .../android/ui/splittunneling/AppsDialog.kt | 37 +------------------ 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt index a924f1ab9..c547fbeb1 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt @@ -22,14 +22,9 @@ import android.Manifest import android.app.ActivityManager import android.content.pm.ApplicationInfo import android.content.pm.PackageManager -import android.content.res.Resources -import android.graphics.drawable.Drawable -import android.os.Build -import android.text.TextUtils import android.view.View import android.widget.ProgressBar import android.widget.TextView -import androidx.core.content.res.ResourcesCompat import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -45,9 +40,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject -private const val MAX_SAFE_LABEL_CHARS = 1000 -private const val MAX_SAFE_LABEL_DP = 500f - @ContentLayout(R.layout.dialog_split_tunnel) class AppsDialog : BaseDialog() { private lateinit var adapter: AppsAdapter @@ -127,33 +119,8 @@ class AppsDialog : BaseDialog() { packageManager: PackageManager, appInfo: ApplicationInfo ): SelectedApplicationEntry { - val appResources = packageManager.getResourcesForApplication(appInfo) - val label = getLabel(appResources, appInfo.labelRes) ?: appInfo.packageName - val icon = getIcon(appResources, appInfo.icon) ?: packageManager.defaultActivityIcon - appResources.assets.close() + val label = appInfo.loadLabel(packageManager) + val icon = appInfo.loadIcon(packageManager) return SelectedApplicationEntry(appInfo.packageName, label.toString(), icon) } - - private fun getLabel(appResources: Resources, labelResource: Int): String? = - try { - val label = appResources.getString(labelResource) - if (Build.VERSION.SDK_INT >= 29) { - val flags = TextUtils.SAFE_STRING_FLAG_TRIM or TextUtils.SAFE_STRING_FLAG_FIRST_LINE - TextUtils - .makeSafeForPresentation(label, MAX_SAFE_LABEL_CHARS, MAX_SAFE_LABEL_DP, flags) - .toString() - } else { - label - } - } catch (e: Resources.NotFoundException) { - // For some app packages resources fail to load, as if the ID is incorrect. - null - } - - private fun getIcon(appResources: Resources, iconResource: Int): Drawable? = - try { - ResourcesCompat.getDrawable(appResources, iconResource, null) - } catch (e: Resources.NotFoundException) { - null - } } From ef9a4eff573f1f12512a65bb9114eddb59f56a22 Mon Sep 17 00:00:00 2001 From: Marcin Simonides Date: Fri, 17 Sep 2021 16:11:08 +0200 Subject: [PATCH 26/78] Split the list of apps in Exclude Apps dialog [VPNAND-629]. --- .../ui/splittunneling/AppViewHolder.kt | 95 ++++++++++++ .../ui/splittunneling/AppsAdapter.java | 133 ----------------- .../android/ui/splittunneling/AppsDialog.kt | 108 ++++++++++---- .../protonvpn/android/utils/BindableItemEx.kt | 1 + app/src/main/res/layout/item_app.xml | 138 +++++++++--------- app/src/main/res/layout/item_apps_header.xml | 30 ++++ .../res/layout/item_apps_load_system_apps.xml | 44 ++++++ app/src/main/res/values/strings.xml | 3 + 8 files changed, 327 insertions(+), 225 deletions(-) create mode 100644 app/src/main/java/com/protonvpn/android/ui/splittunneling/AppViewHolder.kt delete mode 100644 app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsAdapter.java create mode 100644 app/src/main/res/layout/item_apps_header.xml create mode 100644 app/src/main/res/layout/item_apps_load_system_apps.xml diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppViewHolder.kt b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppViewHolder.kt new file mode 100644 index 000000000..76c43ce7f --- /dev/null +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppViewHolder.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021. Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN is 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. + * + * ProtonVPN is 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. + * + * You should have received a copy of the GNU General Public License + * along with ProtonVPN. If not, see . + */ + +package com.protonvpn.android.ui.splittunneling + +import androidx.core.view.isVisible +import com.protonvpn.android.R +import com.protonvpn.android.databinding.ItemAppBinding +import com.protonvpn.android.databinding.ItemAppsHeaderBinding +import com.protonvpn.android.databinding.ItemAppsLoadSystemAppsBinding +import com.protonvpn.android.utils.BindableItemEx +import com.xwray.groupie.databinding.BindableItem + +class AppViewHolder( + private val item: SelectedApplicationEntry, + private val onAdd: (SelectedApplicationEntry) -> Unit, + private val onRemove: (SelectedApplicationEntry) -> Unit +) : BindableItemEx() { + + override fun bind(viewBinding: ItemAppBinding, position: Int) { + super.bind(viewBinding, position) + with(viewBinding) { + imageIcon.setImageDrawable(item.icon) + textName.text = item.toString() + textAdd.setOnClickListener { + onAdd(item) + toggleSelection() + } + clearIcon.setOnClickListener { + onRemove(item) + toggleSelection() + } + updateSelection() + } + } + + private fun toggleSelection() { + item.isSelected = !item.isSelected + updateSelection() + } + + private fun updateSelection() { + with(binding) { + clearIcon.isVisible = item.isSelected + textAdd.isVisible = !item.isSelected + } + } + + override fun getLayout(): Int = R.layout.item_app + + override fun clear() { + } +} + +class AppsHeaderViewHolder(private val titleRes: Int) + : BindableItem() { + override fun bind(viewBinding: ItemAppsHeaderBinding, position: Int) { + viewBinding.textHeader.setText(titleRes) + } + + override fun getLayout(): Int = R.layout.item_apps_header +} + +class LoadSystemAppsViewHolder( + private val onLoadClicked: () -> Unit +) : BindableItem() { + + override fun bind(viewBinding: ItemAppsLoadSystemAppsBinding, position: Int) { + with(viewBinding) { + buttonLoadSystemApps.setOnClickListener { + buttonLoadSystemApps.isVisible = false + progress.isVisible = true + onLoadClicked() + } + } + } + + override fun getLayout(): Int = R.layout.item_apps_load_system_apps +} diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsAdapter.java b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsAdapter.java deleted file mode 100644 index 9cdcc7a33..000000000 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsAdapter.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2018 Proton Technologies AG - * - * This file is part of ProtonVPN. - * - * ProtonVPN is 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. - * - * ProtonVPN is 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. - * - * You should have received a copy of the GNU General Public License - * along with ProtonVPN. If not, see . - */ -package com.protonvpn.android.ui.splittunneling; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.protonvpn.android.R; -import com.protonvpn.android.models.config.UserData; - -import java.util.ArrayList; -import java.util.List; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; - -import static android.view.View.GONE; -import static android.view.View.VISIBLE; - -public class AppsAdapter extends RecyclerView.Adapter { - - private List data = new ArrayList<>(); - private UserData userData; - - public void setData(List data) { - if (data != null) { - this.data.addAll(data); - } - notifyDataSetChanged(); - } - - public AppsAdapter(UserData userData) { - this.userData = userData; - } - - @NonNull - @Override - public AppsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new AppsViewHolder( - LayoutInflater.from(parent.getContext()).inflate(R.layout.item_app, parent, false)) { - @Override - public void removeApp(String packageName) { - userData.removeAppFromSplitTunnel(packageName); - } - - @Override - public void addApp(String packageName) { - userData.addAppToSplitTunnel(packageName); - } - }; - } - - @Override - public void onBindViewHolder(@NonNull AppsViewHolder holder, int position) { - holder.bindData(data.get(position)); - } - - @Override - public long getItemId(int position) { - return data.get(position).toString().hashCode(); - } - - @Override - public int getItemCount() { - return data.size(); - } - -} - -abstract class AppsViewHolder extends RecyclerView.ViewHolder { - - @BindView(R.id.textName) TextView textName; - @BindView(R.id.imageIcon) ImageView imageIcon; - @BindView(R.id.clearIcon) ImageView clearIcon; - @BindView(R.id.textAdd) TextView textAdd; - private SelectedApplicationEntry item; - - public AppsViewHolder(View view) { - super(view); - ButterKnife.bind(this, view); - } - - public abstract void removeApp(String packageName); - - public abstract void addApp(String packageName); - - @OnClick(R.id.layoutAddRemove) - public void layoutAddRemove() { - item.setSelected(!item.isSelected()); - if (item.isSelected()) { - addApp(item.getPackageName()); - } - else { - removeApp(item.getPackageName()); - } - initSelection(); - } - - public void bindData(SelectedApplicationEntry object) { - this.item = object; - initSelection(); - imageIcon.setImageDrawable(object.getIcon()); - textName.setText(object.toString()); - } - - private void initSelection() { - clearIcon.setVisibility(item.isSelected() ? VISIBLE : GONE); - textAdd.setVisibility(item.isSelected() ? GONE : VISIBLE); - } - -} diff --git a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt index c547fbeb1..6914b5685 100644 --- a/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt +++ b/app/src/main/java/com/protonvpn/android/ui/splittunneling/AppsDialog.kt @@ -35,15 +35,19 @@ import com.protonvpn.android.components.BaseDialog import com.protonvpn.android.components.ContentLayout import com.protonvpn.android.models.config.UserData import com.protonvpn.android.utils.sortedByLocaleAware -import kotlinx.coroutines.Dispatchers +import com.xwray.groupie.GroupAdapter +import com.xwray.groupie.GroupieViewHolder +import com.xwray.groupie.Section +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import me.proton.core.util.kotlin.DispatcherProvider import javax.inject.Inject @ContentLayout(R.layout.dialog_split_tunnel) class AppsDialog : BaseDialog() { - private lateinit var adapter: AppsAdapter - @BindView(R.id.textTitle) lateinit var textTitle: TextView @@ -60,31 +64,41 @@ class AppsDialog : BaseDialog() { lateinit var userData: UserData @Inject lateinit var activityManager: ActivityManager + @Inject + lateinit var mainScope: CoroutineScope + @Inject + lateinit var dispatcherProvider: DispatcherProvider override fun onViewCreated() { - list.layoutManager = LinearLayoutManager(activity) - adapter = AppsAdapter(userData) - list.adapter = adapter + val layoutManager = LinearLayoutManager(activity) + list.layoutManager = layoutManager + + val adapter = GroupAdapter() + val regularAppsSection = Section(AppsHeaderViewHolder(R.string.excludeAppsRegularSectionTitle)) + val systemAppsSection = Section(AppsHeaderViewHolder(R.string.excludeAppsSystemSectionTitle)) + systemAppsSection.add(LoadSystemAppsViewHolder { + loadSystemApps(layoutManager, adapter, systemAppsSection) + }) + + adapter.add(regularAppsSection) + adapter.add(systemAppsSection) + textTitle.setText(R.string.excludeAppsTitle) textDescription.setText(R.string.excludeAppsDescription) progressBar.visibility = View.VISIBLE + val packageManager = requireContext().packageManager val selection = userData.splitTunnelApps.toSet() viewLifecycleOwner.lifecycleScope.launch { - val allApps = getInstalledInternetApps(requireContext().packageManager) - val sortedApps = withContext(Dispatchers.Default) { - allApps.forEach { app -> - if (selection.contains(app.packageName)) { - app.isSelected = true - } - } - allApps.sortedByLocaleAware { it.toString() } - } - removeUninstalledApps(userData, allApps) - adapter.setData(sortedApps) - adapter.notifyDataSetChanged() + regularAppsSection.addAll( + getSortedAppViewHolders(packageManager, true, selection) + ) + list.adapter = adapter progressBar.visibility = View.GONE } + mainScope.launch { + removeUninstalledApps(packageManager, userData) + } } @OnClick(R.id.textDone) @@ -92,24 +106,68 @@ class AppsDialog : BaseDialog() { dismiss() } - private fun removeUninstalledApps(userData: UserData, allApps: List) { + private fun loadSystemApps( + layoutManager: LinearLayoutManager, + adapter: GroupAdapter, + systemAppsSection: Section + ) { + viewLifecycleOwner.lifecycleScope.launch { + val selection = userData.splitTunnelApps.toSet() + systemAppsSection.addAll( + getSortedAppViewHolders(requireContext().packageManager, false, selection) + ) + systemAppsSection.remove(systemAppsSection.getItem(1)) + val headerPosition = adapter.getAdapterPosition(systemAppsSection.getItem(0)) + layoutManager.scrollToPositionWithOffset(headerPosition, 0) + } + } + + private suspend fun removeUninstalledApps(packageManager: PackageManager, userData: UserData) { + val installedPackages = withContext(dispatcherProvider.Io) { + packageManager.getInstalledApplications(0).mapTo(mutableSetOf()) { it.packageName } + } val userDataAppPackages = userData.splitTunnelApps - val allAppPackages = HashSet(allApps.size) - allApps.mapTo(allAppPackages) { it.packageName } userDataAppPackages - .filterNot { allAppPackages.contains(it) } + .filterNot { installedPackages.contains(it) } .forEach { userData.removeAppFromSplitTunnel(it) } } + private suspend fun getSortedAppViewHolders( + packageManager: PackageManager, + withLaunchIntent: Boolean, + selection: Set + ): List { + val regularApps = getInstalledInternetApps(packageManager, withLaunchIntent) + val sortedRegularApps = withContext(dispatcherProvider.Comp) { + regularApps.forEach { app -> + if (selection.contains(app.packageName)) { + app.isSelected = true + } + } + regularApps.sortedByLocaleAware { it.toString() } + } + return sortedRegularApps.map { + AppViewHolder( + it, + onAdd = { userData.addAppToSplitTunnel(it.packageName) }, + onRemove = { userData.removeAppFromSplitTunnel(it.packageName) } + ) + } + } + private suspend fun getInstalledInternetApps( - packageManager: PackageManager - ): List = withContext(Dispatchers.IO) { + packageManager: PackageManager, + withLaunchIntent: Boolean + ): List = withContext(dispatcherProvider.Io) { val apps = packageManager.getInstalledApplications( 0 ).filter { appInfo -> - (packageManager.checkPermission(Manifest.permission.INTERNET, appInfo.packageName) + val hasInternet = (packageManager.checkPermission(Manifest.permission.INTERNET, appInfo.packageName) == PackageManager.PERMISSION_GRANTED) + val hasLaunchIntent = packageManager.getLaunchIntentForPackage(appInfo.packageName) != null + hasInternet && hasLaunchIntent == withLaunchIntent }.map { appInfo -> + ensureActive() getAppMetadata(packageManager, appInfo) } apps diff --git a/app/src/main/java/com/protonvpn/android/utils/BindableItemEx.kt b/app/src/main/java/com/protonvpn/android/utils/BindableItemEx.kt index 1452041ed..e0ebe8612 100644 --- a/app/src/main/java/com/protonvpn/android/utils/BindableItemEx.kt +++ b/app/src/main/java/com/protonvpn/android/utils/BindableItemEx.kt @@ -50,6 +50,7 @@ abstract class BindableItemEx : BindableItem() { super.bind(viewHolder, position, payloads, onItemClickListener, onItemLongClickListener) } + @CallSuper override fun bind(viewBinding: T, position: Int) { // Sometimes we can get 2 binds in a row without unbind in between clear() diff --git a/app/src/main/res/layout/item_app.xml b/app/src/main/res/layout/item_app.xml index 4b80c0c53..80b62bd79 100644 --- a/app/src/main/res/layout/item_app.xml +++ b/app/src/main/res/layout/item_app.xml @@ -17,81 +17,85 @@ You should have received a copy of the GNU General Public License along with ProtonVPN. If not, see . --> - + xmlns:tools="http://schemas.android.com/tools"> - - - - - + android:gravity="center_vertical" + android:orientation="horizontal" + tools:background="@color/grey"> + android:id="@+id/imageIcon" + android:layout_width="28dp" + android:layout_height="28dp" + android:layout_marginBottom="8dp" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:duplicateParentState="true" + android:scaleType="centerInside" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + - + android:layout_marginBottom="8dp" + android:layout_marginEnd="16dp" + android:layout_marginTop="8dp" + android:foregroundGravity="right|center_vertical" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + + + + + + - + - + diff --git a/app/src/main/res/layout/item_apps_header.xml b/app/src/main/res/layout/item_apps_header.xml new file mode 100644 index 000000000..0011ba2f0 --- /dev/null +++ b/app/src/main/res/layout/item_apps_header.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/app/src/main/res/layout/item_apps_load_system_apps.xml b/app/src/main/res/layout/item_apps_load_system_apps.xml new file mode 100644 index 000000000..28c736b74 --- /dev/null +++ b/app/src/main/res/layout/item_apps_load_system_apps.xml @@ -0,0 +1,44 @@ + + + + + + +