From 39d6162a97bb93b8f6c209c87e8d106a405e95a3 Mon Sep 17 00:00:00 2001 From: Dmitry Kazantsev <0rufim0@gmail.com> Date: Fri, 1 Nov 2024 19:31:48 +0300 Subject: [PATCH 1/5] Basic support for Android TV startup without cert. --- app/src/main/AndroidManifest.xml | 6 ++++++ .../java/tech/httptoolkit/android/MainActivity.kt | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 102dd00..92881b1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,11 @@ android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" /> + + + + + if (vpnNotConfigured) { + startActivityForResult(vpnIntent, START_VPN_REQUEST_NO_CERT) + } else { + onActivityResult(START_VPN_REQUEST_NO_CERT, RESULT_OK, null) + } + } .show() } } else if (vpnNotConfigured) { @@ -497,6 +505,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { Log.i(TAG, "onActivityResult: " + ( when (requestCode) { START_VPN_REQUEST -> "start-vpn" + START_VPN_REQUEST_NO_CERT -> "start-vpn-nocrt" INSTALL_CERT_REQUEST -> "install-cert" SCAN_REQUEST -> "scan-request" PICK_APPS_REQUEST -> "pick-apps" @@ -513,6 +522,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { if (requestCode == START_VPN_REQUEST && currentProxyConfig != null) { Log.i(TAG, "Installing cert...") ensureCertificateTrusted(currentProxyConfig!!) + } else if (requestCode == START_VPN_REQUEST_NO_CERT && currentProxyConfig != null) { + Log.i(TAG, "Ignore cert...") + onActivityResult(INSTALL_CERT_REQUEST, RESULT_OK, null) } else if (requestCode == INSTALL_CERT_REQUEST) { Log.i(TAG ,"Cert installed, checking notification perms...") ensureNotificationsEnabled() From 23b03fab86a94e2bfb7d20b782edd1882e8d770c Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Tue, 12 Nov 2024 15:46:21 +0100 Subject: [PATCH 2/5] Add a proper Android TV icon --- app/src/main/AndroidManifest.xml | 3 ++- app/src/main/res/drawable/ic_tv_banner.png | Bin 0 -> 6180 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_tv_banner.png diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 92881b1..427a792 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,7 +33,8 @@ android:supportsRtl="true" android:theme="@style/AppTheme" tools:targetApi="m" android:usesCleartextTraffic="true" - android:largeHeap="true"> + android:largeHeap="true" + android:banner="@drawable/ic_tv_banner"> ~goq#vi7tp1-RLd4=)IGoixQ*vI(kHyh+d+X zQD)}ueBbZ;+~>JJ&dhnvK6|gd*IDab@4F&(wACodZjwPD5K0YoWnBmaPafRYkr093 z)s+((@JISi-Ov>Rx&Pzeg=bO7pA8-|yQ#c%({r+N^DuX@gm`#(Ky4lEUEi3$vxGXi zSZD4^--JLIt2LAro_l8fUGVxyKb=jy?=(HXg_vCsqof>Z6;v=Z%A3O{i4xYPjp_Yz zcOY-PJKCD3mwO=1IWbN7iB*lK7*AUFTdOI_f^NdT0P=|YBQqTMoc)|gHwgrC$mitS zr}M(DJI)&wmyU=}4O3@FPhE}|Kl#Ltq*i?Lfy=F;4~2OmBBa$v&hR554ny(Om2-L^ zcR0shLueDMg&~pswM+rZD~1&EoQL;`IB2{W2;w88Km4DsrO2VkIWlitYyD(*&xp{j zj@7P+LdWmiR9|7hElXu4^wNOTmLxixLfq(J7H(r<*4=D=?A9^$roXP+^DFy#pn%7gQM!1 zl9hZv#bwSRxjVP~A=FIbY{?S80tb^}s&AK%Hm)#T>THXGelji$1XYmpa0<0m#yy1< z#2ZqyJj>4AeFc?|iOaZV-+A0MYC4ljY*;P&a(kqvGD(O)OFw)XR*=oQ&3^&9l` z?3dDEGT+z|dJIIaT)30MT95RH%z-EKncbTZ4Fw!ED|{5V=?`U%SRVN$_9n$n+|6r> zU%zz5U$QB!j=+KoRKPJ46Mr<&sd|*IK6Ds;eGePJWv^35!q@grBFq=(v9GWH$c1W< z4eXTgu69?I{+LqccQKSEuSP*!o6&+uxB$)HZ>i5klX|!0~+y|2;_LCUAG`0NnBcT;6N> z{3CyN+g6}5y@er1^8Yy<{93)Qd~2%iVMs&Ul*!hP;zge7pbq9QO#UlVTI!|GEdr!N zRFHy)FI8)Y__?XZfT*6v2Bq=+zVc*!67B4z&M4@xfeb}(>#35X&maL&-q4_23+>Xi z)O8=`oOqibWTK02B%!Yw?(bb5b-Je(Ty2f5xKo;TL);Ra-cb^LX^}w=G|D{?C?oCcz`c(<<-mlfDJwUVgoJ+dN)|16Zyo#M6c2q0De8F7f<>Qle{(#wz zrzXlH_tl?{fL`YP-M(|W*O{JKp*)*`YS$`ttr8&{OOnP~-Hh1#v7d@_Dp((TT6o}U zz){_>c$!g=YC?%>@Ay`!G{Y1Cjkw=-bAQffo*JBb1cvo=VtzkJ3Lm*g*sGP8k@>KS zSXFHvg%K?*q#W_^C##OJ|0^)Ou4Zu_zGD&`0-Ql@a$s(Jltg?J2^=J(H^BNe0^$RM zpU93fW^Udv+WS6o>J&6S^cOQ6&qS2hoIph}CFnMy9QU z>ft_1${Q*FbHDO!0>}*&Z@)XScpFtnw{tu8^b}RM%5#V721@I;srM71$~2X5!?Ige zV?XaY_%Y8|4JYMjzGe!zP9B}?GjJUuBlva6M=o3)jtTV)Wfo0V2JaQ^aa{(6bvtv_ zbzO1wX^zD>`3r~h^Fs4&>-Y7k7};_iWkXj2iIxsBEKV3jv%^}q#g`5aE(~-I%8iS6 zSJ}C7wMzd?=qhF$`$I*u6<^~jSS!mOQee;2s0|OXU)Jj}eI^_JSl%J$zyrM{b}2&+hN|k!(7r%>Bl9` zON(s}GT2G$XNw9tAw@yv>kF@ZKg&NXmkn8mCN#H}_r?FHePRcthZ1qn6LM3By!ftT zS1~>wwvs<9&=*=V{n!IG>4E4y`oSFm7QHQIFZ;3T)%SqKLff#_hmR0&753`#Mj=Q! zMX9}6{) z-gY)?n0m$MOCdkVFRmMgJZ{o;6%~j9Wz9koDrO&e%xL9zNyO20-M2Uj%r71>r08Lf z&tO4uz7R}^Ynx&Orjz;ia29PsnPEsqD?U@O61z$ukB^u@dO)jB?P_EDM&v#fJC}Q0 zcOa34lu_}TuCp0LcaiY!({LxGPI;4e7eiWP%-nOd@!?BaRN+T6lAm9$?Z>g= zW}8Dx0=4W#j~6l%Y#m&DDc`ffRk}1>%gzLoiHP!ull^`tm9;EgFC+K_m2MY9*6uRb z;Z}^n$t1sBH=c$mN9C#xQa9VgPMwH;9Z_eS6BJL4B4!ViDLEwfDBSwCcM7U%URi{x z^Q|!1Mx$o$DEj&uvJT$K;pGJyBjb1$rm7CWp?{?HYcf<<=#lVsfdd z47mnPZf;H2RzTHjfN#f0l&6Lck5M(-u_$j^k~;TrXIwc_%0#@h`$V&?`sd_t%5soi ziRPJwHb^I~87{DWasnjRl{nXt$eCi@hjn>@&+!?b_dMyXq%0+4OXbuF!SGJqMxE%H z;?4~@OCcXu zHJLR(`pe^piaEADO+(i;DI7)pGwvNCv#U3%EAyCPQSG~x@uHba^qq&D83i(ABSWY+H&sUZnUxg+&Oz}f|`1xzVkIT zLlflH#_e_glddo8$1TUiMcS+b1DND^B8N5U@}}OV2Y0UT*0VBZm=cQ{LEWkR?)u=o zDS_i(XPU~d)5Vd*J!SJJ_$$}^$og#7j!}U$w0Ny|8-^Dh4FYK%M%X5#w`m6P*6J&y z$~(ada!pSBSZ4xYIJ?z{JQkeoif~`wS#;bPncR7vMsu%9zBqK+Ou7tkvE!CTqJOX$ z$3)Ieeboj}m7G6LO`>ihZ#@#58yr?8y3pP(w8fei(lCjeZZQmS5yv~q3V^S|NPzqt zO+kD4o}=0+oWFrQIujpI9ELZl6yUKZ*a;@KMWrxgDZcS9fu87)=5eRhgY^$xLb|ymo zVps$?1V968>4+gd+JaxS?f&!h^d2g8BJgY-y(q1cu!pg?ptjzF0e+oWd!znLLY~x$ zFET`a5A{3btldCHYl|(CfKq6*USp*APaoutTIixEUF)C%*t1VJpXmJD)$kwU#a=Mb zM5BFr@nIj6n<@+$cy=1wW*DAiWEzQ_O4ps;`-}0Bw}XOgd6yx$`Im@6*#QwjGQnIH#89qRaW6W_5kNy%?9!Xx?84f1j)(n$ z{zE6Z)}?4(AKZi&z%5DRl59FdureSd+;rJcx|hQ>FyZZKLM0zm4!=PyU}%ad`i(uA~!y>UN}WiO=Fn*S$T zOs2Uy?hl z`BM08%za^Dwyi~7dYG)$&{Q&h9v)s~|Hw}vPsQ7|CQ2e`bX$7SKtDzScCPT^tjZu= zrS7cq3M&Udd$Z}eH&cLV{=G8vkM-Idh73t65ZIk(@0vwi0otJLRUa<|A>f+}AfG`n zD>U2HJn*STHvSH=-KVTbXjc34n=OBE7yz!}W!z#6-Udxg2ci7KhYOxp?#m&hUc>-r zKFZ_RWiGPq4?0$)ji+<3jjnwfcOm4Sw~z8S=W#NJ=xPICrp}O3vU7B@T*IU+*EMP8 zqro`Yf5*K;^7~k`S#rKl9SjaTO21BO5c&rphiPu-XUC76jANapvu@A>0Ctfu+k05eh zV70qh!@7P<%y7s9V0pvmTLC8Di7Y&?)sIWc|I}p2`5T|;_wh@Es!j?ost>n4wPtJE zRJYK^Wi1C#LX_xs&>Im4femjvdMN|BHT2x8gpq;wKkaMI1W&^8HWV&)M;^LvizD=t z)ndYpOi3X^bW#%|rSVeU>gWb#5L06Mb+ zw16O4Os(EkK&f1roG-EZw;zu(tT5=$XaXeEd=KUV61s@DgSzf#sIKcsNGwpBL(afD z&*@uB;1U1Xl_Y5gA8AS4o8yNdigcT@g zMNe-fWoRyfYQP&Pob;PrR@KgNYeOO5O7qkk7v%@O+Z#yY$0~ATJv25h3#314Bh{JM3+wvc4NVDBO>T&cn z=Xnj$oneJbl-@gpr9Gi@4ip?#h7iQpSeA;^+LDO#P-(ZQH-9iW?8lp5&m4FJ3?%>l zxVcZsdS6_^>z$y;Uu%KBIsVGAk+FYN&CuEM@Z$p#hEv-P&Rv7rt z2Im0U3Bgu52=Btc{K@espq~K_{kJ%anBvIHEQ;0=C4?awX%FB{ZVxw&VYHt_9M)+v z5pZ5(3VGT!^moZz_MchYE)qf_5l#R=2d@*vhurgTbK8SiOdvfRYFHVXJs(Wfkd+$) z$r3g`2Nh3k`GB`kL~GYe*X#+MvT2%n<%hb?w2+;${MOGLIz2MReJuJs0#In$a?f9k z;<_na$v>>uZ8D%Q)5N4@n*cD1%&7tzzOFv?sRt-rV;8OfmX z>-*dUdzq5u=;viRAJ-Ops()swZ`->eb+`8ZY>1Tr1aRYD!*indw^xOL7}C8PtIobd zCHU$Y6Q0fTfy6w*%c_Mq^!$5PR(}D1(K;Yfqu@k}Xp)_(scMPhbyY!({GwbKXx0G1 zUbL1$dds^j1?UOSvT+yX6jN>)Id`p{X}p0q5Gk#qa}U@Qo_-k`&FIpYu>%xP5G{qq z$TZ8h9~ZQyJAASSDrNKp#p~NXEtjd`#=vWnEZ@j(;caW4yGy9U1NuYXEfi0gltJAb9snpknR?te0Ke*f*Zw$9OhElG6x$3lI>WheL?X>@jU zMtE+rEpU7xau??{c!HFJfnm*f&wmR$k;O?wm;c7(d9@oDM0?~`Rz3_`fFK%_^ z;_*X1zaRxv^|O0dKs3K@ADpLQl~ZX(ewx1_fRBqe*Qtx(n%(o?^`MYr6o#0%?J}a& zEDkPj@w(;NB(ylDo*NhJ;fUbNG(fxk*|&>QE2(E*XS-BrOp*~-_cL_j8pGpA z&?{K`E*s-^wCO9@16rM4|9XN?V9l)-{&dl^ZTDv`XM0JLbb-!Mocx=6Z_eO$PY_<3 zb@`icba7Uq7A;(3--DFXSyrQ z%=uf>fzCjOi~I_`MqiTS{(1a(Y&Ln=3DXnHl20CzjvPPDg{izAd+>bm(A{A4Vomo? zOtw(7)cwA>O^ordh(L@De1j#z!TUB!Grz3^1tHfOyRb&0@J#meZ-lOT8zFM4YJa%y%I9MJU>7 z^HTKX1uAziD4QlX=Fe{}jkLMk4`0qa$py;Wj}G?#+UUkIUh%G~%dq`{fet@JLq%J;LeVVfe*l+26AJ(U literal 0 HcmV?d00001 From b8bead468637066195e1d7c65dde133aa40112b9 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Tue, 12 Nov 2024 18:12:16 +0100 Subject: [PATCH 3/5] Don't show QR code scan button on devices without cameras --- .../java/tech/httptoolkit/android/MainActivity.kt | 13 ++++++++++--- app/src/main/res/values/strings.xml | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/tech/httptoolkit/android/MainActivity.kt b/app/src/main/java/tech/httptoolkit/android/MainActivity.kt index d256819..2cc706a 100644 --- a/app/src/main/java/tech/httptoolkit/android/MainActivity.kt +++ b/app/src/main/java/tech/httptoolkit/android/MainActivity.kt @@ -238,11 +238,18 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { when (mainState) { MainState.DISCONNECTED -> { statusText.setText(R.string.disconnected_status) + buttonContainer.visibility = View.VISIBLE - detailContainer.addView(detailText(R.string.disconnected_details)) + val hasCamera = this.packageManager + .hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) - buttonContainer.visibility = View.VISIBLE - buttonContainer.addView(primaryButton(R.string.scan_button, ::scanCode)) + if (hasCamera) { + detailContainer.addView(detailText(R.string.disconnected_details)) + val scanQrButton = primaryButton(R.string.scan_button, ::scanCode) + buttonContainer.addView(scanQrButton) + } else { + detailContainer.addView(detailText(R.string.disconnected_no_camera_details)) + } val lastProxy = app.lastProxy if (lastProxy != null) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9372a71..34c384b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,8 +11,8 @@ Connected Oh no! - To intercept this device, start HTTP Toolkit on your computer, and activate Android interception there via QR code or using ADB. - + To intercept this device, start HTTP Toolkit on your computer, and activate Android interception there via QR code or using ADB. + To intercept this device, start HTTP Toolkit on your computer, and activate Android interception there using the ADB or Frida options. to %s on port %d via ADB tunnel From 15de24485620442474ed41889c9e1a91042b2826 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Tue, 12 Nov 2024 19:16:36 +0100 Subject: [PATCH 4/5] Reorganize cert prompt flow & simplify 'skip' logic to match --- .../tech/httptoolkit/android/MainActivity.kt | 106 +++++++++--------- 1 file changed, 51 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/tech/httptoolkit/android/MainActivity.kt b/app/src/main/java/tech/httptoolkit/android/MainActivity.kt index 2cc706a..6f552de 100644 --- a/app/src/main/java/tech/httptoolkit/android/MainActivity.kt +++ b/app/src/main/java/tech/httptoolkit/android/MainActivity.kt @@ -43,7 +43,6 @@ import java.security.cert.X509Certificate const val START_VPN_REQUEST = 123 -const val START_VPN_REQUEST_NO_CERT = 124 const val INSTALL_CERT_REQUEST = 456 const val SCAN_REQUEST = 789 const val PICK_APPS_REQUEST = 499 @@ -343,48 +342,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { Log.i(TAG, if (vpnIntent != null) "got intent" else "no intent") val vpnNotConfigured = vpnIntent != null - if (whereIsCertTrusted(config) == null && PROMPTED_CERT_SETUP_SUPPORTED) { - // The cert isn't trusted, and the VPN may need setup, so there'll be a series of prompts - // here. Explain them beforehand, so users understand what's going on. - withContext(Dispatchers.Main) { - MaterialAlertDialogBuilder(this@MainActivity) - .setTitle("Enable interception") - .setIcon(R.drawable.ic_info_circle) - .setMessage( - "To intercept traffic from this device, you need to " + - (if (vpnNotConfigured) "activate HTTP Toolkit's VPN and " else "") + - "trust your HTTP Toolkit's certificate authority. " + - "\n\n" + - "Please accept the following prompts to allow this." + - if (!isDeviceSecured(applicationContext)) - "\n\n" + - "Due to Android security requirements, trusting the certificate will " + - "require you to set a PIN, password or pattern for this device." - else " To trust the certificate, your device PIN will be required." - ) - .setPositiveButton("Ok") { _, _ -> - if (vpnNotConfigured) { - startActivityForResult(vpnIntent, START_VPN_REQUEST) - } else { - onActivityResult(START_VPN_REQUEST, RESULT_OK, null) - } - } - .setNegativeButton("Continue without certificate") { _, _ -> - if (vpnNotConfigured) { - startActivityForResult(vpnIntent, START_VPN_REQUEST_NO_CERT) - } else { - onActivityResult(START_VPN_REQUEST_NO_CERT, RESULT_OK, null) - } - } - .show() - } - } else if (vpnNotConfigured) { - // In this case the VPN needs setup, but the cert is trusted already, so it's - // a single confirmation. Pretty clear, no need to explain. This happens if the - // VPN/app was removed from the device in the past, or when using injected system certs. + if (vpnNotConfigured) { + // Show the 'Enable the VPN' prompt startActivityForResult(vpnIntent, START_VPN_REQUEST) } else { - // VPN is trusted & cert setup already, lets get to it. + // VPN is trusted already, continue onActivityResult(START_VPN_REQUEST, RESULT_OK, null) } @@ -512,7 +474,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { Log.i(TAG, "onActivityResult: " + ( when (requestCode) { START_VPN_REQUEST -> "start-vpn" - START_VPN_REQUEST_NO_CERT -> "start-vpn-nocrt" INSTALL_CERT_REQUEST -> "install-cert" SCAN_REQUEST -> "scan-request" PICK_APPS_REQUEST -> "pick-apps" @@ -529,9 +490,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { if (requestCode == START_VPN_REQUEST && currentProxyConfig != null) { Log.i(TAG, "Installing cert...") ensureCertificateTrusted(currentProxyConfig!!) - } else if (requestCode == START_VPN_REQUEST_NO_CERT && currentProxyConfig != null) { - Log.i(TAG, "Ignore cert...") - onActivityResult(INSTALL_CERT_REQUEST, RESULT_OK, null) } else if (requestCode == INSTALL_CERT_REQUEST) { Log.i(TAG ,"Cert installed, checking notification perms...") ensureNotificationsEnabled() @@ -656,19 +614,16 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { if (existingTrust == null) { Log.i(TAG, "Certificate not trusted, prompting to install") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - // Android 11+, with no trusted cert: we need to download the cert to Downloads and - // then tell the user how to install it manually: - launch { promptToManuallyInstallCert(proxyConfig.certificate) } - } else { + if (PROMPTED_CERT_SETUP_SUPPORTED) { // Up until Android 11, we can prompt the user to install the CA cert into the user // CA store. Notably, if the cert is already installed as a system cert but // disabled, this will get triggered, and will enable the cert, rather than adding // a normal user cert. - val certInstallIntent = KeyChain.createInstallIntent() - certInstallIntent.putExtra(EXTRA_NAME, "HTTP Toolkit CA") - certInstallIntent.putExtra(EXTRA_CERTIFICATE, proxyConfig.certificate.encoded) - startActivityForResult(certInstallIntent, INSTALL_CERT_REQUEST) + launch { promptToAutoInstallCert(proxyConfig.certificate) } + } else { + // Android 11+, with no trusted cert: we need to download the cert to Downloads and + // then tell the user how to install it manually: + launch { promptToManuallyInstallCert(proxyConfig.certificate) } } } else { Log.i(TAG, "Certificate already trusted, continuing") @@ -676,6 +631,39 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { } } + private suspend fun promptToAutoInstallCert(certificate: Certificate) { + withContext(Dispatchers.Main) { + MaterialAlertDialogBuilder(this@MainActivity) + .setTitle("Enable HTTPS interception") + .setIcon(R.drawable.ic_info_circle) + .setMessage( + "To intercept HTTPS traffic from this device, you need to " + + "trust your HTTP Toolkit's certificate authority. " + + "\n\n" + + "Please accept the following prompts to allow this." + + if (!isDeviceSecured(applicationContext)) + "\n\n" + + "Due to Android security requirements, trusting the certificate will " + + "require you to set a PIN, password or pattern for this device." + else " To trust the certificate, your device PIN will be required." + ) + .setPositiveButton("Install") { _, _ -> + val certInstallIntent = KeyChain.createInstallIntent() + certInstallIntent.putExtra(EXTRA_NAME, "HTTP Toolkit CA") + certInstallIntent.putExtra(EXTRA_CERTIFICATE, certificate.encoded) + startActivityForResult(certInstallIntent, INSTALL_CERT_REQUEST) + } + .setNeutralButton("Skip") { _, _ -> + onActivityResult(INSTALL_CERT_REQUEST, RESULT_OK, null) + } + .setNegativeButton("Cancel") { _, _ -> + disconnect() + } + .setCancelable(false) + .show() + } + } + @RequiresApi(Build.VERSION_CODES.Q) private suspend fun promptToManuallyInstallCert(cert: Certificate, repeatPrompt: Boolean = false) { if (!repeatPrompt) { @@ -713,7 +701,12 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { Html.fromHtml( """

- Android ${Build.VERSION.RELEASE} doesn't allow automatic certificate setup. + ${ + if (PROMPTED_CERT_SETUP_SUPPORTED) + "Automatic certificate installation failed, so it must be done manually." + else + "Android ${Build.VERSION.RELEASE} doesn't allow automatic certificate setup." + }

To allow HTTP Toolkit to intercept HTTPS traffic: @@ -740,6 +733,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { .setPositiveButton("Open security settings") { _, _ -> startActivityForResult(Intent(Settings.ACTION_SECURITY_SETTINGS), INSTALL_CERT_REQUEST) } + .setNeutralButton("Skip") { _, _ -> + onActivityResult(INSTALL_CERT_REQUEST, RESULT_OK, null) + } .setNegativeButton("Cancel") { _, _ -> disconnect() } From bb41a934f4231a46ecf0120654cd9201a3610b6b Mon Sep 17 00:00:00 2001 From: Dmitry Kazantsev <0rufim0@gmail.com> Date: Wed, 13 Nov 2024 16:03:15 +0300 Subject: [PATCH 5/5] Fix Android TV icon for old tv boxes --- .../{drawable => drawable-nodpi}/ic_tv_banner.png | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/res/{drawable => drawable-nodpi}/ic_tv_banner.png (100%) diff --git a/app/src/main/res/drawable/ic_tv_banner.png b/app/src/main/res/drawable-nodpi/ic_tv_banner.png similarity index 100% rename from app/src/main/res/drawable/ic_tv_banner.png rename to app/src/main/res/drawable-nodpi/ic_tv_banner.png